qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [PATCH 7/9] block: vhdx - log parsing, replay, and flus


From: Jeff Cody
Subject: Re: [Qemu-devel] [PATCH 7/9] block: vhdx - log parsing, replay, and flush support
Date: Tue, 30 Jul 2013 09:58:04 -0400
User-agent: Mutt/1.5.21 (2010-09-15)

On Tue, Jul 30, 2013 at 11:48:15AM +0800, Fam Zheng wrote:
> On Wed, 07/24 13:54, Jeff Cody wrote:
> > This adds support for VHDX v0 logs, as specified in Microsoft's
> > VHDX Specification Format v1.00:
> > https://www.microsoft.com/en-us/download/details.aspx?id=34750
> > 
> > The following support is added:
> > 
> > * Log parsing, and validation - validate that an existing log
> >   is correct.
> > 
> > * Log search - search through an existing log, to find any valid
> >   sequence of entries.
> > 
> > * Log replay and flush - replay an existing log, and flush/clear
> >   the log when complete.
> > 
> > The VHDX log is a circular buffer, with elements (sectors) of 4KB.
> > 
> > A log entry is a variably-length number of sectors, that is
> > comprised of a header and 'descriptors', that describe each sector.
> > 
> > A log may contain multiple entries, know as a log sequence.  In a log
> > sequence, each log entry immediately follows the previous entry, with an
> > incrementing sequence number.  There can only ever be one active and
> > valid sequence in the log.
> > 
> > Each log entry must match the file log GUID in order to be valid (along
> > with other criteria).  Once we have flushed all valid log entries, we
> > marked the file log GUID to be zero, which indicates a buffer with no
> > valid entries.
> > 
> > Signed-off-by: Jeff Cody <address@hidden>
> > ---
> >  block/Makefile.objs |   2 +-
> >  block/vhdx-log.c    | 734 
> > ++++++++++++++++++++++++++++++++++++++++++++++++++++
> >  block/vhdx.c        |  44 +---
> >  block/vhdx.h        |   7 +-
> >  4 files changed, 743 insertions(+), 44 deletions(-)
> >  create mode 100644 block/vhdx-log.c
> > 
> > diff --git a/block/Makefile.objs b/block/Makefile.objs
> > index e6f5d33..2fbd79a 100644
> > --- a/block/Makefile.objs
> > +++ b/block/Makefile.objs
> > @@ -2,7 +2,7 @@ block-obj-y += raw.o cow.o qcow.o vdi.o vmdk.o cloop.o 
> > dmg.o bochs.o vpc.o vvfat
> >  block-obj-y += qcow2.o qcow2-refcount.o qcow2-cluster.o qcow2-snapshot.o 
> > qcow2-cache.o
> >  block-obj-y += qed.o qed-gencb.o qed-l2-cache.o qed-table.o qed-cluster.o
> >  block-obj-y += qed-check.o
> > -block-obj-$(CONFIG_VHDX) += vhdx.o vhdx-endian.o
> > +block-obj-$(CONFIG_VHDX) += vhdx.o vhdx-endian.o vhdx-log.o
> >  block-obj-y += parallels.o blkdebug.o blkverify.o
> >  block-obj-y += snapshot.o qapi.o
> >  block-obj-$(CONFIG_WIN32) += raw-win32.o win32-aio.o
> > diff --git a/block/vhdx-log.c b/block/vhdx-log.c
> > new file mode 100644
> > index 0000000..89b9000
> > --- /dev/null
> > +++ b/block/vhdx-log.c
> > @@ -0,0 +1,734 @@
> > +/*
> > + * Block driver for Hyper-V VHDX Images
> > + *
> > + * Copyright (c) 2013 Red Hat, Inc.,
> > + *
> > + * Authors:
> > + *  Jeff Cody <address@hidden>
> > + *
> > + *  This is based on the "VHDX Format Specification v1.00", published 
> > 8/25/2012
> > + *  by Microsoft:
> > + *      https://www.microsoft.com/en-us/download/details.aspx?id=34750
> > + *
> > + * This file covers the functionality of the metadata log writing, 
> > parsing, and
> > + * replay.
> > + *
> > + * This work is licensed under the terms of the GNU LGPL, version 2 or 
> > later.
> > + * See the COPYING.LIB file in the top-level directory.
> > + *
> > + */
> > +#include "qemu-common.h"
> > +#include "block/block_int.h"
> > +#include "qemu/module.h"
> > +#include "block/vhdx.h"
> > +
> > +
> > +typedef struct VHDXLogSequence {
> > +    bool valid;
> > +    uint32_t count;
> > +    VHDXLogEntries log;
> > +    VHDXLogEntryHeader hdr;
> > +} VHDXLogSequence;
> > +
> > +typedef struct VHDXLogDescEntries {
> > +    VHDXLogEntryHeader hdr;
> > +    VHDXLogDescriptor desc[];
> > +} VHDXLogDescEntries;
> > +
> > +
> > +/* Returns true if the GUID is zero */
> > +static bool vhdx_log_guid_is_zero(MSGUID *guid)
> > +{
> > +    int i;
> > +    int ret = 0;
> > +
> > +    /* If either the log guid, or log length is zero,
> > +     * then a replay log is not present */
> > +    for (i = 0; i < sizeof(MSGUID); i++) {
> > +        ret |= ((uint8_t *) guid)[i];
> > +    }
> > +
> > +    return ret == 0;
> > +}
> > +
> > +/* The log located on the disk is circular buffer containing
> > + * sectors of 4096 bytes each.
> > + *
> > + * It is assumed for the read/write functions below that the
> > + * circular buffer scheme uses a 'one sector open' to indicate
> > + * the buffer is full.  Given the validation methods used for each
> > + * sector, this method should be compatible with other methods that
> > + * do not waste a sector.
> > + */
> > +
> > +
> > +/* Allow peeking at the hdr entry at the beginning of the current
> > + * read index, without advancing the read index */
> > +static int vhdx_log_peek_hdr(BlockDriverState *bs, VHDXLogEntries *log,
> > +                             VHDXLogEntryHeader *hdr)
> > +{
> > +    int ret = 0;
> > +    uint64_t offset;
> > +    uint32_t read;
> > +
> > +    assert(hdr != NULL);
> > +
> > +    /* peek is only support on sector boundaries */
> 
> s/support/supported/
>

Thanks

> > +    if (log->read % VHDX_LOG_SECTOR_SIZE) {
> > +        ret = -EFAULT;
> > +        goto exit;
> > +    }
> > +
> > +    read = log->read;
> > +    /* we are guaranteed that a) log sectors are 4096 bytes,
> > +     * and b) the log length is a multiple of 1MB. So, there
> > +     * is always a round number of sectors in the buffer */
> > +    if ((read + sizeof(VHDXLogEntryHeader)) > log->length) {
> > +        read = 0;
> > +    }
> > +
> > +    if (read == log->write) {
> > +        ret = -EINVAL;
> > +        goto exit;
> > +    }
> > +
> > +    offset = log->offset + read;
> > +
> > +    ret = bdrv_pread(bs->file, offset, hdr, sizeof(VHDXLogEntryHeader));
> > +    if (ret < 0) {
> > +        goto exit;
> > +    }
> > +
> > +exit:
> > +    return ret;
> > +}
> > +
> > +/* Index increment for log, based on sector boundaries */
> > +static int vhdx_log_inc_idx(uint32_t idx, uint64_t length)
> > +{
> > +    idx += VHDX_LOG_SECTOR_SIZE;
> > +    /* we are guaranteed that a) log sectors are 4096 bytes,
> > +     * and b) the log length is a multiple of 1MB. So, there
> > +     * is always a round number of sectors in the buffer */
> > +    return idx >= length ? 0 : idx;
> > +}
> > +
> > +
> > +/* Reset the log to empty */
> > +static void vhdx_log_reset(BlockDriverState *bs, BDRVVHDXState *s)
> > +{
> > +    MSGUID guid = { 0 };
> > +    s->log.read = s->log.write = 0;
> > +    /* a log guid of 0 indicates an empty log to any parser of v0
> > +     * VHDX logs */
> > +    vhdx_update_headers(bs, s, false, &guid);
> > +}
> > +
> > +/* Reads num_sectors from the log (all log sectors are 4096 bytes),
> > + * into buffer 'buffer'.  Upon return, *sectors_read will contain
> > + * the number of sectors successfully read.
> > + *
> > + * It is assumed that 'buffer' is already allocated, and of sufficient
> > + * size (i.e. >= 4096*num_sectors).
> > + *
> > + * If 'peek' is true, then the tail (read) pointer for the circular buffer 
> > is
> > + * not modified.
> > + *
> > + * 0 is returned on success, -errno otherwise.  */
> > +static int vhdx_log_read_sectors(BlockDriverState *bs, VHDXLogEntries *log,
> > +                                 uint32_t *sectors_read, void *buffer,
> > +                                 uint32_t num_sectors, bool peek)
> > +{
> > +    int ret = 0;
> > +    uint64_t offset;
> > +    uint32_t read;
> > +
> > +    read = log->read;
> > +
> > +    *sectors_read = 0;
> > +    while (num_sectors) {
> > +        if (read == log->write) {
> > +            /* empty */
> > +            break;
> > +        }
> > +        offset = log->offset + read;
> > +
> > +        ret = bdrv_pread(bs->file, offset, buffer, VHDX_LOG_SECTOR_SIZE);
> > +        if (ret < 0) {
> > +            goto exit;
> > +        }
> > +        read = vhdx_log_inc_idx(read, log->length);
> > +
> > +        *sectors_read = *sectors_read + 1;
> > +        num_sectors--;
> > +    }
> > +
> > +exit:
> > +    if (!peek) {
> > +        log->read = read;
> > +    }
> > +    return ret;
> > +}
> > +
> > +/* Validates a log entry header */
> > +static bool vhdx_log_hdr_is_valid(VHDXLogEntries *log, VHDXLogEntryHeader 
> > *hdr,
> > +                                  BDRVVHDXState *s)
> > +{
> > +    int valid = false;
> > +
> > +    if (memcmp(&hdr->signature, "loge", 4)) {
> > +        goto exit;
> > +    }
> > +
> > +    /* if the individual entry length is larger than the whole log
> > +     * buffer, that is obviously invalid */
> > +    if (log->length < hdr->entry_length) {
> > +        goto exit;
> > +    }
> > +
> > +    /* length of entire entry must be in units of 4KB (log sector size) */
> > +    if (hdr->entry_length % (VHDX_LOG_SECTOR_SIZE)) {
> > +        goto exit;
> > +    }
> > +
> > +    /* per spec, sequence # must be > 0 */
> > +    if (hdr->sequence_number == 0) {
> > +        goto exit;
> > +    }
> > +
> > +    /* log entries are only valid if they match the file-wide log guid
> > +     * found in the active header */
> > +    if (!guid_eq(hdr->log_guid, s->headers[s->curr_header]->log_guid)) {
> > +        goto exit;
> > +    }
> > +
> > +    valid = true;
> > +
> > +exit:
> > +    return valid;
> > +}
> > +
> > +/*
> > + * Given a log header, this will validate that the descriptors and the
> > + * corresponding data sectors (if applicable)
> > + *
> > + * Validation consists of:
> > + *      1. Making sure the sequence numbers matches the entry header
> > + *      2. Verifying a valid signature ('zero' or desc' for descriptors)
> 
> s/ desc'/ 'desc'/
>

Thanks

> > + *      3. File offset field is a multiple of 4KB
> > + *      4. If a data descriptor, the corresponding data sector
> > + *         has its signature ('data') and matching sequence number
> > + *
> > + * 'desc' is the data buffer containing the descriptor
> > + * hdr is the log entry header
> 
> Please use gtkdoc format:
> 
> @desc: the data buffer ...
> @hdr:  the log entry header
>

Sure, I can do that.

> > + *
> > + * Returns true if valid
> > + */
> > +static bool vhdx_log_desc_is_valid(VHDXLogDescriptor *desc,
> > +                                   VHDXLogEntryHeader *hdr)
> > +{
> > +    bool ret = false;
> > +
> > +    if (desc->sequence_number != hdr->sequence_number) {
> > +        goto exit;
> > +    }
> > +    if (desc->file_offset % VHDX_LOG_SECTOR_SIZE) {
> > +        goto exit;
> > +    }
> > +
> > +    if (!memcmp(&desc->signature, "zero", 4)) {
> > +        if (!desc->zero_length % VHDX_LOG_SECTOR_SIZE) {
> > +            /* valid */
> > +            ret = true;
> > +        }
> > +    } else if (!memcmp(&desc->signature, "desc", 4)) {
> > +            /* valid */
> > +            ret = true;
> > +    }
> > +
> > +exit:
> > +    return ret;
> > +}
> > +
> > +
> > +/* Prior to sector data for a log entry, there is the header
> > + * and the descriptors referenced in the header:
> > + *
> > + * [] = 4KB sector
> > + *
> > + * [ hdr, desc ][   desc   ][ ... ][ data ][ ... ]
> > + *
> > + * The first sector in a log entry has a 64 byte header, and
> > + * up to 126 32-byte descriptors.  If more descriptors than
> > + * 126 are required, then subsequent sectors can have up to 128
> > + * descriptors.  Each sector is 4KB.  Data follows the descriptor
> > + * sectors.
> > + *
> > + * This will return the number of sectors needed to encompass
> > + * the passed number of descriptors in desc_cnt.
> > + *
> > + * This will never return 0, even if desc_cnt is 0.
> > + */
> > +static int vhdx_compute_desc_sectors(uint32_t desc_cnt)
> > +{
> > +    uint32_t desc_sectors;
> > +
> > +    desc_cnt += 2; /* account for header in first sector */
> > +    desc_sectors = desc_cnt / 128;
> > +    if (desc_cnt % 128) {
> > +        desc_sectors++;
> > +    }
> > +
> > +    return desc_sectors;
> > +}
> > +
> > +
> > +/* Reads the log header, and subsequent descriptors (if any).  This
> > + * will allocate all the space for buffer, which must be NULL when
> > + * passed into this function. Each descriptor will also be validated,
> > + * and error returned if any are invalid. */
> > +static int vhdx_log_read_desc(BlockDriverState *bs, BDRVVHDXState *s,
> > +                              VHDXLogEntries *log, VHDXLogDescEntries 
> > **buffer)
> > +{
> > +    int ret = 0;
> > +    uint32_t desc_sectors;
> > +    uint32_t sectors_read;
> > +    VHDXLogEntryHeader hdr;
> > +    VHDXLogDescEntries *desc_entries = NULL;
> > +    int i;
> > +
> > +    assert(*buffer == NULL);
> > +
> > +    ret = vhdx_log_peek_hdr(bs, log, &hdr);
> > +    if (ret < 0) {
> > +        goto exit;
> > +    }
> > +    vhdx_log_entry_hdr_le_import(&hdr);
> > +    if (vhdx_log_hdr_is_valid(log, &hdr, s) == false) {
> > +        ret = -EINVAL;
> > +        goto exit;
> > +    }
> > +
> > +    desc_sectors = vhdx_compute_desc_sectors(hdr.descriptor_count);
> > +    desc_entries = qemu_blockalign(bs, desc_sectors * 
> > VHDX_LOG_SECTOR_SIZE);
> > +
> > +    ret = vhdx_log_read_sectors(bs, log, &sectors_read, desc_entries,
> > +                                desc_sectors, false);
> > +    if (ret < 0) {
> > +        goto free_and_exit;
> > +    }
> > +    if (sectors_read != desc_sectors) {
> > +        ret = -EINVAL;
> > +        goto free_and_exit;
> > +    }
> > +
> > +    /* put in proper endianness, and validate each desc */
> > +    for (i = 0; i < hdr.descriptor_count; i++) {
> > +        vhdx_log_desc_le_import(&desc_entries->desc[i]);
> > +        if (vhdx_log_desc_is_valid(&desc_entries->desc[i], &hdr) == false) 
> > {
> > +            ret = -EINVAL;
> > +            goto free_and_exit;
> > +        }
> > +    }
> > +
> > +    *buffer = desc_entries;
> > +    goto exit;
> > +
> > +free_and_exit:
> > +    qemu_vfree(desc_entries);
> > +exit:
> > +    return ret;
> > +}
> > +
> > +
> > +/* Flushes the descriptor described by desc to the VHDX image file.
> > + * If the descriptor is a data descriptor, than 'data' must be non-NULL,
> > + * and >= 4096 bytes (VHDX_LOG_SECTOR_SIZE), containing the data to be
> > + * written.
> > + *
> > + * Verification is performed to make sure the sequence numbers of a data
> > + * descriptor match the sequence number in the desc.
> > + *
> > + * For a zero descriptor, it may describe multiple sectors to fill with 
> > zeroes.
> > + * In this case, it should be noted that zeroes are written to disk, and 
> > the
> > + * image file is not extended as a sparse file.  */
> > +static int vhdx_log_flush_desc(BlockDriverState *bs, VHDXLogDescriptor 
> > *desc,
> > +                               VHDXLogDataSector *data)
> > +{
> > +    int ret = 0;
> > +    uint64_t seq, file_offset;
> > +    uint32_t offset = 0;
> > +    void *buffer = NULL;
> > +    uint64_t count = 1;
> > +    int i;
> > +
> > +    buffer = qemu_blockalign(bs, VHDX_LOG_SECTOR_SIZE);
> > +
> > +    if (!memcmp(&desc->signature, "desc", 4)) {
> > +        /* data sector */
> > +        if (data == NULL) {
> > +            ret = -EFAULT;
> > +            goto exit;
> > +        }
> > +
> > +        /* The sequence number of the data sector must match that
> > +         * in the descriptor */
> > +        seq = data->sequence_high;
> > +        seq <<= 32;
> > +        seq |= data->sequence_low & 0xffffffff;
> > +
> > +        if (seq != desc->sequence_number) {
> > +            ret = -EINVAL;
> > +            goto exit;
> > +        }
> > +
> > +        /* Each data sector is in total 4096 bytes, however the first
> > +         * 8 bytes, and last 4 bytes, are located in the descriptor */
> > +        memcpy(buffer, &desc->leading_bytes, sizeof(desc->leading_bytes));
> > +        offset += sizeof(desc->leading_bytes);
> > +
> > +        memcpy(buffer+offset, data->data, 4084);
> > +        offset += 4084;
> 
> Could you use sizeof(data->data) instead of 4084?
>

I suppose I should be consistent, and either use the explicit sizes,
or use 'sizeof'.  I was on the fence on whether it is better for these
three variables to use sizeof() or explicit sizes.

> > +
> > +        memcpy(buffer+offset, &desc->trailing_bytes,
> > +               sizeof(desc->trailing_bytes));
> > +
> > +    } else if (!memcmp(&desc->signature, "zero", 4)) {
> > +        /* write 'count' sectors of sector */
> > +        memset(buffer, 0, VHDX_LOG_SECTOR_SIZE);
> > +        count = desc->zero_length / VHDX_LOG_SECTOR_SIZE;
> > +    }
> > +
> > +    file_offset = desc->file_offset;
> > +
> > +    /* count is only > 1 if we are writing zeroes */
> > +    for (i = 0; i < count; i++) {
> > +        ret = bdrv_pwrite_sync(bs->file, file_offset, buffer,
> > +                               VHDX_LOG_SECTOR_SIZE);
> > +        if (ret < 0) {
> > +            goto exit;
> > +        }
> > +        file_offset += VHDX_LOG_SECTOR_SIZE;
> > +    }
> > +
> > +exit:
> > +    qemu_vfree(buffer);
> > +    return ret;
> > +}
> > +
> > +/* Flush the entire log (as described by 'logs') to the VHDX image
> > + * file, and then set the log to 'empty' status once complete.
> > + *
> > + * The log entries should be validate prior to flushing */
> > +static int vhdx_log_flush(BlockDriverState *bs, BDRVVHDXState *s,
> > +                          VHDXLogSequence *logs)
> > +{
> > +    int ret = 0;
> > +    int i;
> > +    uint32_t cnt, sectors_read;
> > +    uint64_t new_file_size;
> > +    void *data = NULL;
> > +    VHDXLogDescEntries *desc_entries = NULL;
> > +    VHDXLogEntryHeader hdr_tmp = { 0 };
> > +
> > +    cnt = logs->count;
> > +
> > +    data = qemu_blockalign(bs, VHDX_LOG_SECTOR_SIZE);
> > +
> > +    vhdx_user_visible_write(bs, s);
> > +
> > +    /* each iteration represents one log sequence, which may span multiple
> > +     * sectors */
> > +    while (cnt--) {
> > +        ret = vhdx_log_peek_hdr(bs, &logs->log, &hdr_tmp);
> > +        if (ret < 0) {
> > +            goto exit;
> > +        }
> > +        /* if the log shows a FlushedFileOffset larger than our current 
> > file
> > +         * size, then that means the file has been truncated / corrupted, 
> > and
> > +         * we must refused to open it / use it */
> > +        if (hdr_tmp.flushed_file_offset > bdrv_getlength(bs->file)) {
> > +            ret = -EINVAL;
> > +            goto exit;
> > +        }
> > +
> > +        ret = vhdx_log_read_desc(bs, s, &logs->log, &desc_entries);
> > +        if (ret < 0) {
> > +            goto exit;
> > +        }
> > +
> > +        for (i = 0; i < desc_entries->hdr.descriptor_count; i++) {
> > +            if (!memcmp(&desc_entries->desc[i].signature, "desc", 4)) {
> > +                /* data sector, so read a sector to flush */
> > +                ret = vhdx_log_read_sectors(bs, &logs->log, &sectors_read,
> > +                                            data, 1, false);
> > +                if (ret < 0) {
> > +                    goto exit;
> > +                }
> > +                if (sectors_read != 1) {
> > +                    ret = -EINVAL;
> > +                    goto exit;
> > +                }
> > +            }
> > +
> > +            ret = vhdx_log_flush_desc(bs, &desc_entries->desc[i], data);
> > +            if (ret < 0) {
> > +                goto exit;
> > +            }
> > +        }
> > +        if (bdrv_getlength(bs->file) < desc_entries->hdr.last_file_offset) 
> > {
> > +            new_file_size = desc_entries->hdr.last_file_offset;
> > +            if (new_file_size % (1024*1024)) {
> > +                /* round up to nearest 1MB boundary */
> > +                new_file_size = ((new_file_size >> 20) + 1) << 20;
> > +                bdrv_truncate(bs->file, new_file_size);
> > +            }
> > +        }
> > +        qemu_vfree(desc_entries);
> > +        desc_entries = NULL;
> > +    }
> > +
> > +    /* once the log is fully flushed, indicate that we have an empty log
> > +     * now.  This also sets the log guid to 0, to indicate an empty log */
> > +    vhdx_log_reset(bs, s);
> > +
> > +exit:
> > +    qemu_vfree(data);
> > +    qemu_vfree(desc_entries);
> > +    return ret;
> > +}
> > +
> > +static int vhdx_validate_log_entry(BlockDriverState *bs, BDRVVHDXState *s,
> > +                                   VHDXLogEntries *log, uint64_t seq,
> > +                                   bool *valid, VHDXLogEntryHeader *entry)
> > +{
> > +    int ret = 0;
> > +    VHDXLogEntryHeader hdr;
> > +    void *buffer = NULL;
> > +    uint32_t i, desc_sectors, total_sectors, crc;
> > +    uint32_t sectors_read = 0;
> > +    VHDXLogDescEntries *desc_buffer = NULL;
> > +
> > +    *valid = false;
> > +
> > +    ret = vhdx_log_peek_hdr(bs, log, &hdr);
> > +    if (ret < 0) {
> > +        goto inc_and_exit;
> > +    }
> > +
> > +    vhdx_log_entry_hdr_le_import(&hdr);
> > +
> > +
> > +    if (vhdx_log_hdr_is_valid(log, &hdr, s) == false) {
> > +        goto inc_and_exit;
> > +    }
> > +
> > +    if (seq > 0) {
> > +        if (hdr.sequence_number != seq + 1) {
> > +            goto inc_and_exit;
> > +        }
> > +    }
> > +
> > +    desc_sectors = vhdx_compute_desc_sectors(hdr.descriptor_count);
> > +
> > +    /* Read desc sectors, and calculate log checksum */
> > +
> > +    total_sectors = hdr.entry_length / VHDX_LOG_SECTOR_SIZE;
> > +
> > +
> > +    /* read_desc() will incrememnt the read idx */
> > +    ret = vhdx_log_read_desc(bs, s, log, &desc_buffer);
> > +    if (ret < 0) {
> > +        goto free_and_exit;
> > +    }
> > +
> > +    crc = vhdx_checksum_calc(0xffffffff, (void *)desc_buffer,
> > +                            desc_sectors * VHDX_LOG_SECTOR_SIZE, 4);
> > +    crc ^= 0xffffffff;
> > +
> > +    buffer = qemu_blockalign(bs, VHDX_LOG_SECTOR_SIZE);
> > +    if (total_sectors > desc_sectors) {
> > +        for (i = 0; i < total_sectors - desc_sectors; i++) {
> > +            sectors_read = 0;
> > +            ret = vhdx_log_read_sectors(bs, log, &sectors_read, buffer,
> > +                                        1, false);
> > +            if (ret < 0 || sectors_read != 1) {
> > +                goto free_and_exit;
> > +            }
> > +            crc = vhdx_checksum_calc(crc, buffer, VHDX_LOG_SECTOR_SIZE, 
> > -1);
> > +            crc ^= 0xffffffff;
> > +        }
> > +    }
> > +    crc ^= 0xffffffff;
> > +    if (crc != desc_buffer->hdr.checksum) {
> > +        goto free_and_exit;
> > +    }
> > +
> > +    *valid = true;
> > +    *entry = hdr;
> > +    goto free_and_exit;
> > +
> > +inc_and_exit:
> > +    log->read = vhdx_log_inc_idx(log->read, log->length);
> > +
> > +free_and_exit:
> > +    qemu_vfree(buffer);
> > +    qemu_vfree(desc_buffer);
> > +    return ret;
> > +}
> > +
> > +/* Search through the log circular buffer, and find the valid, active
> > + * log sequence, if any exists
> > + * */
> > +static int vhdx_log_search(BlockDriverState *bs, BDRVVHDXState *s,
> > +                           VHDXLogSequence *logs)
> > +{
> > +    int ret = 0;
> > +
> > +    uint64_t curr_seq = 0;
> > +    VHDXLogSequence candidate = { 0 };
> > +    VHDXLogSequence current = { 0 };
> > +
> > +    uint32_t tail;
> > +    bool seq_valid = false;
> > +    VHDXLogEntryHeader hdr = { 0 };
> > +    VHDXLogEntries curr_log;
> > +
> > +    memcpy(&curr_log, &s->log, sizeof(VHDXLogEntries));
> > +    curr_log.write = curr_log.length;   /* assume log is full */
> > +    curr_log.read = 0;
> > +
> > +
> > +    /* now we will go through the whole log sector by sector, until
> > +     * we find a valid, active log sequence, or reach the end of the
> > +     * log buffer */
> > +    for (;;) {
> > +        tail = curr_log.read;
> > +
> > +        curr_seq = 0;
> > +        memset(&current, 0, sizeof(current));
> > +
> > +        ret = vhdx_validate_log_entry(bs, s, &curr_log, curr_seq,
> > +                                      &seq_valid, &hdr);
> > +        if (ret < 0) {
> > +            goto exit;
> > +        }
> > +
> > +        if (seq_valid) {
> > +            current.valid     = true;
> > +            current.log       = curr_log;
> > +            current.log.read  = tail;
> > +            current.log.write = curr_log.read;
> > +            current.count     = 1;
> > +            current.hdr       = hdr;
> > +
> > +
> > +            for (;;) {
> > +                ret = vhdx_validate_log_entry(bs, s, &curr_log, curr_seq,
> > +                                              &seq_valid, &hdr);
> > +                if (ret < 0) {
> > +                    goto exit;
> > +                }
> > +                if (seq_valid == false) {
> > +                    break;
> > +                }
> > +                current.log.write = curr_log.read;
> > +                current.count++;
> > +
> > +                curr_seq = hdr.sequence_number;
> > +            }
> > +        }
> > +
> > +        if (current.valid) {
> > +            if (candidate.valid == false ||
> > +                current.hdr.sequence_number > 
> > candidate.hdr.sequence_number) {
> > +                candidate = current;
> > +            }
> > +        }
> > +
> > +        if (curr_log.read < tail) {
> > +            break;
> > +        }
> > +    }
> > +
> > +    *logs = candidate;
> > +
> > +    if (candidate.valid) {
> > +        /* this is the next sequence number, for writes */
> > +        s->log.sequence = candidate.hdr.sequence_number + 1;
> > +    }
> > +
> > +
> > +exit:
> > +    return ret;
> > +}
> > +
> > +/* Parse the replay log.  Per the VHDX spec, if the log is present
> > + * it must be replayed prior to opening the file, even read-only.
> > + *
> > + * If read-only, we must replay the log in RAM (or refuse to open
> > + * a dirty VHDX file read-only */
> > +int vhdx_parse_log(BlockDriverState *bs, BDRVVHDXState *s)
> > +{
> > +    int ret = 0;
> > +    VHDXHeader *hdr;
> > +    VHDXLogSequence logs = { 0 };
> > +
> > +    hdr = s->headers[s->curr_header];
> > +
> > +    /* s->log.hdr is freed in vhdx_close() */
> > +    if (s->log.hdr == NULL) {
> > +        s->log.hdr = qemu_blockalign(bs, sizeof(VHDXLogEntryHeader));
> > +    }
> > +
> > +    s->log.offset = hdr->log_offset;
> > +    s->log.length = hdr->log_length;
> > +
> > +    if (s->log.offset < VHDX_LOG_MIN_SIZE ||
> > +        s->log.offset % VHDX_LOG_MIN_SIZE) {
> > +        ret = -EINVAL;
> > +        goto exit;
> > +    }
> > +
> > +    /* per spec, only log version of 0 is supported */
> > +    if (hdr->log_version != 0) {
> > +        ret = -EINVAL;
> > +        goto exit;
> > +    }
> > +
> > +    /* If either the log guid, or log length is zero,
> > +     * then a replay log is not present */
> > +    if (vhdx_log_guid_is_zero(&hdr->log_guid)) {
> > +        goto exit;
> > +    }
> > +
> > +
> Too many blank lines here.
> > +
> > +    if (hdr->log_length == 0) {
> > +        goto exit;
> > +    }
> > +
> > +    if (hdr->log_length % VHDX_LOG_MIN_SIZE) {
> > +        ret = -EINVAL;
> > +        goto exit;
> > +    }
> > +
> > +
> > +    /* The log is present, we need to find if and where there is an active
> > +     * sequence of valid entries present in the log.  */
> > +
> > +    ret = vhdx_log_search(bs, s, &logs);
> > +    if (ret < 0) {
> > +        goto exit;
> > +    }
> > +
> > +    if (logs.valid) {
> > +        /* now flush the log */
> > +        ret = vhdx_log_flush(bs, s, &logs);
> 
> Does it flush log regardless of read-only open?
> 

Yes - that will cause an error return to go up the stack once the
write is attempted.

Alternatively, I could return error if the image file has a dirty log,
and is opened read-only (in that case, could return -ENOTSUP).

> > +    }
> > +
> > +
> > +exit:
> > +    return ret;
> > +}
> > +
> > diff --git a/block/vhdx.c b/block/vhdx.c
> > index f5689c3..a8dd6d7 100644
> > --- a/block/vhdx.c
> > +++ b/block/vhdx.c
> > @@ -735,48 +735,6 @@ exit:
> >      return ret;
> >  }
> >  
> > -/* Parse the replay log.  Per the VHDX spec, if the log is present
> > - * it must be replayed prior to opening the file, even read-only.
> > - *
> > - * If read-only, we must replay the log in RAM (or refuse to open
> > - * a dirty VHDX file read-only */
> > -static int vhdx_parse_log(BlockDriverState *bs, BDRVVHDXState *s)
> > -{
> > -    int ret = 0;
> > -    int i;
> > -    VHDXHeader *hdr;
> > -
> > -    hdr = s->headers[s->curr_header];
> > -
> > -    /* either the log guid, or log length is zero,
> > -     * then a replay log is present */
> > -    for (i = 0; i < sizeof(hdr->log_guid.data4); i++) {
> > -        ret |= hdr->log_guid.data4[i];
> > -    }
> > -    if (hdr->log_guid.data1 == 0 &&
> > -        hdr->log_guid.data2 == 0 &&
> > -        hdr->log_guid.data3 == 0 &&
> > -        ret == 0) {
> > -        goto exit;
> > -    }
> > -
> > -    /* per spec, only log version of 0 is supported */
> > -    if (hdr->log_version != 0) {
> > -        ret = -EINVAL;
> > -        goto exit;
> > -    }
> > -
> > -    if (hdr->log_length == 0) {
> > -        goto exit;
> > -    }
> > -
> > -    /* We currently do not support images with logs to replay */
> > -    ret = -ENOTSUP;
> > -
> > -exit:
> > -    return ret;
> > -}
> > -
> >  
> >  static int vhdx_open(BlockDriverState *bs, QDict *options, int flags)
> >  {
> > @@ -789,6 +747,7 @@ static int vhdx_open(BlockDriverState *bs, QDict 
> > *options, int flags)
> >  
> >      s->bat = NULL;
> >      s->first_visible_write = true;
> > +    s->log.write = s->log.read = 0;
> >  
> >      qemu_co_mutex_init(&s->lock);
> >  
> > @@ -1030,6 +989,7 @@ static void vhdx_close(BlockDriverState *bs)
> >      qemu_vfree(s->headers[1]);
> >      qemu_vfree(s->bat);
> >      qemu_vfree(s->parent_entries);
> > +    qemu_vfree(s->log.hdr);
> >  }
> >  
> >  static BlockDriver bdrv_vhdx = {
> > diff --git a/block/vhdx.h b/block/vhdx.h
> > index cb3ce0e..24b126e 100644
> > --- a/block/vhdx.h
> > +++ b/block/vhdx.h
> > @@ -326,7 +326,11 @@ typedef struct VHDXMetadataEntries {
> >  typedef struct VHDXLogEntries {
> >      uint64_t offset;
> >      uint64_t length;
> > -    uint32_t head;
> > +    uint32_t write;
> > +    uint32_t read;
> > +    VHDXLogEntryHeader *hdr;
> > +    void *desc_buffer;
> > +    uint64_t sequence;
> >      uint32_t tail;
> >  } VHDXLogEntries;
> >  
> > @@ -387,6 +391,7 @@ uint32_t vhdx_checksum_calc(uint32_t crc, uint8_t *buf, 
> > size_t size,
> >  
> >  bool vhdx_checksum_is_valid(uint8_t *buf, size_t size, int crc_offset);
> >  
> > +int vhdx_parse_log(BlockDriverState *bs, BDRVVHDXState *s);
> >  
> >  static inline void leguid_to_cpus(MSGUID *guid)
> >  {
> > -- 
> > 1.8.1.4
> > 
> > 
> 
> -- 
> Fam



reply via email to

[Prev in Thread] Current Thread [Next in Thread]