|
|
0a122b |
From db9d361c509aea4ce8aca7b7f16a1808880bad29 Mon Sep 17 00:00:00 2001
|
|
|
0a122b |
From: Jeffrey Cody <jcody@redhat.com>
|
|
|
0a122b |
Date: Wed, 20 Nov 2013 19:43:59 +0100
|
|
|
0a122b |
Subject: [PATCH 16/25] block: vhdx - add log write support
|
|
|
0a122b |
|
|
|
0a122b |
RH-Author: Jeffrey Cody <jcody@redhat.com>
|
|
|
0a122b |
Message-id: <376bced13e2a7e1069a50bcbd09f3d822306865a.1384975172.git.jcody@redhat.com>
|
|
|
0a122b |
Patchwork-id: 55809
|
|
|
0a122b |
O-Subject: [RHEL7 qemu-kvm PATCH 16/26] block: vhdx - add log write support
|
|
|
0a122b |
Bugzilla: 879234
|
|
|
0a122b |
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
|
|
|
0a122b |
RH-Acked-by: Paolo Bonzini <pbonzini@redhat.com>
|
|
|
0a122b |
RH-Acked-by: Fam Zheng <famz@redhat.com>
|
|
|
0a122b |
|
|
|
0a122b |
This adds support for writing to the VHDX log.
|
|
|
0a122b |
|
|
|
0a122b |
For spec details, see VHDX Specification Format v1.00:
|
|
|
0a122b |
https://www.microsoft.com/en-us/download/details.aspx?id=34750
|
|
|
0a122b |
|
|
|
0a122b |
There are a few limitations to this log support:
|
|
|
0a122b |
1.) There is no caching yet
|
|
|
0a122b |
2.) The log is flushed after each entry
|
|
|
0a122b |
|
|
|
0a122b |
The primary write interface, vhdx_log_write_and_flush(), performs a log
|
|
|
0a122b |
write followed by an immediate flush of the log.
|
|
|
0a122b |
|
|
|
0a122b |
As each log entry sector is a minimum of 4KB, partial sector writes are
|
|
|
0a122b |
filled in with data from the disk write destination.
|
|
|
0a122b |
|
|
|
0a122b |
If the current file log GUID is 0, a new GUID is generated and updated
|
|
|
0a122b |
in the header.
|
|
|
0a122b |
|
|
|
0a122b |
Signed-off-by: Jeff Cody <jcody@redhat.com>
|
|
|
0a122b |
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
|
|
|
0a122b |
(cherry picked from commit 8adc52336d9c44ab4c7b9358a7be22ac0ef962bf)
|
|
|
0a122b |
---
|
|
|
0a122b |
block/vhdx-log.c | 282 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
0a122b |
block/vhdx.h | 3 +
|
|
|
0a122b |
2 files changed, 285 insertions(+)
|
|
|
0a122b |
|
|
|
0a122b |
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
|
|
|
0a122b |
---
|
|
|
0a122b |
block/vhdx-log.c | 282 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
0a122b |
block/vhdx.h | 3 +
|
|
|
0a122b |
2 files changed, 285 insertions(+), 0 deletions(-)
|
|
|
0a122b |
|
|
|
0a122b |
diff --git a/block/vhdx-log.c b/block/vhdx-log.c
|
|
|
0a122b |
index 0284729..ee5583c 100644
|
|
|
0a122b |
--- a/block/vhdx-log.c
|
|
|
0a122b |
+++ b/block/vhdx-log.c
|
|
|
0a122b |
@@ -156,6 +156,55 @@ exit:
|
|
|
0a122b |
return ret;
|
|
|
0a122b |
}
|
|
|
0a122b |
|
|
|
0a122b |
+/* Writes num_sectors to the log (all log sectors are 4096 bytes),
|
|
|
0a122b |
+ * from buffer 'buffer'. Upon return, *sectors_written will contain
|
|
|
0a122b |
+ * the number of sectors successfully written.
|
|
|
0a122b |
+ *
|
|
|
0a122b |
+ * It is assumed that 'buffer' is at least 4096*num_sectors large.
|
|
|
0a122b |
+ *
|
|
|
0a122b |
+ * 0 is returned on success, -errno otherwise */
|
|
|
0a122b |
+static int vhdx_log_write_sectors(BlockDriverState *bs, VHDXLogEntries *log,
|
|
|
0a122b |
+ uint32_t *sectors_written, void *buffer,
|
|
|
0a122b |
+ uint32_t num_sectors)
|
|
|
0a122b |
+{
|
|
|
0a122b |
+ int ret = 0;
|
|
|
0a122b |
+ uint64_t offset;
|
|
|
0a122b |
+ uint32_t write;
|
|
|
0a122b |
+ void *buffer_tmp;
|
|
|
0a122b |
+ BDRVVHDXState *s = bs->opaque;
|
|
|
0a122b |
+
|
|
|
0a122b |
+ ret = vhdx_user_visible_write(bs, s);
|
|
|
0a122b |
+ if (ret < 0) {
|
|
|
0a122b |
+ goto exit;
|
|
|
0a122b |
+ }
|
|
|
0a122b |
+
|
|
|
0a122b |
+ write = log->write;
|
|
|
0a122b |
+
|
|
|
0a122b |
+ buffer_tmp = buffer;
|
|
|
0a122b |
+ while (num_sectors) {
|
|
|
0a122b |
+
|
|
|
0a122b |
+ offset = log->offset + write;
|
|
|
0a122b |
+ write = vhdx_log_inc_idx(write, log->length);
|
|
|
0a122b |
+ if (write == log->read) {
|
|
|
0a122b |
+ /* full */
|
|
|
0a122b |
+ break;
|
|
|
0a122b |
+ }
|
|
|
0a122b |
+ ret = bdrv_pwrite(bs->file, offset, buffer_tmp, VHDX_LOG_SECTOR_SIZE);
|
|
|
0a122b |
+ if (ret < 0) {
|
|
|
0a122b |
+ goto exit;
|
|
|
0a122b |
+ }
|
|
|
0a122b |
+ buffer_tmp += VHDX_LOG_SECTOR_SIZE;
|
|
|
0a122b |
+
|
|
|
0a122b |
+ log->write = write;
|
|
|
0a122b |
+ *sectors_written = *sectors_written + 1;
|
|
|
0a122b |
+ num_sectors--;
|
|
|
0a122b |
+ }
|
|
|
0a122b |
+
|
|
|
0a122b |
+exit:
|
|
|
0a122b |
+ return ret;
|
|
|
0a122b |
+}
|
|
|
0a122b |
+
|
|
|
0a122b |
+
|
|
|
0a122b |
/* Validates a log entry header */
|
|
|
0a122b |
static bool vhdx_log_hdr_is_valid(VHDXLogEntries *log, VHDXLogEntryHeader *hdr,
|
|
|
0a122b |
BDRVVHDXState *s)
|
|
|
0a122b |
@@ -726,3 +775,236 @@ exit:
|
|
|
0a122b |
}
|
|
|
0a122b |
|
|
|
0a122b |
|
|
|
0a122b |
+
|
|
|
0a122b |
+static void vhdx_log_raw_to_le_sector(VHDXLogDescriptor *desc,
|
|
|
0a122b |
+ VHDXLogDataSector *sector, void *data,
|
|
|
0a122b |
+ uint64_t seq)
|
|
|
0a122b |
+{
|
|
|
0a122b |
+ /* 8 + 4084 + 4 = 4096, 1 log sector */
|
|
|
0a122b |
+ memcpy(&desc->leading_bytes, data, 8);
|
|
|
0a122b |
+ data += 8;
|
|
|
0a122b |
+ cpu_to_le64s(&desc->leading_bytes);
|
|
|
0a122b |
+ memcpy(sector->data, data, 4084);
|
|
|
0a122b |
+ data += 4084;
|
|
|
0a122b |
+ memcpy(&desc->trailing_bytes, data, 4);
|
|
|
0a122b |
+ cpu_to_le32s(&desc->trailing_bytes);
|
|
|
0a122b |
+ data += 4;
|
|
|
0a122b |
+
|
|
|
0a122b |
+ sector->sequence_high = (uint32_t) (seq >> 32);
|
|
|
0a122b |
+ sector->sequence_low = (uint32_t) (seq & 0xffffffff);
|
|
|
0a122b |
+ sector->data_signature = VHDX_LOG_DATA_SIGNATURE;
|
|
|
0a122b |
+
|
|
|
0a122b |
+ vhdx_log_desc_le_export(desc);
|
|
|
0a122b |
+ vhdx_log_data_le_export(sector);
|
|
|
0a122b |
+}
|
|
|
0a122b |
+
|
|
|
0a122b |
+
|
|
|
0a122b |
+static int vhdx_log_write(BlockDriverState *bs, BDRVVHDXState *s,
|
|
|
0a122b |
+ void *data, uint32_t length, uint64_t offset)
|
|
|
0a122b |
+{
|
|
|
0a122b |
+ int ret = 0;
|
|
|
0a122b |
+ void *buffer = NULL;
|
|
|
0a122b |
+ void *merged_sector = NULL;
|
|
|
0a122b |
+ void *data_tmp, *sector_write;
|
|
|
0a122b |
+ unsigned int i;
|
|
|
0a122b |
+ int sector_offset;
|
|
|
0a122b |
+ uint32_t desc_sectors, sectors, total_length;
|
|
|
0a122b |
+ uint32_t sectors_written = 0;
|
|
|
0a122b |
+ uint32_t aligned_length;
|
|
|
0a122b |
+ uint32_t leading_length = 0;
|
|
|
0a122b |
+ uint32_t trailing_length = 0;
|
|
|
0a122b |
+ uint32_t partial_sectors = 0;
|
|
|
0a122b |
+ uint32_t bytes_written = 0;
|
|
|
0a122b |
+ uint64_t file_offset;
|
|
|
0a122b |
+ VHDXHeader *header;
|
|
|
0a122b |
+ VHDXLogEntryHeader new_hdr;
|
|
|
0a122b |
+ VHDXLogDescriptor *new_desc = NULL;
|
|
|
0a122b |
+ VHDXLogDataSector *data_sector = NULL;
|
|
|
0a122b |
+ MSGUID new_guid = { 0 };
|
|
|
0a122b |
+
|
|
|
0a122b |
+ header = s->headers[s->curr_header];
|
|
|
0a122b |
+
|
|
|
0a122b |
+ /* need to have offset read data, and be on 4096 byte boundary */
|
|
|
0a122b |
+
|
|
|
0a122b |
+ if (length > header->log_length) {
|
|
|
0a122b |
+ /* no log present. we could create a log here instead of failing */
|
|
|
0a122b |
+ ret = -EINVAL;
|
|
|
0a122b |
+ goto exit;
|
|
|
0a122b |
+ }
|
|
|
0a122b |
+
|
|
|
0a122b |
+ if (guid_eq(header->log_guid, zero_guid)) {
|
|
|
0a122b |
+ vhdx_guid_generate(&new_guid);
|
|
|
0a122b |
+ vhdx_update_headers(bs, s, false, &new_guid);
|
|
|
0a122b |
+ } else {
|
|
|
0a122b |
+ /* currently, we require that the log be flushed after
|
|
|
0a122b |
+ * every write. */
|
|
|
0a122b |
+ ret = -ENOTSUP;
|
|
|
0a122b |
+ goto exit;
|
|
|
0a122b |
+ }
|
|
|
0a122b |
+
|
|
|
0a122b |
+ /* 0 is an invalid sequence number, but may also represent the first
|
|
|
0a122b |
+ * log write (or a wrapped seq) */
|
|
|
0a122b |
+ if (s->log.sequence == 0) {
|
|
|
0a122b |
+ s->log.sequence = 1;
|
|
|
0a122b |
+ }
|
|
|
0a122b |
+
|
|
|
0a122b |
+ sector_offset = offset % VHDX_LOG_SECTOR_SIZE;
|
|
|
0a122b |
+ file_offset = (offset / VHDX_LOG_SECTOR_SIZE) * VHDX_LOG_SECTOR_SIZE;
|
|
|
0a122b |
+
|
|
|
0a122b |
+ aligned_length = length;
|
|
|
0a122b |
+
|
|
|
0a122b |
+ /* add in the unaligned head and tail bytes */
|
|
|
0a122b |
+ if (sector_offset) {
|
|
|
0a122b |
+ leading_length = (VHDX_LOG_SECTOR_SIZE - sector_offset);
|
|
|
0a122b |
+ leading_length = leading_length > length ? length : leading_length;
|
|
|
0a122b |
+ aligned_length -= leading_length;
|
|
|
0a122b |
+ partial_sectors++;
|
|
|
0a122b |
+ }
|
|
|
0a122b |
+
|
|
|
0a122b |
+ sectors = aligned_length / VHDX_LOG_SECTOR_SIZE;
|
|
|
0a122b |
+ trailing_length = aligned_length - (sectors * VHDX_LOG_SECTOR_SIZE);
|
|
|
0a122b |
+ if (trailing_length) {
|
|
|
0a122b |
+ partial_sectors++;
|
|
|
0a122b |
+ }
|
|
|
0a122b |
+
|
|
|
0a122b |
+ sectors += partial_sectors;
|
|
|
0a122b |
+
|
|
|
0a122b |
+ /* sectors is now how many sectors the data itself takes, not
|
|
|
0a122b |
+ * including the header and descriptor metadata */
|
|
|
0a122b |
+
|
|
|
0a122b |
+ new_hdr = (VHDXLogEntryHeader) {
|
|
|
0a122b |
+ .signature = VHDX_LOG_SIGNATURE,
|
|
|
0a122b |
+ .tail = s->log.tail,
|
|
|
0a122b |
+ .sequence_number = s->log.sequence,
|
|
|
0a122b |
+ .descriptor_count = sectors,
|
|
|
0a122b |
+ .reserved = 0,
|
|
|
0a122b |
+ .flushed_file_offset = bdrv_getlength(bs->file),
|
|
|
0a122b |
+ .last_file_offset = bdrv_getlength(bs->file),
|
|
|
0a122b |
+ };
|
|
|
0a122b |
+
|
|
|
0a122b |
+ new_hdr.log_guid = header->log_guid;
|
|
|
0a122b |
+
|
|
|
0a122b |
+ desc_sectors = vhdx_compute_desc_sectors(new_hdr.descriptor_count);
|
|
|
0a122b |
+
|
|
|
0a122b |
+ total_length = (desc_sectors + sectors) * VHDX_LOG_SECTOR_SIZE;
|
|
|
0a122b |
+ new_hdr.entry_length = total_length;
|
|
|
0a122b |
+
|
|
|
0a122b |
+ vhdx_log_entry_hdr_le_export(&new_hdr);
|
|
|
0a122b |
+
|
|
|
0a122b |
+ buffer = qemu_blockalign(bs, total_length);
|
|
|
0a122b |
+ memcpy(buffer, &new_hdr, sizeof(new_hdr));
|
|
|
0a122b |
+
|
|
|
0a122b |
+ new_desc = (VHDXLogDescriptor *) (buffer + sizeof(new_hdr));
|
|
|
0a122b |
+ data_sector = buffer + (desc_sectors * VHDX_LOG_SECTOR_SIZE);
|
|
|
0a122b |
+ data_tmp = data;
|
|
|
0a122b |
+
|
|
|
0a122b |
+ /* All log sectors are 4KB, so for any partial sectors we must
|
|
|
0a122b |
+ * merge the data with preexisting data from the final file
|
|
|
0a122b |
+ * destination */
|
|
|
0a122b |
+ merged_sector = qemu_blockalign(bs, VHDX_LOG_SECTOR_SIZE);
|
|
|
0a122b |
+
|
|
|
0a122b |
+ for (i = 0; i < sectors; i++) {
|
|
|
0a122b |
+ new_desc->signature = VHDX_LOG_DESC_SIGNATURE;
|
|
|
0a122b |
+ new_desc->sequence_number = s->log.sequence;
|
|
|
0a122b |
+ new_desc->file_offset = file_offset;
|
|
|
0a122b |
+
|
|
|
0a122b |
+ if (i == 0 && leading_length) {
|
|
|
0a122b |
+ /* partial sector at the front of the buffer */
|
|
|
0a122b |
+ ret = bdrv_pread(bs->file, file_offset, merged_sector,
|
|
|
0a122b |
+ VHDX_LOG_SECTOR_SIZE);
|
|
|
0a122b |
+ if (ret < 0) {
|
|
|
0a122b |
+ goto exit;
|
|
|
0a122b |
+ }
|
|
|
0a122b |
+ memcpy(merged_sector + sector_offset, data_tmp, leading_length);
|
|
|
0a122b |
+ bytes_written = leading_length;
|
|
|
0a122b |
+ sector_write = merged_sector;
|
|
|
0a122b |
+ } else if (i == sectors - 1 && trailing_length) {
|
|
|
0a122b |
+ /* partial sector at the end of the buffer */
|
|
|
0a122b |
+ ret = bdrv_pread(bs->file,
|
|
|
0a122b |
+ file_offset,
|
|
|
0a122b |
+ merged_sector + trailing_length,
|
|
|
0a122b |
+ VHDX_LOG_SECTOR_SIZE - trailing_length);
|
|
|
0a122b |
+ if (ret < 0) {
|
|
|
0a122b |
+ goto exit;
|
|
|
0a122b |
+ }
|
|
|
0a122b |
+ memcpy(merged_sector, data_tmp, trailing_length);
|
|
|
0a122b |
+ bytes_written = trailing_length;
|
|
|
0a122b |
+ sector_write = merged_sector;
|
|
|
0a122b |
+ } else {
|
|
|
0a122b |
+ bytes_written = VHDX_LOG_SECTOR_SIZE;
|
|
|
0a122b |
+ sector_write = data_tmp;
|
|
|
0a122b |
+ }
|
|
|
0a122b |
+
|
|
|
0a122b |
+ /* populate the raw sector data into the proper structures,
|
|
|
0a122b |
+ * as well as update the descriptor, and convert to proper
|
|
|
0a122b |
+ * endianness */
|
|
|
0a122b |
+ vhdx_log_raw_to_le_sector(new_desc, data_sector, sector_write,
|
|
|
0a122b |
+ s->log.sequence);
|
|
|
0a122b |
+
|
|
|
0a122b |
+ data_tmp += bytes_written;
|
|
|
0a122b |
+ data_sector++;
|
|
|
0a122b |
+ new_desc++;
|
|
|
0a122b |
+ file_offset += VHDX_LOG_SECTOR_SIZE;
|
|
|
0a122b |
+ }
|
|
|
0a122b |
+
|
|
|
0a122b |
+ /* checksum covers entire entry, from the log header through the
|
|
|
0a122b |
+ * last data sector */
|
|
|
0a122b |
+ vhdx_update_checksum(buffer, total_length,
|
|
|
0a122b |
+ offsetof(VHDXLogEntryHeader, checksum));
|
|
|
0a122b |
+ cpu_to_le32s((uint32_t *)(buffer + 4));
|
|
|
0a122b |
+
|
|
|
0a122b |
+ /* now write to the log */
|
|
|
0a122b |
+ vhdx_log_write_sectors(bs, &s->log, §ors_written, buffer,
|
|
|
0a122b |
+ desc_sectors + sectors);
|
|
|
0a122b |
+ if (ret < 0) {
|
|
|
0a122b |
+ goto exit;
|
|
|
0a122b |
+ }
|
|
|
0a122b |
+
|
|
|
0a122b |
+ if (sectors_written != desc_sectors + sectors) {
|
|
|
0a122b |
+ /* instead of failing, we could flush the log here */
|
|
|
0a122b |
+ ret = -EINVAL;
|
|
|
0a122b |
+ goto exit;
|
|
|
0a122b |
+ }
|
|
|
0a122b |
+
|
|
|
0a122b |
+ s->log.sequence++;
|
|
|
0a122b |
+ /* write new tail */
|
|
|
0a122b |
+ s->log.tail = s->log.write;
|
|
|
0a122b |
+
|
|
|
0a122b |
+exit:
|
|
|
0a122b |
+ qemu_vfree(buffer);
|
|
|
0a122b |
+ qemu_vfree(merged_sector);
|
|
|
0a122b |
+ return ret;
|
|
|
0a122b |
+}
|
|
|
0a122b |
+
|
|
|
0a122b |
+/* Perform a log write, and then immediately flush the entire log */
|
|
|
0a122b |
+int vhdx_log_write_and_flush(BlockDriverState *bs, BDRVVHDXState *s,
|
|
|
0a122b |
+ void *data, uint32_t length, uint64_t offset)
|
|
|
0a122b |
+{
|
|
|
0a122b |
+ int ret = 0;
|
|
|
0a122b |
+ VHDXLogSequence logs = { .valid = true,
|
|
|
0a122b |
+ .count = 1,
|
|
|
0a122b |
+ .hdr = { 0 } };
|
|
|
0a122b |
+
|
|
|
0a122b |
+
|
|
|
0a122b |
+ /* Make sure data written (new and/or changed blocks) is stable
|
|
|
0a122b |
+ * on disk, before creating log entry */
|
|
|
0a122b |
+ bdrv_flush(bs);
|
|
|
0a122b |
+ ret = vhdx_log_write(bs, s, data, length, offset);
|
|
|
0a122b |
+ if (ret < 0) {
|
|
|
0a122b |
+ goto exit;
|
|
|
0a122b |
+ }
|
|
|
0a122b |
+ logs.log = s->log;
|
|
|
0a122b |
+
|
|
|
0a122b |
+ /* Make sure log is stable on disk */
|
|
|
0a122b |
+ bdrv_flush(bs);
|
|
|
0a122b |
+ ret = vhdx_log_flush(bs, s, &logs);
|
|
|
0a122b |
+ if (ret < 0) {
|
|
|
0a122b |
+ goto exit;
|
|
|
0a122b |
+ }
|
|
|
0a122b |
+
|
|
|
0a122b |
+ s->log = logs.log;
|
|
|
0a122b |
+
|
|
|
0a122b |
+exit:
|
|
|
0a122b |
+ return ret;
|
|
|
0a122b |
+}
|
|
|
0a122b |
+
|
|
|
0a122b |
diff --git a/block/vhdx.h b/block/vhdx.h
|
|
|
0a122b |
index 91ef8fe..4bb83de 100644
|
|
|
0a122b |
--- a/block/vhdx.h
|
|
|
0a122b |
+++ b/block/vhdx.h
|
|
|
0a122b |
@@ -398,6 +398,9 @@ bool vhdx_checksum_is_valid(uint8_t *buf, size_t size, int crc_offset);
|
|
|
0a122b |
|
|
|
0a122b |
int vhdx_parse_log(BlockDriverState *bs, BDRVVHDXState *s, bool *flushed);
|
|
|
0a122b |
|
|
|
0a122b |
+int vhdx_log_write_and_flush(BlockDriverState *bs, BDRVVHDXState *s,
|
|
|
0a122b |
+ void *data, uint32_t length, uint64_t offset);
|
|
|
0a122b |
+
|
|
|
0a122b |
static inline void leguid_to_cpus(MSGUID *guid)
|
|
|
0a122b |
{
|
|
|
0a122b |
le32_to_cpus(&guid->data1);
|
|
|
0a122b |
--
|
|
|
0a122b |
1.7.1
|
|
|
0a122b |
|