All of lore.kernel.org
 help / color / mirror / Atom feed
From: Xiaodong Gong <gordongong0350@gmail.com>
To: kwolf@redhat.com, stefanha@redhat.com, arei.gonglei@huawei.com,
	hutao@cn.fujitsu.com, rudy.zhangmin@huawei.com, jcody@redhat.com,
	cyliu@suse.com, brian.luohao@huawei.com, hahn@univention.de,
	petrutlucian94@gmail.com
Cc: Xiaodong Gong <gongxiaodong1@huawei.com>, qemu-devel@nongnu.org
Subject: [Qemu-devel] [PATCH v6] Support vhd type VHD_DIFFERENCING
Date: Fri, 17 Oct 2014 23:45:56 +0800	[thread overview]
Message-ID: <1413560756-5777-1-git-send-email-gongxiaodong1@huawei.com> (raw)

Now qemu only supports vhd type VHD_FIXED and VHD_DYNAMIC, so qemu
can't read snapshot volume of vhd, and can't support other storage
features of vhd file.

This patch add read parent information in function "vpc_open", read
bitmap in "vpc_read", and change bitmap in "vpc_write".

Signed-off-by: Xiaodong Gong <gongxiaodong1@huawei.com>
---
Changes since v5:
- Change malloc to g_malloc. (Gonglei)
- Fix the bug of free(null). (Gonglei)

Changes since v4:
- Parse the batmap only when the version of VHD > 1.2. (Lucian Petrut)
- Add support to parent location of W2RU. (Lucian Petrut) (Philipp Hahn)

Changes since v3:
- Remove the PARENT_MAX_LOC.

Changes since v2:
- Change MACX to PLATFAORM_MACX. (Kevin Wolf)
- Return with EINVAL to parent location is W2RU and W2KU. (Kevin Wolf)
- Change -1 == ret to a natrual order of ret == -1. (Kevin Wolf)
- Get rid of the get_sector_offset_diff, get_sector_offset
  supports VHD_DIFF. (Kevin Wolf)
- Return code of get_sector_offset is set to, -1 for error,
  -2 for not allocate, -3 for in parent. (Kevin Wolf)
- Fix un init ret of vpc_write, when nb_sector == 0. (Kevin Wolf)
- Change if (diff == ture) to if (diff) and so on. (Kevin Wolf)
- Add PARENT_MAX_LOC to more understand. (Kevin Wolf)
- Restore the boundary check to write on dynamic type in
  get_sector_offset. (Kevin Wolf)

Changes since v1:
- Add Boundary check to any input. (Stefan Hajnoczi)
- Clean the code no used after in vpc_open. (Stefan Hajnoczi)
- Change bdrv_co_readv() to bdrv_preadv in vpc_read. (Stefan Hajnoczi)
- Added some code to make it easy to understand. (Stefan Hajnoczi)
---
 block/vpc.c | 425 ++++++++++++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 355 insertions(+), 70 deletions(-)

diff --git a/block/vpc.c b/block/vpc.c
index e08144a..15e57c1 100644
--- a/block/vpc.c
+++ b/block/vpc.c
@@ -29,17 +29,27 @@
 #if defined(CONFIG_UUID)
 #include <uuid/uuid.h>
 #endif
+#include <iconv.h>
 
 /**************************************************************/
 
 #define HEADER_SIZE 512
+#define DYNAMIC_HEADER_SIZE 1024
+#define PARENT_LOCATOR_NUM 8
+#define MACX_PREFIX_LEN 7 /* file:// */
+#define TBBATMAP_HEAD_SIZE 28
+
+#define PLATFORM_MACX 0x5863614d /* big endian */
+#define PLATFORM_W2RU 0x75723257
+
+#define VHD_VERSION(major, minor)  (((major) << 16) | ((minor) & 0x0000FFFF))
 
 //#define CACHE
 
 enum vhd_type {
     VHD_FIXED           = 2,
     VHD_DYNAMIC         = 3,
-    VHD_DIFFERENCING    = 4,
+    VHD_DIFF            = 4,
 };
 
 // Seconds since Jan 1, 2000 0:00:00 (UTC)
@@ -138,6 +148,15 @@ typedef struct BDRVVPCState {
     Error *migration_blocker;
 } BDRVVPCState;
 
+typedef struct vhd_tdbatmap_header {
+    char magic[8]; /* always "tdbatmap" */
+
+    uint64_t batmap_offset;
+    uint32_t batmap_size;
+    uint32_t batmap_version;
+    uint32_t checksum;
+} QEMU_PACKED VHDTdBatmapHeader;
+
 static uint32_t vpc_checksum(uint8_t* buf, size_t size)
 {
     uint32_t res = 0;
@@ -157,6 +176,102 @@ static int vpc_probe(const uint8_t *buf, int buf_size, const char *filename)
     return 0;
 }
 
+static int vpc_read_backing_loc(VHDDynDiskHeader *dyndisk_header,
+                                BlockDriverState *bs,
+                                Error **errp)
+{
+    BDRVVPCState *s = bs->opaque;
+    int64_t data_offset = 0;
+    int data_length = 0;
+    uint32_t platform;
+    bool done = false;
+    int parent_locator_offset = 0;
+    int i;
+    int ret = 0;
+
+    for (i = 0; i < PARENT_LOCATOR_NUM; i++) {
+        data_offset =
+            be64_to_cpu(dyndisk_header->parent_locator[i].data_offset);
+        data_length =
+            be32_to_cpu(dyndisk_header->parent_locator[i].data_length);
+        platform = dyndisk_header->parent_locator[i].platform;
+
+        /* Extend the location offset */
+        if (parent_locator_offset < data_offset) {
+            parent_locator_offset = data_offset;
+        }
+
+        if (done) {
+            continue;
+        }
+
+        /* Skip "file://" in MacX platform */
+        if (platform == PLATFORM_MACX) {
+            data_offset += MACX_PREFIX_LEN;
+            data_length -= MACX_PREFIX_LEN;
+        }
+
+        /* Read location of backing file */
+        if (platform == PLATFORM_MACX || platform == PLATFORM_W2RU) {
+            if (data_offset > s->max_table_entries * s->block_size) {
+                return -1;
+            }
+            if (data_length > BDRV_SECTOR_SIZE) {
+                return -1;
+            }
+            ret = bdrv_pread(bs->file, data_offset, bs->backing_file,
+                data_length);
+            if (ret < 0) {
+                return ret;
+            }
+            bs->backing_file[data_length] = '\0';
+        }
+
+        /* Convert location to ACSII string */
+        if (platform == PLATFORM_MACX) {
+            done = true;
+
+        } else if (platform == PLATFORM_W2RU) {
+            /* Must be UTF16-LE to ASCII */
+            char *out, *optr;
+            int j;
+
+            optr = out = (char *) g_malloc(data_length + 1);
+            if (out == NULL) {
+                ret = -1;
+                return ret;
+            }
+
+            for (j = 0; j < data_length + 1; j++) {
+                out[j] = bs->backing_file[2*j];
+            }
+            out[data_length + 1] = '\0';
+
+            while (*optr != '\0') {
+                if (*optr == '\\') {
+                    *optr = '/';
+                }
+                optr++;
+            }
+
+            strncpy(bs->backing_file, out, data_length + 1);
+
+            g_free(out);
+            out = NULL;
+
+            done = true;
+        }
+    }
+
+    if (bs->backing_file[0] == '\0') {
+        error_setg(errp, "block-vpc: differencing is not support in w2ku");
+        ret = -EINVAL;
+        return ret;
+    }
+
+    return parent_locator_offset;
+}
+
 static int vpc_open(BlockDriverState *bs, QDict *options, int flags,
                     Error **errp)
 {
@@ -164,11 +279,14 @@ static int vpc_open(BlockDriverState *bs, QDict *options, int flags,
     int i;
     VHDFooter *footer;
     VHDDynDiskHeader *dyndisk_header;
-    uint8_t buf[HEADER_SIZE];
+    uint8_t buf[DYNAMIC_HEADER_SIZE];
+    uint8_t tdbatmap_header_buf[TBBATMAP_HEAD_SIZE];
     uint32_t checksum;
     uint64_t computed_size;
-    int disk_type = VHD_DYNAMIC;
+    uint32_t disk_type;
     int ret;
+    VHDTdBatmapHeader *tdbatmap_header;
+    int parent_locator_offset = 0;
 
     ret = bdrv_pread(bs->file, 0, s->footer_buf, HEADER_SIZE);
     if (ret < 0) {
@@ -176,6 +294,8 @@ static int vpc_open(BlockDriverState *bs, QDict *options, int flags,
     }
 
     footer = (VHDFooter *) s->footer_buf;
+    disk_type = be32_to_cpu(footer->type);
+
     if (strncmp(footer->creator, "conectix", 8)) {
         int64_t offset = bdrv_getlength(bs->file);
         if (offset < 0) {
@@ -230,9 +350,9 @@ static int vpc_open(BlockDriverState *bs, QDict *options, int flags,
         goto fail;
     }
 
-    if (disk_type == VHD_DYNAMIC) {
+    if (disk_type == VHD_DYNAMIC || disk_type == VHD_DIFF) {
         ret = bdrv_pread(bs->file, be64_to_cpu(footer->data_offset), buf,
-                         HEADER_SIZE);
+                         DYNAMIC_HEADER_SIZE);
         if (ret < 0) {
             goto fail;
         }
@@ -286,6 +406,37 @@ static int vpc_open(BlockDriverState *bs, QDict *options, int flags,
         s->free_data_block_offset =
             (s->bat_offset + (s->max_table_entries * 4) + 511) & ~511;
 
+        /* Read tdbatmap header by offset */
+        if (footer->version >= VHD_VERSION(1, 2)) {
+            ret = bdrv_pread(bs->file, s->free_data_block_offset,
+                tdbatmap_header_buf, TBBATMAP_HEAD_SIZE);
+            if (ret < 0) {
+                goto fail;
+            }
+
+            tdbatmap_header = (VHDTdBatmapHeader *) tdbatmap_header_buf;
+            if (!strncmp(tdbatmap_header->magic, "tdbatmap", 8)) {
+                s->free_data_block_offset =
+                    be32_to_cpu(tdbatmap_header->batmap_size) * 512
+                    + be64_to_cpu(tdbatmap_header->batmap_offset);
+            }
+        }
+
+        /* Read backing file location from dyn header table */
+        if (dyndisk_header->parent_name[0] || dyndisk_header->parent_name[1]) {
+            ret = parent_locator_offset = vpc_read_backing_loc(dyndisk_header,
+                bs, errp);
+            if (ret < 0) {
+                goto fail;
+            }
+        }
+
+        if (s->free_data_block_offset < parent_locator_offset
+            + BDRV_SECTOR_SIZE) {
+            s->free_data_block_offset = parent_locator_offset
+                + BDRV_SECTOR_SIZE;
+        }
+
         for (i = 0; i < s->max_table_entries; i++) {
             be32_to_cpus(&s->pagetable[i]);
             if (s->pagetable[i] != 0xFFFFFFFF) {
@@ -340,35 +491,76 @@ static int vpc_reopen_prepare(BDRVReopenState *state,
 }
 
 /*
- * Returns the absolute byte offset of the given sector in the image file.
- * If the sector is not allocated, -1 is returned instead.
+ * Returns the absolute byte offset of the given sector in the differencing
+ * image file.
+ *
+ * If error happened, -1 is returned.
+ *
+ * When write all type or read dynamic, if the sector is not allocated, -2
+ * is returned instead. If the sector is allocated in current file, the block
+ * offset is returned.
  *
- * The parameter write must be 1 if the offset will be used for a write
- * operation (the block bitmaps is updated then), 0 otherwise.
+ * When read diff. If the sector is not allocated, -2 is returned instead.
+ * If the sector is allocated in the backing file, -3 is returned. If the
+ * sector is allocated in current file, the block offset is returned.
  */
 static inline int64_t get_sector_offset(BlockDriverState *bs,
-    int64_t sector_num, int write)
+    int64_t sector_num, bool write, bool diff)
 {
     BDRVVPCState *s = bs->opaque;
-    uint64_t offset = sector_num * 512;
-    uint64_t bitmap_offset, block_offset;
+    uint64_t offset = sector_num << BDRV_SECTOR_BITS;
+    uint64_t bitmap_offset;
     uint32_t pagetable_index, pageentry_index;
+    int64_t block_offset = LONG_MIN;
+    int ret;
 
     pagetable_index = offset / s->block_size;
-    pageentry_index = (offset % s->block_size) / 512;
+    pageentry_index = (offset % s->block_size) >> BDRV_SECTOR_BITS;
 
-    if (pagetable_index >= s->max_table_entries || s->pagetable[pagetable_index] == 0xffffffff)
-        return -1; // not allocated
+    if (pagetable_index >= s->max_table_entries) {
+        return -2;
+    }
+    if (s->pagetable[pagetable_index] == 0xffffffff) {
+        if (!write && diff) {
+            return -3; /* parent allocated */
+        } else {
+            return -2; /* not allocated */
+        }
+    }
 
-    bitmap_offset = 512 * (uint64_t) s->pagetable[pagetable_index];
-    block_offset = bitmap_offset + s->bitmap_size + (512 * pageentry_index);
+    bitmap_offset = (uint64_t) s->pagetable[pagetable_index]
+        << BDRV_SECTOR_BITS;
+
+    if (!diff || write) {
+        block_offset = bitmap_offset + s->bitmap_size
+            + (pageentry_index << BDRV_SECTOR_BITS);
+    } else {
+        uint32_t bitmap_index, bitmapentry_index;
+        uint8_t bitmap[s->bitmap_size];
 
+        if (bitmap_offset > s->max_table_entries * s->block_size) {
+            return -1;
+        }
+        ret = bdrv_pread(bs->file, bitmap_offset, bitmap, s->bitmap_size);
+        if (ret < 0) {
+            return -1;
+        }
+
+        bitmap_index = pageentry_index / 8;
+        bitmapentry_index = 7 - pageentry_index % 8;
+        if (bitmap[bitmap_index] & 0x1 << bitmapentry_index) {
+            block_offset = bitmap_offset + s->bitmap_size
+                + (pageentry_index << BDRV_SECTOR_BITS);
+        } else {
+            return -3;
+        }
+    }
     // We must ensure that we don't write to any sectors which are marked as
     // unused in the bitmap. We get away with setting all bits in the block
     // bitmap each time we write to a new block. This might cause Virtual PC to
     // miss sparse read optimization, but it's not a problem in terms of
     // correctness.
-    if (write && (s->last_bitmap_offset != bitmap_offset)) {
+    if (!diff && write && (s->last_bitmap_offset != bitmap_offset)) {
         uint8_t bitmap[s->bitmap_size];
 
         s->last_bitmap_offset = bitmap_offset;
@@ -376,7 +568,7 @@ static inline int64_t get_sector_offset(BlockDriverState *bs,
         bdrv_pwrite_sync(bs->file, bitmap_offset, bitmap, s->bitmap_size);
     }
 
-//    printf("sector: %" PRIx64 ", index: %x, offset: %x, bioff: %" PRIx64 ", bloff: %" PRIx64 "\n",
+//  printf("sector: %" PRIx64 ", index: %x, offset: %x, bioff: %" PRIx64 ", bloff: %" PRIx64 "\n",
 //	sector_num, pagetable_index, pageentry_index,
 //	bitmap_offset, block_offset);
 
@@ -437,7 +629,8 @@ static int rewrite_footer(BlockDriverState* bs)
  *
  * Returns the sectors' offset in the image file on success and < 0 on error
  */
-static int64_t alloc_block(BlockDriverState* bs, int64_t sector_num)
+static int64_t alloc_block(BlockDriverState *bs, int64_t sector_num,
+    bool diff)
 {
     BDRVVPCState *s = bs->opaque;
     int64_t bat_offset;
@@ -457,7 +650,11 @@ static int64_t alloc_block(BlockDriverState* bs, int64_t sector_num)
     s->pagetable[index] = s->free_data_block_offset / 512;
 
     // Initialize the block's bitmap
-    memset(bitmap, 0xff, s->bitmap_size);
+    if (diff) {
+        memset(bitmap, 0x0, s->bitmap_size);
+    } else {
+        memset(bitmap, 0xff, s->bitmap_size);
+    }
     ret = bdrv_pwrite_sync(bs->file, s->free_data_block_offset, bitmap,
         s->bitmap_size);
     if (ret < 0) {
@@ -477,7 +674,7 @@ static int64_t alloc_block(BlockDriverState* bs, int64_t sector_num)
     if (ret < 0)
         goto fail;
 
-    return get_sector_offset(bs, sector_num, 0);
+    return get_sector_offset(bs, sector_num, false, diff);
 
 fail:
     s->free_data_block_offset -= (s->block_size + s->bitmap_size);
@@ -501,36 +698,66 @@ static int vpc_read(BlockDriverState *bs, int64_t sector_num,
                     uint8_t *buf, int nb_sectors)
 {
     BDRVVPCState *s = bs->opaque;
-    int ret;
-    int64_t offset;
-    int64_t sectors, sectors_per_block;
     VHDFooter *footer = (VHDFooter *) s->footer_buf;
+    int64_t sectors_per_block = s->block_size >> BDRV_SECTOR_BITS;
+    int64_t offset, sectors;
+    int ret;
 
-    if (be32_to_cpu(footer->type) == VHD_FIXED) {
+    switch (be32_to_cpu(footer->type)) {
+    case VHD_FIXED:
         return bdrv_read(bs->file, sector_num, buf, nb_sectors);
-    }
-    while (nb_sectors > 0) {
-        offset = get_sector_offset(bs, sector_num, 0);
-
-        sectors_per_block = s->block_size >> BDRV_SECTOR_BITS;
-        sectors = sectors_per_block - (sector_num % sectors_per_block);
-        if (sectors > nb_sectors) {
-            sectors = nb_sectors;
-        }
+    case VHD_DYNAMIC:
+        while (nb_sectors > 0) {
+            sectors = sectors_per_block - (sector_num % sectors_per_block);
+            if (sectors > nb_sectors) {
+                sectors = nb_sectors;
+            }
 
-        if (offset == -1) {
-            memset(buf, 0, sectors * BDRV_SECTOR_SIZE);
-        } else {
-            ret = bdrv_pread(bs->file, offset, buf,
-                sectors * BDRV_SECTOR_SIZE);
-            if (ret != sectors * BDRV_SECTOR_SIZE) {
+            offset = get_sector_offset(bs, sector_num, false, false);
+            if (offset == -1) {
                 return -1;
+            } else if (offset == -2) {
+                memset(buf, 0, sectors * BDRV_SECTOR_SIZE);
+            } else {
+                ret = bdrv_pread(bs->file, offset, buf,
+                    sectors * BDRV_SECTOR_SIZE);
+                if (ret != sectors * BDRV_SECTOR_SIZE) {
+                    return -1;
+                }
             }
+
+            nb_sectors -= sectors;
+            sector_num += sectors;
+            buf += sectors * BDRV_SECTOR_SIZE;
         }
+        break;
+    case VHD_DIFF:
+        while (nb_sectors > 0) {
+            offset = get_sector_offset(bs, sector_num, false, true);
+            if (offset == -1) {
+                return -1;
+            } else if (offset == -2) {
+                memset(buf, 0, BDRV_SECTOR_SIZE);
+            } else if (offset == -3) {
+                ret = bdrv_pread(bs->backing_hd, sector_num << BDRV_SECTOR_BITS
+                    , buf, BDRV_SECTOR_SIZE);
+                if (ret < 0) {
+                    return -1;
+                }
+            } else {
+                ret = bdrv_pread(bs->file, offset, buf, BDRV_SECTOR_SIZE);
+                if (ret != BDRV_SECTOR_SIZE) {
+                    return -1;
+                }
+            }
 
-        nb_sectors -= sectors;
-        sector_num += sectors;
-        buf += sectors * BDRV_SECTOR_SIZE;
+            nb_sectors--;
+            sector_num++;
+            buf += BDRV_SECTOR_SIZE;
+        }
+        break;
+    default:
+        return -1;
     }
     return 0;
 }
@@ -546,44 +773,101 @@ static coroutine_fn int vpc_co_read(BlockDriverState *bs, int64_t sector_num,
     return ret;
 }
 
-static int vpc_write(BlockDriverState *bs, int64_t sector_num,
-    const uint8_t *buf, int nb_sectors)
+static inline int64_t write_bitmap(BlockDriverState *bs, int64_t sector_num,
+    int64_t sectors)
 {
     BDRVVPCState *s = bs->opaque;
-    int64_t offset;
-    int64_t sectors, sectors_per_block;
+    uint64_t offset = sector_num << BDRV_SECTOR_BITS;
+    uint64_t bitmap_offset;
+    uint32_t pagetable_index, pageentry_index;
+    uint8_t bitmap[s->bitmap_size];
+    uint32_t bitmap_index, bitmapbit_index;
+    int i;
     int ret;
-    VHDFooter *footer =  (VHDFooter *) s->footer_buf;
 
-    if (be32_to_cpu(footer->type) == VHD_FIXED) {
-        return bdrv_write(bs->file, sector_num, buf, nb_sectors);
+    pagetable_index = offset / s->block_size;
+    pageentry_index = (offset % s->block_size) / 512;
+    bitmap_offset = 512 * (uint64_t) s->pagetable[pagetable_index];
+
+    if (bitmap_offset > s->max_table_entries * s->block_size) {
+        return -1;
+    }
+    ret = bdrv_pread(bs->file, bitmap_offset, bitmap, s->bitmap_size);
+    if (ret < 0) {
+        return -1;
     }
-    while (nb_sectors > 0) {
-        offset = get_sector_offset(bs, sector_num, 1);
 
-        sectors_per_block = s->block_size >> BDRV_SECTOR_BITS;
-        sectors = sectors_per_block - (sector_num % sectors_per_block);
-        if (sectors > nb_sectors) {
-            sectors = nb_sectors;
+    for (i = 0; i < sectors; i++) {
+        bitmap_index = pageentry_index / 8;
+        bitmapbit_index = 7 - pageentry_index % 8;
+        bitmap[bitmap_index] |= (0x1 << bitmapbit_index);
+        pageentry_index++;
+    }
+    ret = bdrv_pwrite(bs->file, bitmap_offset, bitmap, s->bitmap_size);
+    if (ret < 0) {
+        return -1;
+    }
+
+    return 0;
+}
+
+static int vpc_write(BlockDriverState *bs, int64_t sector_num,
+    const uint8_t *buf, int nb_sectors)
+{
+    BDRVVPCState *s = bs->opaque;
+    VHDFooter *footer = (VHDFooter *) s->footer_buf;
+    int64_t sectors_per_block = s->block_size >> BDRV_SECTOR_BITS;
+    int64_t offset, sectors;
+    bool diff = true;
+    int ret = 0;
+
+    switch (be32_to_cpu(footer->type)) {
+    case VHD_FIXED:
+        return bdrv_write(bs->file, sector_num, buf, nb_sectors);
+    case VHD_DYNAMIC:
+    case VHD_DIFF:
+        if (be32_to_cpu(footer->type) == VHD_DYNAMIC) {
+            diff = false;
         }
 
-        if (offset == -1) {
-            offset = alloc_block(bs, sector_num);
-            if (offset < 0)
+        while (nb_sectors > 0) {
+            sectors = sectors_per_block - (sector_num % sectors_per_block);
+            if (sectors > nb_sectors) {
+                sectors = nb_sectors;
+            }
+
+            offset = get_sector_offset(bs, sector_num, true, diff);
+            if (offset == -1) {
                 return -1;
-        }
+            } else if (offset == -2) {
+                offset = alloc_block(bs, sector_num, diff);
+                if (offset < 0) {
+                    return -1;
+                }
+            }
 
-        ret = bdrv_pwrite(bs->file, offset, buf, sectors * BDRV_SECTOR_SIZE);
-        if (ret != sectors * BDRV_SECTOR_SIZE) {
-            return -1;
-        }
+            ret = bdrv_pwrite(bs->file, offset, buf,
+                sectors * BDRV_SECTOR_SIZE);
+            if (ret != sectors * BDRV_SECTOR_SIZE) {
+                return -1;
+            }
 
-        nb_sectors -= sectors;
-        sector_num += sectors;
-        buf += sectors * BDRV_SECTOR_SIZE;
-    }
+            if (diff) {
+                ret = write_bitmap(bs, sector_num, sectors);
+                if (ret < 0) {
+                    return -1;
+                }
+            }
 
-    return 0;
+            nb_sectors -= sectors;
+            sector_num += sectors;
+            buf += sectors * BDRV_SECTOR_SIZE;
+        }
+        break;
+    default:
+        return -1;
+    }
+    return ret;
 }
 
 static coroutine_fn int vpc_co_write(BlockDriverState *bs, int64_t sector_num,
@@ -911,6 +1195,7 @@ static BlockDriver bdrv_vpc = {
     .bdrv_close             = vpc_close,
     .bdrv_reopen_prepare    = vpc_reopen_prepare,
     .bdrv_create            = vpc_create,
+    .supports_backing       = true,
 
     .bdrv_read              = vpc_co_read,
     .bdrv_write             = vpc_co_write,
-- 
1.8.3.1

             reply	other threads:[~2014-10-17 15:47 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2014-10-17 15:45 Xiaodong Gong [this message]
2014-10-24  4:08 ` [Qemu-devel] [PATCH v6] Support vhd type VHD_DIFFERENCING Xiaodong Gong

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=1413560756-5777-1-git-send-email-gongxiaodong1@huawei.com \
    --to=gordongong0350@gmail.com \
    --cc=arei.gonglei@huawei.com \
    --cc=brian.luohao@huawei.com \
    --cc=cyliu@suse.com \
    --cc=gongxiaodong1@huawei.com \
    --cc=hahn@univention.de \
    --cc=hutao@cn.fujitsu.com \
    --cc=jcody@redhat.com \
    --cc=kwolf@redhat.com \
    --cc=petrutlucian94@gmail.com \
    --cc=qemu-devel@nongnu.org \
    --cc=rudy.zhangmin@huawei.com \
    --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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.