9ae3a8
From f02d8f3eb38c3ed03742cbb981823a1917b3c5b2 Mon Sep 17 00:00:00 2001
9ae3a8
From: Jeffrey Cody <jcody@redhat.com>
9ae3a8
Date: Wed, 20 Nov 2013 19:44:00 +0100
9ae3a8
Subject: [PATCH 17/25] block: vhdx write support
9ae3a8
9ae3a8
RH-Author: Jeffrey Cody <jcody@redhat.com>
9ae3a8
Message-id: <aa16eed6f83efd7ff007cb38cca8d52f4c696054.1384975172.git.jcody@redhat.com>
9ae3a8
Patchwork-id: 55810
9ae3a8
O-Subject: [RHEL7 qemu-kvm PATCH 17/26] block: vhdx write support
9ae3a8
Bugzilla: 879234
9ae3a8
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
9ae3a8
RH-Acked-by: Paolo Bonzini <pbonzini@redhat.com>
9ae3a8
RH-Acked-by: Fam Zheng <famz@redhat.com>
9ae3a8
9ae3a8
This adds support for writing to VHDX image files, using coroutines.
9ae3a8
Writes into the BAT table goes through the VHDX log.  Currently, BAT
9ae3a8
table writes occur when expanding a dynamic VHDX file, and allocating a
9ae3a8
new BAT entry.
9ae3a8
9ae3a8
Signed-off-by: Jeff Cody <jcody@redhat.com>
9ae3a8
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
9ae3a8
(cherry picked from commit d92aa8833c051b53d3bf2614ff885df0037f10bb)
9ae3a8
---
9ae3a8
 block/vhdx.c | 212 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
9ae3a8
 block/vhdx.h |   2 +-
9ae3a8
 2 files changed, 209 insertions(+), 5 deletions(-)
9ae3a8
9ae3a8
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
9ae3a8
---
9ae3a8
 block/vhdx.c |  212 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
9ae3a8
 block/vhdx.h |    2 +-
9ae3a8
 2 files changed, 209 insertions(+), 5 deletions(-)
9ae3a8
9ae3a8
diff --git a/block/vhdx.c b/block/vhdx.c
9ae3a8
index e36c60e..baf8970 100644
9ae3a8
--- a/block/vhdx.c
9ae3a8
+++ b/block/vhdx.c
9ae3a8
@@ -914,7 +914,7 @@ static int vhdx_open(BlockDriverState *bs, QDict *options, int flags,
9ae3a8
         if (payblocks--) {
9ae3a8
             /* payload bat entries */
9ae3a8
             if ((s->bat[i] & VHDX_BAT_STATE_BIT_MASK) ==
9ae3a8
-                    PAYLOAD_BLOCK_FULL_PRESENT) {
9ae3a8
+                    PAYLOAD_BLOCK_FULLY_PRESENT) {
9ae3a8
                 ret = vhdx_region_check(s, s->bat[i] & VHDX_BAT_FILE_OFF_MASK,
9ae3a8
                                         s->block_size);
9ae3a8
                 if (ret < 0) {
9ae3a8
@@ -935,7 +935,7 @@ static int vhdx_open(BlockDriverState *bs, QDict *options, int flags,
9ae3a8
         }
9ae3a8
     }
9ae3a8
 
9ae3a8
-    /* TODO: differencing files, write */
9ae3a8
+    /* TODO: differencing files */
9ae3a8
 
9ae3a8
     /* Disable migration when VHDX images are used */
9ae3a8
     error_set(&s->migration_blocker,
9ae3a8
@@ -1052,7 +1052,7 @@ static coroutine_fn int vhdx_co_readv(BlockDriverState *bs, int64_t sector_num,
9ae3a8
                 /* return zero */
9ae3a8
                 qemu_iovec_memset(&hd_qiov, 0, 0, sinfo.bytes_avail);
9ae3a8
                 break;
9ae3a8
-            case PAYLOAD_BLOCK_FULL_PRESENT:
9ae3a8
+            case PAYLOAD_BLOCK_FULLY_PRESENT:
9ae3a8
                 qemu_co_mutex_unlock(&s->lock);
9ae3a8
                 ret = bdrv_co_readv(bs->file,
9ae3a8
                                     sinfo.file_offset >> BDRV_SECTOR_BITS,
9ae3a8
@@ -1082,7 +1082,43 @@ exit:
9ae3a8
     return ret;
9ae3a8
 }
9ae3a8
 
9ae3a8
+/*
9ae3a8
+ * Allocate a new payload block at the end of the file.
9ae3a8
+ *
9ae3a8
+ * Allocation will happen at 1MB alignment inside the file
9ae3a8
+ *
9ae3a8
+ * Returns the file offset start of the new payload block
9ae3a8
+ */
9ae3a8
+static int vhdx_allocate_block(BlockDriverState *bs, BDRVVHDXState *s,
9ae3a8
+                                    uint64_t *new_offset)
9ae3a8
+{
9ae3a8
+    *new_offset = bdrv_getlength(bs->file);
9ae3a8
+
9ae3a8
+    /* per the spec, the address for a block is in units of 1MB */
9ae3a8
+    *new_offset = ROUND_UP(*new_offset, 1024 * 1024);
9ae3a8
+
9ae3a8
+    return bdrv_truncate(bs->file, *new_offset + s->block_size);
9ae3a8
+}
9ae3a8
+
9ae3a8
+/*
9ae3a8
+ * Update the BAT table entry with the new file offset, and the new entry
9ae3a8
+ * state */
9ae3a8
+static void vhdx_update_bat_table_entry(BlockDriverState *bs, BDRVVHDXState *s,
9ae3a8
+                                       VHDXSectorInfo *sinfo,
9ae3a8
+                                       uint64_t *bat_entry_le,
9ae3a8
+                                       uint64_t *bat_offset, int state)
9ae3a8
+{
9ae3a8
+    /* The BAT entry is a uint64, with 44 bits for the file offset in units of
9ae3a8
+     * 1MB, and 3 bits for the block state. */
9ae3a8
+    s->bat[sinfo->bat_idx]  = ((sinfo->file_offset>>20) <<
9ae3a8
+                               VHDX_BAT_FILE_OFF_BITS);
9ae3a8
 
9ae3a8
+    s->bat[sinfo->bat_idx] |= state & VHDX_BAT_STATE_BIT_MASK;
9ae3a8
+
9ae3a8
+    *bat_entry_le = cpu_to_le64(s->bat[sinfo->bat_idx]);
9ae3a8
+    *bat_offset = s->bat_offset + sinfo->bat_idx * sizeof(VHDXBatEntry);
9ae3a8
+
9ae3a8
+}
9ae3a8
 
9ae3a8
 /* Per the spec, on the first write of guest-visible data to the file the
9ae3a8
  * data write guid must be updated in the header */
9ae3a8
@@ -1099,7 +1135,175 @@ int vhdx_user_visible_write(BlockDriverState *bs, BDRVVHDXState *s)
9ae3a8
 static coroutine_fn int vhdx_co_writev(BlockDriverState *bs, int64_t sector_num,
9ae3a8
                                       int nb_sectors, QEMUIOVector *qiov)
9ae3a8
 {
9ae3a8
-    return -ENOTSUP;
9ae3a8
+    int ret = -ENOTSUP;
9ae3a8
+    BDRVVHDXState *s = bs->opaque;
9ae3a8
+    VHDXSectorInfo sinfo;
9ae3a8
+    uint64_t bytes_done = 0;
9ae3a8
+    uint64_t bat_entry = 0;
9ae3a8
+    uint64_t bat_entry_offset = 0;
9ae3a8
+    QEMUIOVector hd_qiov;
9ae3a8
+    struct iovec iov1 = { 0 };
9ae3a8
+    struct iovec iov2 = { 0 };
9ae3a8
+    int sectors_to_write;
9ae3a8
+    int bat_state;
9ae3a8
+    uint64_t bat_prior_offset = 0;
9ae3a8
+    bool bat_update = false;
9ae3a8
+
9ae3a8
+    qemu_iovec_init(&hd_qiov, qiov->niov);
9ae3a8
+
9ae3a8
+    qemu_co_mutex_lock(&s->lock);
9ae3a8
+
9ae3a8
+    ret = vhdx_user_visible_write(bs, s);
9ae3a8
+    if (ret < 0) {
9ae3a8
+        goto exit;
9ae3a8
+    }
9ae3a8
+
9ae3a8
+    while (nb_sectors > 0) {
9ae3a8
+        bool use_zero_buffers = false;
9ae3a8
+        bat_update = false;
9ae3a8
+        if (s->params.data_bits & VHDX_PARAMS_HAS_PARENT) {
9ae3a8
+            /* not supported yet */
9ae3a8
+            ret = -ENOTSUP;
9ae3a8
+            goto exit;
9ae3a8
+        } else {
9ae3a8
+            vhdx_block_translate(s, sector_num, nb_sectors, &sinfo);
9ae3a8
+            sectors_to_write = sinfo.sectors_avail;
9ae3a8
+
9ae3a8
+            qemu_iovec_reset(&hd_qiov);
9ae3a8
+            /* check the payload block state */
9ae3a8
+            bat_state = s->bat[sinfo.bat_idx] & VHDX_BAT_STATE_BIT_MASK;
9ae3a8
+            switch (bat_state) {
9ae3a8
+            case PAYLOAD_BLOCK_ZERO:
9ae3a8
+                /* in this case, we need to preserve zero writes for
9ae3a8
+                 * data that is not part of this write, so we must pad
9ae3a8
+                 * the rest of the buffer to zeroes */
9ae3a8
+
9ae3a8
+                /* if we are on a posix system with ftruncate() that extends
9ae3a8
+                 * a file, then it is zero-filled for us.  On Win32, the raw
9ae3a8
+                 * layer uses SetFilePointer and SetFileEnd, which does not
9ae3a8
+                 * zero fill AFAIK */
9ae3a8
+
9ae3a8
+                /* Queue another write of zero buffers if the underlying file
9ae3a8
+                 * does not zero-fill on file extension */
9ae3a8
+
9ae3a8
+                if (bdrv_has_zero_init(bs->file) == 0) {
9ae3a8
+                    use_zero_buffers = true;
9ae3a8
+
9ae3a8
+                    /* zero fill the front, if any */
9ae3a8
+                    if (sinfo.block_offset) {
9ae3a8
+                        iov1.iov_len = sinfo.block_offset;
9ae3a8
+                        iov1.iov_base = qemu_blockalign(bs, iov1.iov_len);
9ae3a8
+                        memset(iov1.iov_base, 0, iov1.iov_len);
9ae3a8
+                        qemu_iovec_concat_iov(&hd_qiov, &iov1, 1, 0,
9ae3a8
+                                              sinfo.block_offset);
9ae3a8
+                        sectors_to_write += iov1.iov_len >> BDRV_SECTOR_BITS;
9ae3a8
+                    }
9ae3a8
+
9ae3a8
+                    /* our actual data */
9ae3a8
+                    qemu_iovec_concat(&hd_qiov, qiov,  bytes_done,
9ae3a8
+                                      sinfo.bytes_avail);
9ae3a8
+
9ae3a8
+                    /* zero fill the back, if any */
9ae3a8
+                    if ((sinfo.bytes_avail - sinfo.block_offset) <
9ae3a8
+                         s->block_size) {
9ae3a8
+                        iov2.iov_len = s->block_size -
9ae3a8
+                                      (sinfo.bytes_avail + sinfo.block_offset);
9ae3a8
+                        iov2.iov_base = qemu_blockalign(bs, iov2.iov_len);
9ae3a8
+                        memset(iov2.iov_base, 0, iov2.iov_len);
9ae3a8
+                        qemu_iovec_concat_iov(&hd_qiov, &iov2, 1, 0,
9ae3a8
+                                              sinfo.block_offset);
9ae3a8
+                        sectors_to_write += iov2.iov_len >> BDRV_SECTOR_BITS;
9ae3a8
+                    }
9ae3a8
+                }
9ae3a8
+
9ae3a8
+                /* fall through */
9ae3a8
+            case PAYLOAD_BLOCK_NOT_PRESENT: /* fall through */
9ae3a8
+            case PAYLOAD_BLOCK_UNMAPPED:    /* fall through */
9ae3a8
+            case PAYLOAD_BLOCK_UNDEFINED:   /* fall through */
9ae3a8
+                bat_prior_offset = sinfo.file_offset;
9ae3a8
+                ret = vhdx_allocate_block(bs, s, &sinfo.file_offset);
9ae3a8
+                if (ret < 0) {
9ae3a8
+                    goto exit;
9ae3a8
+                }
9ae3a8
+                /* once we support differencing files, this may also be
9ae3a8
+                 * partially present */
9ae3a8
+                /* update block state to the newly specified state */
9ae3a8
+                vhdx_update_bat_table_entry(bs, s, &sinfo, &bat_entry,
9ae3a8
+                                            &bat_entry_offset,
9ae3a8
+                                            PAYLOAD_BLOCK_FULLY_PRESENT);
9ae3a8
+                bat_update = true;
9ae3a8
+                /* since we just allocated a block, file_offset is the
9ae3a8
+                 * beginning of the payload block. It needs to be the
9ae3a8
+                 * write address, which includes the offset into the block */
9ae3a8
+                if (!use_zero_buffers) {
9ae3a8
+                    sinfo.file_offset += sinfo.block_offset;
9ae3a8
+                }
9ae3a8
+                /* fall through */
9ae3a8
+            case PAYLOAD_BLOCK_FULLY_PRESENT:
9ae3a8
+                /* if the file offset address is in the header zone,
9ae3a8
+                 * there is a problem */
9ae3a8
+                if (sinfo.file_offset < (1024 * 1024)) {
9ae3a8
+                    ret = -EFAULT;
9ae3a8
+                    goto error_bat_restore;
9ae3a8
+                }
9ae3a8
+
9ae3a8
+                if (!use_zero_buffers) {
9ae3a8
+                    qemu_iovec_concat(&hd_qiov, qiov,  bytes_done,
9ae3a8
+                                      sinfo.bytes_avail);
9ae3a8
+                }
9ae3a8
+                /* block exists, so we can just overwrite it */
9ae3a8
+                qemu_co_mutex_unlock(&s->lock);
9ae3a8
+                ret = bdrv_co_writev(bs->file,
9ae3a8
+                                    sinfo.file_offset >> BDRV_SECTOR_BITS,
9ae3a8
+                                    sectors_to_write, &hd_qiov);
9ae3a8
+                qemu_co_mutex_lock(&s->lock);
9ae3a8
+                if (ret < 0) {
9ae3a8
+                    goto error_bat_restore;
9ae3a8
+                }
9ae3a8
+                break;
9ae3a8
+            case PAYLOAD_BLOCK_PARTIALLY_PRESENT:
9ae3a8
+                /* we don't yet support difference files, fall through
9ae3a8
+                 * to error */
9ae3a8
+            default:
9ae3a8
+                ret = -EIO;
9ae3a8
+                goto exit;
9ae3a8
+                break;
9ae3a8
+            }
9ae3a8
+
9ae3a8
+            if (bat_update) {
9ae3a8
+                /* this will update the BAT entry into the log journal, and
9ae3a8
+                 * then flush the log journal out to disk */
9ae3a8
+                ret =  vhdx_log_write_and_flush(bs, s, &bat_entry,
9ae3a8
+                                                sizeof(VHDXBatEntry),
9ae3a8
+                                                bat_entry_offset);
9ae3a8
+                if (ret < 0) {
9ae3a8
+                    goto exit;
9ae3a8
+                }
9ae3a8
+            }
9ae3a8
+
9ae3a8
+            nb_sectors -= sinfo.sectors_avail;
9ae3a8
+            sector_num += sinfo.sectors_avail;
9ae3a8
+            bytes_done += sinfo.bytes_avail;
9ae3a8
+
9ae3a8
+        }
9ae3a8
+    }
9ae3a8
+
9ae3a8
+    goto exit;
9ae3a8
+
9ae3a8
+error_bat_restore:
9ae3a8
+    if (bat_update) {
9ae3a8
+        /* keep metadata in sync, and restore the bat entry state
9ae3a8
+         * if error. */
9ae3a8
+        sinfo.file_offset = bat_prior_offset;
9ae3a8
+        vhdx_update_bat_table_entry(bs, s, &sinfo, &bat_entry,
9ae3a8
+                                    &bat_entry_offset, bat_state);
9ae3a8
+    }
9ae3a8
+exit:
9ae3a8
+    qemu_vfree(iov1.iov_base);
9ae3a8
+    qemu_vfree(iov2.iov_base);
9ae3a8
+    qemu_co_mutex_unlock(&s->lock);
9ae3a8
+    qemu_iovec_destroy(&hd_qiov);
9ae3a8
+    return ret;
9ae3a8
 }
9ae3a8
 
9ae3a8
 
9ae3a8
diff --git a/block/vhdx.h b/block/vhdx.h
9ae3a8
index 4bb83de..a85c5c8 100644
9ae3a8
--- a/block/vhdx.h
9ae3a8
+++ b/block/vhdx.h
9ae3a8
@@ -217,7 +217,7 @@ typedef struct QEMU_PACKED VHDXLogDataSector {
9ae3a8
 #define PAYLOAD_BLOCK_UNDEFINED         1
9ae3a8
 #define PAYLOAD_BLOCK_ZERO              2
9ae3a8
 #define PAYLOAD_BLOCK_UNMAPPED          5
9ae3a8
-#define PAYLOAD_BLOCK_FULL_PRESENT      6
9ae3a8
+#define PAYLOAD_BLOCK_FULLY_PRESENT     6
9ae3a8
 #define PAYLOAD_BLOCK_PARTIALLY_PRESENT 7
9ae3a8
 
9ae3a8
 #define SB_BLOCK_NOT_PRESENT    0
9ae3a8
-- 
9ae3a8
1.7.1
9ae3a8