Merge remote-tracking branch 'stefanha/block' into staging
# By Jeff Cody (26) and others # Via Stefan Hajnoczi * stefanha/block: (37 commits) block: Round up total_sectors block: vhdx qemu-iotest - log replay of data sector block: qemu-iotests for vhdx, add write test support block: vhdx - update _make_test_img() to filter out vhdx options block: vhdx - add .bdrv_create() support block: vhdx - fix comment typos in header, fix incorrect struct fields block: vhdx - break out code operations to functions block: vhdx - move more endian translations to vhdx-endian.c block: vhdx - remove BAT file offset bit shifting block: vhdx write support block: vhdx - add log write support block: vhdx - add region overlap detection for image files block: vhdx - log parsing, replay, and flush support block: vhdx code movement - move vhdx_close() above vhdx_open() block: vhdx - update log guid in header, and first write tracker block: vhdx - break endian translation functions out block: vhdx - log support struct and defines block: vhdx code movement - VHDXMetadataEntries and BDRVVHDXState to header. block: vhdx - add header update capability. block: vhdx - minor comments and typo correction. ... Message-id: 1383905551-16411-1-git-send-email-stefanha@redhat.com Signed-off-by: Anthony Liguori <aliguori@amazon.com>
This commit is contained in:
commit
deb0f50065
4
block.c
4
block.c
@ -640,7 +640,7 @@ static int refresh_total_sectors(BlockDriverState *bs, int64_t hint)
|
||||
if (length < 0) {
|
||||
return length;
|
||||
}
|
||||
hint = length >> BDRV_SECTOR_BITS;
|
||||
hint = DIV_ROUND_UP(length, BDRV_SECTOR_SIZE);
|
||||
}
|
||||
|
||||
bs->total_sectors = hint;
|
||||
@ -1084,8 +1084,8 @@ int bdrv_open(BlockDriverState *bs, const char *filename, QDict *options,
|
||||
snprintf(backing_filename, sizeof(backing_filename),
|
||||
"%s", filename);
|
||||
} else if (!realpath(filename, backing_filename)) {
|
||||
error_setg_errno(errp, errno, "Could not resolve path '%s'", filename);
|
||||
ret = -errno;
|
||||
error_setg_errno(errp, errno, "Could not resolve path '%s'", filename);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ block-obj-y += raw_bsd.o cow.o qcow.o vdi.o vmdk.o cloop.o dmg.o bochs.o vpc.o v
|
||||
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-y += vhdx.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
|
||||
|
@ -1842,7 +1842,8 @@ static BlockDriver bdrv_host_cdrom = {
|
||||
#endif /* __linux__ */
|
||||
|
||||
#if defined (__FreeBSD__) || defined(__FreeBSD_kernel__)
|
||||
static int cdrom_open(BlockDriverState *bs, QDict *options, int flags)
|
||||
static int cdrom_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp)
|
||||
{
|
||||
BDRVRawState *s = bs->opaque;
|
||||
Error *local_err = NULL;
|
||||
|
216
block/vhdx-endian.c
Normal file
216
block/vhdx-endian.c
Normal file
@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Block driver for Hyper-V VHDX Images
|
||||
*
|
||||
* Copyright (c) 2013 Red Hat, Inc.,
|
||||
*
|
||||
* Authors:
|
||||
* Jeff Cody <jcody@redhat.com>
|
||||
*
|
||||
* 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 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 "block/vhdx.h"
|
||||
|
||||
#include <uuid/uuid.h>
|
||||
|
||||
|
||||
/*
|
||||
* All the VHDX formats on disk are little endian - the following
|
||||
* are helper import/export functions to correctly convert
|
||||
* endianness from disk read to native cpu format, and back again.
|
||||
*/
|
||||
|
||||
|
||||
/* VHDX File Header */
|
||||
|
||||
|
||||
void vhdx_header_le_import(VHDXHeader *h)
|
||||
{
|
||||
assert(h != NULL);
|
||||
|
||||
le32_to_cpus(&h->signature);
|
||||
le32_to_cpus(&h->checksum);
|
||||
le64_to_cpus(&h->sequence_number);
|
||||
|
||||
leguid_to_cpus(&h->file_write_guid);
|
||||
leguid_to_cpus(&h->data_write_guid);
|
||||
leguid_to_cpus(&h->log_guid);
|
||||
|
||||
le16_to_cpus(&h->log_version);
|
||||
le16_to_cpus(&h->version);
|
||||
le32_to_cpus(&h->log_length);
|
||||
le64_to_cpus(&h->log_offset);
|
||||
}
|
||||
|
||||
void vhdx_header_le_export(VHDXHeader *orig_h, VHDXHeader *new_h)
|
||||
{
|
||||
assert(orig_h != NULL);
|
||||
assert(new_h != NULL);
|
||||
|
||||
new_h->signature = cpu_to_le32(orig_h->signature);
|
||||
new_h->checksum = cpu_to_le32(orig_h->checksum);
|
||||
new_h->sequence_number = cpu_to_le64(orig_h->sequence_number);
|
||||
|
||||
new_h->file_write_guid = orig_h->file_write_guid;
|
||||
new_h->data_write_guid = orig_h->data_write_guid;
|
||||
new_h->log_guid = orig_h->log_guid;
|
||||
|
||||
cpu_to_leguids(&new_h->file_write_guid);
|
||||
cpu_to_leguids(&new_h->data_write_guid);
|
||||
cpu_to_leguids(&new_h->log_guid);
|
||||
|
||||
new_h->log_version = cpu_to_le16(orig_h->log_version);
|
||||
new_h->version = cpu_to_le16(orig_h->version);
|
||||
new_h->log_length = cpu_to_le32(orig_h->log_length);
|
||||
new_h->log_offset = cpu_to_le64(orig_h->log_offset);
|
||||
}
|
||||
|
||||
|
||||
/* VHDX Log Headers */
|
||||
|
||||
|
||||
void vhdx_log_desc_le_import(VHDXLogDescriptor *d)
|
||||
{
|
||||
assert(d != NULL);
|
||||
|
||||
le32_to_cpus(&d->signature);
|
||||
le32_to_cpus(&d->trailing_bytes);
|
||||
le64_to_cpus(&d->leading_bytes);
|
||||
le64_to_cpus(&d->file_offset);
|
||||
le64_to_cpus(&d->sequence_number);
|
||||
}
|
||||
|
||||
void vhdx_log_desc_le_export(VHDXLogDescriptor *d)
|
||||
{
|
||||
assert(d != NULL);
|
||||
|
||||
cpu_to_le32s(&d->signature);
|
||||
cpu_to_le32s(&d->trailing_bytes);
|
||||
cpu_to_le64s(&d->leading_bytes);
|
||||
cpu_to_le64s(&d->file_offset);
|
||||
cpu_to_le64s(&d->sequence_number);
|
||||
}
|
||||
|
||||
void vhdx_log_data_le_export(VHDXLogDataSector *d)
|
||||
{
|
||||
assert(d != NULL);
|
||||
|
||||
cpu_to_le32s(&d->data_signature);
|
||||
cpu_to_le32s(&d->sequence_high);
|
||||
cpu_to_le32s(&d->sequence_low);
|
||||
}
|
||||
|
||||
void vhdx_log_entry_hdr_le_import(VHDXLogEntryHeader *hdr)
|
||||
{
|
||||
assert(hdr != NULL);
|
||||
|
||||
le32_to_cpus(&hdr->signature);
|
||||
le32_to_cpus(&hdr->checksum);
|
||||
le32_to_cpus(&hdr->entry_length);
|
||||
le32_to_cpus(&hdr->tail);
|
||||
le64_to_cpus(&hdr->sequence_number);
|
||||
le32_to_cpus(&hdr->descriptor_count);
|
||||
leguid_to_cpus(&hdr->log_guid);
|
||||
le64_to_cpus(&hdr->flushed_file_offset);
|
||||
le64_to_cpus(&hdr->last_file_offset);
|
||||
}
|
||||
|
||||
void vhdx_log_entry_hdr_le_export(VHDXLogEntryHeader *hdr)
|
||||
{
|
||||
assert(hdr != NULL);
|
||||
|
||||
cpu_to_le32s(&hdr->signature);
|
||||
cpu_to_le32s(&hdr->checksum);
|
||||
cpu_to_le32s(&hdr->entry_length);
|
||||
cpu_to_le32s(&hdr->tail);
|
||||
cpu_to_le64s(&hdr->sequence_number);
|
||||
cpu_to_le32s(&hdr->descriptor_count);
|
||||
cpu_to_leguids(&hdr->log_guid);
|
||||
cpu_to_le64s(&hdr->flushed_file_offset);
|
||||
cpu_to_le64s(&hdr->last_file_offset);
|
||||
}
|
||||
|
||||
|
||||
/* Region table entries */
|
||||
void vhdx_region_header_le_import(VHDXRegionTableHeader *hdr)
|
||||
{
|
||||
assert(hdr != NULL);
|
||||
|
||||
le32_to_cpus(&hdr->signature);
|
||||
le32_to_cpus(&hdr->checksum);
|
||||
le32_to_cpus(&hdr->entry_count);
|
||||
}
|
||||
|
||||
void vhdx_region_header_le_export(VHDXRegionTableHeader *hdr)
|
||||
{
|
||||
assert(hdr != NULL);
|
||||
|
||||
cpu_to_le32s(&hdr->signature);
|
||||
cpu_to_le32s(&hdr->checksum);
|
||||
cpu_to_le32s(&hdr->entry_count);
|
||||
}
|
||||
|
||||
void vhdx_region_entry_le_import(VHDXRegionTableEntry *e)
|
||||
{
|
||||
assert(e != NULL);
|
||||
|
||||
leguid_to_cpus(&e->guid);
|
||||
le64_to_cpus(&e->file_offset);
|
||||
le32_to_cpus(&e->length);
|
||||
le32_to_cpus(&e->data_bits);
|
||||
}
|
||||
|
||||
void vhdx_region_entry_le_export(VHDXRegionTableEntry *e)
|
||||
{
|
||||
assert(e != NULL);
|
||||
|
||||
cpu_to_leguids(&e->guid);
|
||||
cpu_to_le64s(&e->file_offset);
|
||||
cpu_to_le32s(&e->length);
|
||||
cpu_to_le32s(&e->data_bits);
|
||||
}
|
||||
|
||||
|
||||
/* Metadata headers & table */
|
||||
void vhdx_metadata_header_le_import(VHDXMetadataTableHeader *hdr)
|
||||
{
|
||||
assert(hdr != NULL);
|
||||
|
||||
le64_to_cpus(&hdr->signature);
|
||||
le16_to_cpus(&hdr->entry_count);
|
||||
}
|
||||
|
||||
void vhdx_metadata_header_le_export(VHDXMetadataTableHeader *hdr)
|
||||
{
|
||||
assert(hdr != NULL);
|
||||
|
||||
cpu_to_le64s(&hdr->signature);
|
||||
cpu_to_le16s(&hdr->entry_count);
|
||||
}
|
||||
|
||||
void vhdx_metadata_entry_le_import(VHDXMetadataTableEntry *e)
|
||||
{
|
||||
assert(e != NULL);
|
||||
|
||||
leguid_to_cpus(&e->item_id);
|
||||
le32_to_cpus(&e->offset);
|
||||
le32_to_cpus(&e->length);
|
||||
le32_to_cpus(&e->data_bits);
|
||||
}
|
||||
void vhdx_metadata_entry_le_export(VHDXMetadataTableEntry *e)
|
||||
{
|
||||
assert(e != NULL);
|
||||
|
||||
cpu_to_leguids(&e->item_id);
|
||||
cpu_to_le32s(&e->offset);
|
||||
cpu_to_le32s(&e->length);
|
||||
cpu_to_le32s(&e->data_bits);
|
||||
}
|
1010
block/vhdx-log.c
Normal file
1010
block/vhdx-log.c
Normal file
File diff suppressed because it is too large
Load Diff
1296
block/vhdx.c
1296
block/vhdx.c
File diff suppressed because it is too large
Load Diff
178
block/vhdx.h
178
block/vhdx.h
@ -6,9 +6,9 @@
|
||||
* Authors:
|
||||
* Jeff Cody <jcody@redhat.com>
|
||||
*
|
||||
* This is based on the "VHDX Format Specification v0.95", published 4/12/2012
|
||||
* 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=29681
|
||||
* https://www.microsoft.com/en-us/download/details.aspx?id=34750
|
||||
*
|
||||
* 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.
|
||||
@ -18,6 +18,11 @@
|
||||
#ifndef BLOCK_VHDX_H
|
||||
#define BLOCK_VHDX_H
|
||||
|
||||
#define KiB (1 * 1024)
|
||||
#define MiB (KiB * 1024)
|
||||
#define GiB (MiB * 1024)
|
||||
#define TiB ((uint64_t) GiB * 1024)
|
||||
|
||||
/* Structures and fields present in the VHDX file */
|
||||
|
||||
/* The header section has the following blocks,
|
||||
@ -30,14 +35,15 @@
|
||||
* 0.........64KB...........128KB........192KB..........256KB................1MB
|
||||
*/
|
||||
|
||||
#define VHDX_HEADER_BLOCK_SIZE (64*1024)
|
||||
#define VHDX_HEADER_BLOCK_SIZE (64 * 1024)
|
||||
|
||||
#define VHDX_FILE_ID_OFFSET 0
|
||||
#define VHDX_HEADER1_OFFSET (VHDX_HEADER_BLOCK_SIZE*1)
|
||||
#define VHDX_HEADER2_OFFSET (VHDX_HEADER_BLOCK_SIZE*2)
|
||||
#define VHDX_REGION_TABLE_OFFSET (VHDX_HEADER_BLOCK_SIZE*3)
|
||||
|
||||
#define VHDX_HEADER1_OFFSET (VHDX_HEADER_BLOCK_SIZE * 1)
|
||||
#define VHDX_HEADER2_OFFSET (VHDX_HEADER_BLOCK_SIZE * 2)
|
||||
#define VHDX_REGION_TABLE_OFFSET (VHDX_HEADER_BLOCK_SIZE * 3)
|
||||
#define VHDX_REGION_TABLE2_OFFSET (VHDX_HEADER_BLOCK_SIZE * 4)
|
||||
|
||||
#define VHDX_HEADER_SECTION_END (1 * MiB)
|
||||
/*
|
||||
* A note on the use of MS-GUID fields. For more details on the GUID,
|
||||
* please see: https://en.wikipedia.org/wiki/Globally_unique_identifier.
|
||||
@ -55,10 +61,11 @@
|
||||
/* These structures are ones that are defined in the VHDX specification
|
||||
* document */
|
||||
|
||||
#define VHDX_FILE_SIGNATURE 0x656C696678646876 /* "vhdxfile" in ASCII */
|
||||
typedef struct VHDXFileIdentifier {
|
||||
uint64_t signature; /* "vhdxfile" in ASCII */
|
||||
uint16_t creator[256]; /* optional; utf-16 string to identify
|
||||
the vhdx file creator. Diagnotistic
|
||||
the vhdx file creator. Diagnostic
|
||||
only */
|
||||
} VHDXFileIdentifier;
|
||||
|
||||
@ -67,7 +74,7 @@ typedef struct VHDXFileIdentifier {
|
||||
* Microsoft is not just 16 bytes though - it is a structure that is defined,
|
||||
* so we need to follow it here so that endianness does not trip us up */
|
||||
|
||||
typedef struct MSGUID {
|
||||
typedef struct QEMU_PACKED MSGUID {
|
||||
uint32_t data1;
|
||||
uint16_t data2;
|
||||
uint16_t data3;
|
||||
@ -77,14 +84,15 @@ typedef struct MSGUID {
|
||||
#define guid_eq(a, b) \
|
||||
(memcmp(&(a), &(b), sizeof(MSGUID)) == 0)
|
||||
|
||||
#define VHDX_HEADER_SIZE (4*1024) /* although the vhdx_header struct in disk
|
||||
is only 582 bytes, for purposes of crc
|
||||
the header is the first 4KB of the 64KB
|
||||
block */
|
||||
#define VHDX_HEADER_SIZE (4 * 1024) /* although the vhdx_header struct in disk
|
||||
is only 582 bytes, for purposes of crc
|
||||
the header is the first 4KB of the 64KB
|
||||
block */
|
||||
|
||||
/* The full header is 4KB, although the actual header data is much smaller.
|
||||
* But for the checksum calculation, it is over the entire 4KB structure,
|
||||
* not just the defined portion of it */
|
||||
#define VHDX_HEADER_SIGNATURE 0x64616568
|
||||
typedef struct QEMU_PACKED VHDXHeader {
|
||||
uint32_t signature; /* "head" in ASCII */
|
||||
uint32_t checksum; /* CRC-32C hash of the whole header */
|
||||
@ -92,7 +100,7 @@ typedef struct QEMU_PACKED VHDXHeader {
|
||||
VHDX file has 2 of these headers,
|
||||
and only the header with the highest
|
||||
sequence number is valid */
|
||||
MSGUID file_write_guid; /* 128 bit unique identifier. Must be
|
||||
MSGUID file_write_guid; /* 128 bit unique identifier. Must be
|
||||
updated to new, unique value before
|
||||
the first modification is made to
|
||||
file */
|
||||
@ -114,9 +122,9 @@ typedef struct QEMU_PACKED VHDXHeader {
|
||||
there is no valid log. If non-zero,
|
||||
log entries with this guid are
|
||||
valid. */
|
||||
uint16_t log_version; /* version of the log format. Mustn't be
|
||||
zero, unless log_guid is also zero */
|
||||
uint16_t version; /* version of th evhdx file. Currently,
|
||||
uint16_t log_version; /* version of the log format. Must be
|
||||
set to zero */
|
||||
uint16_t version; /* version of the vhdx file. Currently,
|
||||
only supported version is "1" */
|
||||
uint32_t log_length; /* length of the log. Must be multiple
|
||||
of 1MB */
|
||||
@ -125,6 +133,7 @@ typedef struct QEMU_PACKED VHDXHeader {
|
||||
} VHDXHeader;
|
||||
|
||||
/* Header for the region table block */
|
||||
#define VHDX_REGION_SIGNATURE 0x69676572 /* "regi" in ASCII */
|
||||
typedef struct QEMU_PACKED VHDXRegionTableHeader {
|
||||
uint32_t signature; /* "regi" in ASCII */
|
||||
uint32_t checksum; /* CRC-32C hash of the 64KB table */
|
||||
@ -151,7 +160,10 @@ typedef struct QEMU_PACKED VHDXRegionTableEntry {
|
||||
|
||||
|
||||
/* ---- LOG ENTRY STRUCTURES ---- */
|
||||
#define VHDX_LOG_MIN_SIZE (1024 * 1024)
|
||||
#define VHDX_LOG_SECTOR_SIZE 4096
|
||||
#define VHDX_LOG_HDR_SIZE 64
|
||||
#define VHDX_LOG_SIGNATURE 0x65676f6c
|
||||
typedef struct QEMU_PACKED VHDXLogEntryHeader {
|
||||
uint32_t signature; /* "loge" in ASCII */
|
||||
uint32_t checksum; /* CRC-32C hash of the 64KB table */
|
||||
@ -174,7 +186,8 @@ typedef struct QEMU_PACKED VHDXLogEntryHeader {
|
||||
} VHDXLogEntryHeader;
|
||||
|
||||
#define VHDX_LOG_DESC_SIZE 32
|
||||
|
||||
#define VHDX_LOG_DESC_SIGNATURE 0x63736564
|
||||
#define VHDX_LOG_ZERO_SIGNATURE 0x6f72657a
|
||||
typedef struct QEMU_PACKED VHDXLogDescriptor {
|
||||
uint32_t signature; /* "zero" or "desc" in ASCII */
|
||||
union {
|
||||
@ -194,6 +207,7 @@ typedef struct QEMU_PACKED VHDXLogDescriptor {
|
||||
vhdx_log_entry_header */
|
||||
} VHDXLogDescriptor;
|
||||
|
||||
#define VHDX_LOG_DATA_SIGNATURE 0x61746164
|
||||
typedef struct QEMU_PACKED VHDXLogDataSector {
|
||||
uint32_t data_signature; /* "data" in ASCII */
|
||||
uint32_t sequence_high; /* 4 MSB of 8 byte sequence_number */
|
||||
@ -212,19 +226,19 @@ typedef struct QEMU_PACKED VHDXLogDataSector {
|
||||
#define PAYLOAD_BLOCK_UNDEFINED 1
|
||||
#define PAYLOAD_BLOCK_ZERO 2
|
||||
#define PAYLOAD_BLOCK_UNMAPPED 5
|
||||
#define PAYLOAD_BLOCK_FULL_PRESENT 6
|
||||
#define PAYLOAD_BLOCK_FULLY_PRESENT 6
|
||||
#define PAYLOAD_BLOCK_PARTIALLY_PRESENT 7
|
||||
|
||||
#define SB_BLOCK_NOT_PRESENT 0
|
||||
#define SB_BLOCK_PRESENT 6
|
||||
|
||||
/* per the spec */
|
||||
#define VHDX_MAX_SECTORS_PER_BLOCK (1<<23)
|
||||
#define VHDX_MAX_SECTORS_PER_BLOCK (1 << 23)
|
||||
|
||||
/* upper 44 bits are the file offset in 1MB units lower 3 bits are the state
|
||||
other bits are reserved */
|
||||
#define VHDX_BAT_STATE_BIT_MASK 0x07
|
||||
#define VHDX_BAT_FILE_OFF_BITS (64-44)
|
||||
#define VHDX_BAT_FILE_OFF_MASK 0xFFFFFFFFFFF00000 /* upper 44 bits */
|
||||
typedef uint64_t VHDXBatEntry;
|
||||
|
||||
/* ---- METADATA REGION STRUCTURES ---- */
|
||||
@ -233,6 +247,7 @@ typedef uint64_t VHDXBatEntry;
|
||||
#define VHDX_METADATA_MAX_ENTRIES 2047 /* not including the header */
|
||||
#define VHDX_METADATA_TABLE_MAX_SIZE \
|
||||
(VHDX_METADATA_ENTRY_SIZE * (VHDX_METADATA_MAX_ENTRIES+1))
|
||||
#define VHDX_METADATA_SIGNATURE 0x617461646174656D /* "metadata" in ASCII */
|
||||
typedef struct QEMU_PACKED VHDXMetadataTableHeader {
|
||||
uint64_t signature; /* "metadata" in ASCII */
|
||||
uint16_t reserved;
|
||||
@ -252,8 +267,8 @@ typedef struct QEMU_PACKED VHDXMetadataTableEntry {
|
||||
metadata region */
|
||||
/* note: if length = 0, so is offset */
|
||||
uint32_t length; /* length of metadata. <= 1MB. */
|
||||
uint32_t data_bits; /* least-significant 3 bits are flags, the
|
||||
rest are reserved (see above) */
|
||||
uint32_t data_bits; /* least-significant 3 bits are flags,
|
||||
the rest are reserved (see above) */
|
||||
uint32_t reserved2;
|
||||
} VHDXMetadataTableEntry;
|
||||
|
||||
@ -262,13 +277,16 @@ typedef struct QEMU_PACKED VHDXMetadataTableEntry {
|
||||
If set indicates a fixed
|
||||
size VHDX file */
|
||||
#define VHDX_PARAMS_HAS_PARENT 0x02 /* has parent / backing file */
|
||||
#define VHDX_BLOCK_SIZE_MIN (1 * MiB)
|
||||
#define VHDX_BLOCK_SIZE_MAX (256 * MiB)
|
||||
typedef struct QEMU_PACKED VHDXFileParameters {
|
||||
uint32_t block_size; /* size of each payload block, always
|
||||
power of 2, <= 256MB and >= 1MB. */
|
||||
uint32_t data_bits; /* least-significant 2 bits are flags, the rest
|
||||
are reserved (see above) */
|
||||
uint32_t data_bits; /* least-significant 2 bits are flags,
|
||||
the rest are reserved (see above) */
|
||||
} VHDXFileParameters;
|
||||
|
||||
#define VHDX_MAX_IMAGE_SIZE ((uint64_t) 64 * TiB)
|
||||
typedef struct QEMU_PACKED VHDXVirtualDiskSize {
|
||||
uint64_t virtual_disk_size; /* Size of the virtual disk, in bytes.
|
||||
Must be multiple of the sector size,
|
||||
@ -276,7 +294,7 @@ typedef struct QEMU_PACKED VHDXVirtualDiskSize {
|
||||
} VHDXVirtualDiskSize;
|
||||
|
||||
typedef struct QEMU_PACKED VHDXPage83Data {
|
||||
MSGUID page_83_data[16]; /* unique id for scsi devices that
|
||||
MSGUID page_83_data; /* unique id for scsi devices that
|
||||
support page 0x83 */
|
||||
} VHDXPage83Data;
|
||||
|
||||
@ -291,7 +309,7 @@ typedef struct QEMU_PACKED VHDXVirtualDiskPhysicalSectorSize {
|
||||
} VHDXVirtualDiskPhysicalSectorSize;
|
||||
|
||||
typedef struct QEMU_PACKED VHDXParentLocatorHeader {
|
||||
MSGUID locator_type[16]; /* type of the parent virtual disk. */
|
||||
MSGUID locator_type; /* type of the parent virtual disk. */
|
||||
uint16_t reserved;
|
||||
uint16_t key_value_count; /* number of key/value pairs for this
|
||||
locator */
|
||||
@ -308,18 +326,122 @@ typedef struct QEMU_PACKED VHDXParentLocatorEntry {
|
||||
|
||||
/* ----- END VHDX SPECIFICATION STRUCTURES ---- */
|
||||
|
||||
typedef struct VHDXMetadataEntries {
|
||||
VHDXMetadataTableEntry file_parameters_entry;
|
||||
VHDXMetadataTableEntry virtual_disk_size_entry;
|
||||
VHDXMetadataTableEntry page83_data_entry;
|
||||
VHDXMetadataTableEntry logical_sector_size_entry;
|
||||
VHDXMetadataTableEntry phys_sector_size_entry;
|
||||
VHDXMetadataTableEntry parent_locator_entry;
|
||||
uint16_t present;
|
||||
} VHDXMetadataEntries;
|
||||
|
||||
typedef struct VHDXLogEntries {
|
||||
uint64_t offset;
|
||||
uint64_t length;
|
||||
uint32_t write;
|
||||
uint32_t read;
|
||||
VHDXLogEntryHeader *hdr;
|
||||
void *desc_buffer;
|
||||
uint64_t sequence;
|
||||
uint32_t tail;
|
||||
} VHDXLogEntries;
|
||||
|
||||
typedef struct VHDXRegionEntry {
|
||||
uint64_t start;
|
||||
uint64_t end;
|
||||
QLIST_ENTRY(VHDXRegionEntry) entries;
|
||||
} VHDXRegionEntry;
|
||||
|
||||
typedef struct BDRVVHDXState {
|
||||
CoMutex lock;
|
||||
|
||||
int curr_header;
|
||||
VHDXHeader *headers[2];
|
||||
|
||||
VHDXRegionTableHeader rt;
|
||||
VHDXRegionTableEntry bat_rt; /* region table for the BAT */
|
||||
VHDXRegionTableEntry metadata_rt; /* region table for the metadata */
|
||||
|
||||
VHDXMetadataTableHeader metadata_hdr;
|
||||
VHDXMetadataEntries metadata_entries;
|
||||
|
||||
VHDXFileParameters params;
|
||||
uint32_t block_size;
|
||||
uint32_t block_size_bits;
|
||||
uint32_t sectors_per_block;
|
||||
uint32_t sectors_per_block_bits;
|
||||
|
||||
uint64_t virtual_disk_size;
|
||||
uint32_t logical_sector_size;
|
||||
uint32_t physical_sector_size;
|
||||
|
||||
uint64_t chunk_ratio;
|
||||
uint32_t chunk_ratio_bits;
|
||||
uint32_t logical_sector_size_bits;
|
||||
|
||||
uint32_t bat_entries;
|
||||
VHDXBatEntry *bat;
|
||||
uint64_t bat_offset;
|
||||
|
||||
bool first_visible_write;
|
||||
MSGUID session_guid;
|
||||
|
||||
VHDXLogEntries log;
|
||||
|
||||
VHDXParentLocatorHeader parent_header;
|
||||
VHDXParentLocatorEntry *parent_entries;
|
||||
|
||||
Error *migration_blocker;
|
||||
|
||||
QLIST_HEAD(VHDXRegionHead, VHDXRegionEntry) regions;
|
||||
} BDRVVHDXState;
|
||||
|
||||
void vhdx_guid_generate(MSGUID *guid);
|
||||
|
||||
int vhdx_update_headers(BlockDriverState *bs, BDRVVHDXState *s, bool rw,
|
||||
MSGUID *log_guid);
|
||||
|
||||
uint32_t vhdx_update_checksum(uint8_t *buf, size_t size, int crc_offset);
|
||||
uint32_t vhdx_checksum_calc(uint32_t crc, uint8_t *buf, size_t size,
|
||||
int crc_offset);
|
||||
|
||||
bool vhdx_checksum_is_valid(uint8_t *buf, size_t size, int crc_offset);
|
||||
|
||||
int vhdx_parse_log(BlockDriverState *bs, BDRVVHDXState *s, bool *flushed);
|
||||
|
||||
static void leguid_to_cpus(MSGUID *guid)
|
||||
int vhdx_log_write_and_flush(BlockDriverState *bs, BDRVVHDXState *s,
|
||||
void *data, uint32_t length, uint64_t offset);
|
||||
|
||||
static inline void leguid_to_cpus(MSGUID *guid)
|
||||
{
|
||||
le32_to_cpus(&guid->data1);
|
||||
le16_to_cpus(&guid->data2);
|
||||
le16_to_cpus(&guid->data3);
|
||||
}
|
||||
|
||||
static inline void cpu_to_leguids(MSGUID *guid)
|
||||
{
|
||||
cpu_to_le32s(&guid->data1);
|
||||
cpu_to_le16s(&guid->data2);
|
||||
cpu_to_le16s(&guid->data3);
|
||||
}
|
||||
|
||||
void vhdx_header_le_import(VHDXHeader *h);
|
||||
void vhdx_header_le_export(VHDXHeader *orig_h, VHDXHeader *new_h);
|
||||
void vhdx_log_desc_le_import(VHDXLogDescriptor *d);
|
||||
void vhdx_log_desc_le_export(VHDXLogDescriptor *d);
|
||||
void vhdx_log_data_le_export(VHDXLogDataSector *d);
|
||||
void vhdx_log_entry_hdr_le_import(VHDXLogEntryHeader *hdr);
|
||||
void vhdx_log_entry_hdr_le_export(VHDXLogEntryHeader *hdr);
|
||||
void vhdx_region_header_le_import(VHDXRegionTableHeader *hdr);
|
||||
void vhdx_region_header_le_export(VHDXRegionTableHeader *hdr);
|
||||
void vhdx_region_entry_le_import(VHDXRegionTableEntry *e);
|
||||
void vhdx_region_entry_le_export(VHDXRegionTableEntry *e);
|
||||
void vhdx_metadata_header_le_import(VHDXMetadataTableHeader *hdr);
|
||||
void vhdx_metadata_header_le_export(VHDXMetadataTableHeader *hdr);
|
||||
void vhdx_metadata_entry_le_import(VHDXMetadataTableEntry *e);
|
||||
void vhdx_metadata_entry_le_export(VHDXMetadataTableEntry *e);
|
||||
int vhdx_user_visible_write(BlockDriverState *bs, BDRVVHDXState *s);
|
||||
|
||||
#endif
|
||||
|
@ -211,6 +211,15 @@ static int vpc_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
bs->total_sectors = (int64_t)
|
||||
be16_to_cpu(footer->cyls) * footer->heads * footer->secs_per_cyl;
|
||||
|
||||
/* images created with disk2vhd report a far higher virtual size
|
||||
* than expected with the cyls * heads * sectors_per_cyl formula.
|
||||
* use the footer->size instead if the image was created with
|
||||
* disk2vhd.
|
||||
*/
|
||||
if (!strncmp(footer->creator_app, "d2v", 4)) {
|
||||
bs->total_sectors = be64_to_cpu(footer->size) / BDRV_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
/* Allow a maximum disk size of approximately 2 TB */
|
||||
if (bs->total_sectors >= 65535LL * 255 * 255) {
|
||||
ret = -EFBIG;
|
||||
|
27
blockdev.c
27
blockdev.c
@ -341,7 +341,7 @@ static DriveInfo *blockdev_init(QDict *bs_opts,
|
||||
qemu_opts_absorb_qdict(opts, bs_opts, &error);
|
||||
if (error_is_set(&error)) {
|
||||
error_propagate(errp, error);
|
||||
return NULL;
|
||||
goto early_err;
|
||||
}
|
||||
|
||||
if (id) {
|
||||
@ -361,7 +361,7 @@ static DriveInfo *blockdev_init(QDict *bs_opts,
|
||||
if ((buf = qemu_opt_get(opts, "discard")) != NULL) {
|
||||
if (bdrv_parse_discard_flags(buf, &bdrv_flags) != 0) {
|
||||
error_setg(errp, "invalid discard option");
|
||||
return NULL;
|
||||
goto early_err;
|
||||
}
|
||||
}
|
||||
|
||||
@ -383,7 +383,7 @@ static DriveInfo *blockdev_init(QDict *bs_opts,
|
||||
/* this is the default */
|
||||
} else {
|
||||
error_setg(errp, "invalid aio option");
|
||||
return NULL;
|
||||
goto early_err;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -393,13 +393,13 @@ static DriveInfo *blockdev_init(QDict *bs_opts,
|
||||
error_printf("Supported formats:");
|
||||
bdrv_iterate_format(bdrv_format_print, NULL);
|
||||
error_printf("\n");
|
||||
return NULL;
|
||||
goto early_err;
|
||||
}
|
||||
|
||||
drv = bdrv_find_format(buf);
|
||||
if (!drv) {
|
||||
error_setg(errp, "'%s' invalid format", buf);
|
||||
return NULL;
|
||||
goto early_err;
|
||||
}
|
||||
}
|
||||
|
||||
@ -435,20 +435,20 @@ static DriveInfo *blockdev_init(QDict *bs_opts,
|
||||
|
||||
if (!check_throttle_config(&cfg, &error)) {
|
||||
error_propagate(errp, error);
|
||||
return NULL;
|
||||
goto early_err;
|
||||
}
|
||||
|
||||
on_write_error = BLOCKDEV_ON_ERROR_ENOSPC;
|
||||
if ((buf = qemu_opt_get(opts, "werror")) != NULL) {
|
||||
if (type != IF_IDE && type != IF_SCSI && type != IF_VIRTIO && type != IF_NONE) {
|
||||
error_setg(errp, "werror is not supported by this bus type");
|
||||
return NULL;
|
||||
goto early_err;
|
||||
}
|
||||
|
||||
on_write_error = parse_block_error_action(buf, 0, &error);
|
||||
if (error_is_set(&error)) {
|
||||
error_propagate(errp, error);
|
||||
return NULL;
|
||||
goto early_err;
|
||||
}
|
||||
}
|
||||
|
||||
@ -456,13 +456,13 @@ static DriveInfo *blockdev_init(QDict *bs_opts,
|
||||
if ((buf = qemu_opt_get(opts, "rerror")) != NULL) {
|
||||
if (type != IF_IDE && type != IF_VIRTIO && type != IF_SCSI && type != IF_NONE) {
|
||||
error_report("rerror is not supported by this bus type");
|
||||
return NULL;
|
||||
goto early_err;
|
||||
}
|
||||
|
||||
on_read_error = parse_block_error_action(buf, 1, &error);
|
||||
if (error_is_set(&error)) {
|
||||
error_propagate(errp, error);
|
||||
return NULL;
|
||||
goto early_err;
|
||||
}
|
||||
}
|
||||
|
||||
@ -491,6 +491,8 @@ static DriveInfo *blockdev_init(QDict *bs_opts,
|
||||
if (has_driver_specific_opts) {
|
||||
file = NULL;
|
||||
} else {
|
||||
QDECREF(bs_opts);
|
||||
qemu_opts_del(opts);
|
||||
return dinfo;
|
||||
}
|
||||
}
|
||||
@ -529,12 +531,13 @@ static DriveInfo *blockdev_init(QDict *bs_opts,
|
||||
return dinfo;
|
||||
|
||||
err:
|
||||
qemu_opts_del(opts);
|
||||
QDECREF(bs_opts);
|
||||
bdrv_unref(dinfo->bdrv);
|
||||
g_free(dinfo->id);
|
||||
QTAILQ_REMOVE(&drives, dinfo, next);
|
||||
g_free(dinfo);
|
||||
early_err:
|
||||
QDECREF(bs_opts);
|
||||
qemu_opts_del(opts);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
24
configure
vendored
24
configure
vendored
@ -260,6 +260,7 @@ gtk=""
|
||||
gtkabi="2.0"
|
||||
tpm="no"
|
||||
libssh2=""
|
||||
vhdx=""
|
||||
|
||||
# parse CC options first
|
||||
for opt do
|
||||
@ -985,6 +986,10 @@ for opt do
|
||||
;;
|
||||
--enable-libssh2) libssh2="yes"
|
||||
;;
|
||||
--enable-vhdx) vhdx="yes"
|
||||
;;
|
||||
--disable-vhdx) vhdx="no"
|
||||
;;
|
||||
*) echo "ERROR: unknown option $opt"; show_help="yes"
|
||||
;;
|
||||
esac
|
||||
@ -1217,6 +1222,8 @@ echo " --gcov=GCOV use specified gcov [$gcov_tool]"
|
||||
echo " --enable-tpm enable TPM support"
|
||||
echo " --disable-libssh2 disable ssh block device support"
|
||||
echo " --enable-libssh2 enable ssh block device support"
|
||||
echo " --disable-vhdx disables support for the Microsoft VHDX image format"
|
||||
echo " --enable-vhdx enable support for the Microsoft VHDX image format"
|
||||
echo ""
|
||||
echo "NOTE: The object files are built at the place where configure is launched"
|
||||
exit 1
|
||||
@ -2017,6 +2024,18 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "$vhdx" = "yes" ; then
|
||||
if test "$uuid" = "no" ; then
|
||||
error_exit "uuid required for VHDX support"
|
||||
fi
|
||||
elif test "$vhdx" != "no" ; then
|
||||
if test "$uuid" = "yes" ; then
|
||||
vhdx=yes
|
||||
else
|
||||
vhdx=no
|
||||
fi
|
||||
fi
|
||||
|
||||
##########################################
|
||||
# xfsctl() probe, used for raw-posix
|
||||
if test "$xfs" != "no" ; then
|
||||
@ -3760,6 +3779,7 @@ echo "TPM support $tpm"
|
||||
echo "libssh2 support $libssh2"
|
||||
echo "TPM passthrough $tpm_passthrough"
|
||||
echo "QOM debugging $qom_cast_debug"
|
||||
echo "vhdx $vhdx"
|
||||
|
||||
if test "$sdl_too_old" = "yes"; then
|
||||
echo "-> Your SDL version is too old - please upgrade to have SDL support"
|
||||
@ -4152,6 +4172,10 @@ if test "$virtio_blk_data_plane" = "yes" ; then
|
||||
echo 'CONFIG_VIRTIO_BLK_DATA_PLANE=$(CONFIG_VIRTIO)' >> $config_host_mak
|
||||
fi
|
||||
|
||||
if test "$vhdx" = "yes" ; then
|
||||
echo "CONFIG_VHDX=y" >> $config_host_mak
|
||||
fi
|
||||
|
||||
# USB host support
|
||||
if test "$libusb" = "yes"; then
|
||||
echo "HOST_USB=libusb legacy" >> $config_host_mak
|
||||
|
@ -227,7 +227,7 @@
|
||||
##
|
||||
# @ImageInfoSpecificVmdk:
|
||||
#
|
||||
# @create_type: The create type of VMDK image
|
||||
# @create-type: The create type of VMDK image
|
||||
#
|
||||
# @cid: Content id of image
|
||||
#
|
||||
|
@ -68,6 +68,8 @@ check-qtest-i386-y += tests/rtc-test$(EXESUF)
|
||||
check-qtest-i386-y += tests/i440fx-test$(EXESUF)
|
||||
check-qtest-i386-y += tests/fw_cfg-test$(EXESUF)
|
||||
check-qtest-i386-y += tests/qom-test$(EXESUF)
|
||||
check-qtest-i386-y += tests/blockdev-test$(EXESUF)
|
||||
check-qtest-i386-y += tests/qdev-monitor-test$(EXESUF)
|
||||
check-qtest-x86_64-y = $(check-qtest-i386-y)
|
||||
gcov-files-i386-y += i386-softmmu/hw/mc146818rtc.c
|
||||
gcov-files-x86_64-y = $(subst i386-softmmu/,x86_64-softmmu/,$(gcov-files-i386-y))
|
||||
@ -200,6 +202,8 @@ tests/tmp105-test$(EXESUF): tests/tmp105-test.o $(libqos-omap-obj-y)
|
||||
tests/i440fx-test$(EXESUF): tests/i440fx-test.o $(libqos-pc-obj-y)
|
||||
tests/fw_cfg-test$(EXESUF): tests/fw_cfg-test.o $(libqos-pc-obj-y)
|
||||
tests/qom-test$(EXESUF): tests/qom-test.o
|
||||
tests/blockdev-test$(EXESUF): tests/blockdev-test.o $(libqos-pc-obj-y)
|
||||
tests/qdev-monitor-test$(EXESUF): tests/qdev-monitor-test.o $(libqos-pc-obj-y)
|
||||
tests/qemu-iotests/socket_scm_helper$(EXESUF): tests/qemu-iotests/socket_scm_helper.o
|
||||
|
||||
# QTest rules
|
||||
|
59
tests/blockdev-test.c
Normal file
59
tests/blockdev-test.c
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* blockdev.c test cases
|
||||
*
|
||||
* Copyright (C) 2013 Red Hat Inc.
|
||||
*
|
||||
* Authors:
|
||||
* Stefan Hajnoczi <stefanha@redhat.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
|
||||
* See the COPYING.LIB file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
#include "libqtest.h"
|
||||
|
||||
static void test_drive_add_empty(void)
|
||||
{
|
||||
QDict *response;
|
||||
const char *response_return;
|
||||
|
||||
/* Start with an empty drive */
|
||||
qtest_start("-drive if=none,id=drive0");
|
||||
|
||||
/* Delete the drive */
|
||||
response = qmp("{\"execute\": \"human-monitor-command\","
|
||||
" \"arguments\": {"
|
||||
" \"command-line\": \"drive_del drive0\""
|
||||
"}}");
|
||||
g_assert(response);
|
||||
response_return = qdict_get_try_str(response, "return");
|
||||
g_assert(response_return);
|
||||
g_assert(strcmp(response_return, "") == 0);
|
||||
QDECREF(response);
|
||||
|
||||
/* Ensure re-adding the drive works - there should be no duplicate ID error
|
||||
* because the old drive must be gone.
|
||||
*/
|
||||
response = qmp("{\"execute\": \"human-monitor-command\","
|
||||
" \"arguments\": {"
|
||||
" \"command-line\": \"drive_add 0 if=none,id=drive0\""
|
||||
"}}");
|
||||
g_assert(response);
|
||||
response_return = qdict_get_try_str(response, "return");
|
||||
g_assert(response_return);
|
||||
g_assert(strcmp(response_return, "OK\r\n") == 0);
|
||||
QDECREF(response);
|
||||
|
||||
qtest_end();
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
qtest_add_func("/qmp/drive_add_empty", test_drive_add_empty);
|
||||
|
||||
return g_test_run();
|
||||
}
|
@ -41,12 +41,12 @@ static void test_a_boot_order(const char *machine,
|
||||
qtest_start(args);
|
||||
actual = read_boot_order();
|
||||
g_assert_cmphex(actual, ==, expected_boot);
|
||||
qmp("{ 'execute': 'system_reset' }");
|
||||
qmp_discard_response("{ 'execute': 'system_reset' }");
|
||||
/*
|
||||
* system_reset only requests reset. We get a RESET event after
|
||||
* the actual reset completes. Need to wait for that.
|
||||
*/
|
||||
qmp(""); /* HACK: wait for event */
|
||||
qmp_discard_response(""); /* HACK: wait for event */
|
||||
actual = read_boot_order();
|
||||
g_assert_cmphex(actual, ==, expected_reboot);
|
||||
qtest_quit(global_qtest);
|
||||
|
@ -290,10 +290,12 @@ static void test_media_insert(void)
|
||||
|
||||
/* Insert media in drive. DSKCHK should not be reset until a step pulse
|
||||
* is sent. */
|
||||
qmp("{'execute':'change', 'arguments':{ 'device':'floppy0', "
|
||||
"'target': '%s' }}", test_image);
|
||||
qmp(""); /* ignore event (FIXME open -> open transition?!) */
|
||||
qmp(""); /* ignore event */
|
||||
qmp_discard_response("{'execute':'change', 'arguments':{"
|
||||
" 'device':'floppy0', 'target': '%s' }}",
|
||||
test_image);
|
||||
qmp_discard_response(""); /* ignore event
|
||||
(FIXME open -> open transition?!) */
|
||||
qmp_discard_response(""); /* ignore event */
|
||||
|
||||
dir = inb(FLOPPY_BASE + reg_dir);
|
||||
assert_bit_set(dir, DSKCHG);
|
||||
@ -322,8 +324,9 @@ static void test_media_change(void)
|
||||
|
||||
/* Eject the floppy and check that DSKCHG is set. Reading it out doesn't
|
||||
* reset the bit. */
|
||||
qmp("{'execute':'eject', 'arguments':{ 'device':'floppy0' }}");
|
||||
qmp(""); /* ignore event */
|
||||
qmp_discard_response("{'execute':'eject', 'arguments':{"
|
||||
" 'device':'floppy0' }}");
|
||||
qmp_discard_response(""); /* ignore event */
|
||||
|
||||
dir = inb(FLOPPY_BASE + reg_dir);
|
||||
assert_bit_set(dir, DSKCHG);
|
||||
|
@ -460,8 +460,9 @@ static void test_flush(void)
|
||||
tmp_path);
|
||||
|
||||
/* Delay the completion of the flush request until we explicitly do it */
|
||||
qmp("{'execute':'human-monitor-command', 'arguments': { "
|
||||
"'command-line': 'qemu-io ide0-hd0 \"break flush_to_os A\"'} }");
|
||||
qmp_discard_response("{'execute':'human-monitor-command', 'arguments': {"
|
||||
" 'command-line':"
|
||||
" 'qemu-io ide0-hd0 \"break flush_to_os A\"'} }");
|
||||
|
||||
/* FLUSH CACHE command on device 0*/
|
||||
outb(IDE_BASE + reg_device, 0);
|
||||
@ -473,8 +474,9 @@ static void test_flush(void)
|
||||
assert_bit_clear(data, DF | ERR | DRQ);
|
||||
|
||||
/* Complete the command */
|
||||
qmp("{'execute':'human-monitor-command', 'arguments': { "
|
||||
"'command-line': 'qemu-io ide0-hd0 \"resume A\"'} }");
|
||||
qmp_discard_response("{'execute':'human-monitor-command', 'arguments': {"
|
||||
" 'command-line':"
|
||||
" 'qemu-io ide0-hd0 \"resume A\"'} }");
|
||||
|
||||
/* Check registers */
|
||||
data = inb(IDE_BASE + reg_device);
|
||||
|
@ -30,6 +30,8 @@
|
||||
|
||||
#include "qemu/compiler.h"
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/qmp/json-streamer.h"
|
||||
#include "qapi/qmp/json-parser.h"
|
||||
|
||||
#define MAX_IRQ 256
|
||||
|
||||
@ -151,8 +153,8 @@ QTestState *qtest_init(const char *extra_args)
|
||||
}
|
||||
|
||||
/* Read the QMP greeting and then do the handshake */
|
||||
qtest_qmp(s, "");
|
||||
qtest_qmp(s, "{ 'execute': 'qmp_capabilities' }");
|
||||
qtest_qmp_discard_response(s, "");
|
||||
qtest_qmp_discard_response(s, "{ 'execute': 'qmp_capabilities' }");
|
||||
|
||||
if (getenv("QTEST_STOP")) {
|
||||
kill(qtest_qemu_pid(s), SIGSTOP);
|
||||
@ -291,16 +293,38 @@ redo:
|
||||
return words;
|
||||
}
|
||||
|
||||
void qtest_qmpv(QTestState *s, const char *fmt, va_list ap)
|
||||
typedef struct {
|
||||
JSONMessageParser parser;
|
||||
QDict *response;
|
||||
} QMPResponseParser;
|
||||
|
||||
static void qmp_response(JSONMessageParser *parser, QList *tokens)
|
||||
{
|
||||
bool has_reply = false;
|
||||
int nesting = 0;
|
||||
QMPResponseParser *qmp = container_of(parser, QMPResponseParser, parser);
|
||||
QObject *obj;
|
||||
|
||||
obj = json_parser_parse(tokens, NULL);
|
||||
if (!obj) {
|
||||
fprintf(stderr, "QMP JSON response parsing failed\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
g_assert(qobject_type(obj) == QTYPE_QDICT);
|
||||
g_assert(!qmp->response);
|
||||
qmp->response = (QDict *)obj;
|
||||
}
|
||||
|
||||
QDict *qtest_qmpv(QTestState *s, const char *fmt, va_list ap)
|
||||
{
|
||||
QMPResponseParser qmp;
|
||||
|
||||
/* Send QMP request */
|
||||
socket_sendf(s->qmp_fd, fmt, ap);
|
||||
|
||||
/* Receive reply */
|
||||
while (!has_reply || nesting > 0) {
|
||||
qmp.response = NULL;
|
||||
json_message_parser_init(&qmp.parser, qmp_response);
|
||||
while (!qmp.response) {
|
||||
ssize_t len;
|
||||
char c;
|
||||
|
||||
@ -314,25 +338,39 @@ void qtest_qmpv(QTestState *s, const char *fmt, va_list ap)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
case '{':
|
||||
nesting++;
|
||||
has_reply = true;
|
||||
break;
|
||||
case '}':
|
||||
nesting--;
|
||||
break;
|
||||
}
|
||||
json_message_parser_feed(&qmp.parser, &c, 1);
|
||||
}
|
||||
json_message_parser_destroy(&qmp.parser);
|
||||
|
||||
return qmp.response;
|
||||
}
|
||||
|
||||
void qtest_qmp(QTestState *s, const char *fmt, ...)
|
||||
QDict *qtest_qmp(QTestState *s, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
QDict *response;
|
||||
|
||||
va_start(ap, fmt);
|
||||
qtest_qmpv(s, fmt, ap);
|
||||
response = qtest_qmpv(s, fmt, ap);
|
||||
va_end(ap);
|
||||
return response;
|
||||
}
|
||||
|
||||
void qtest_qmpv_discard_response(QTestState *s, const char *fmt, va_list ap)
|
||||
{
|
||||
QDict *response = qtest_qmpv(s, fmt, ap);
|
||||
QDECREF(response);
|
||||
}
|
||||
|
||||
void qtest_qmp_discard_response(QTestState *s, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
QDict *response;
|
||||
|
||||
va_start(ap, fmt);
|
||||
response = qtest_qmpv(s, fmt, ap);
|
||||
va_end(ap);
|
||||
QDECREF(response);
|
||||
}
|
||||
|
||||
const char *qtest_get_arch(void)
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/types.h>
|
||||
#include "qapi/qmp/qdict.h"
|
||||
|
||||
typedef struct QTestState QTestState;
|
||||
|
||||
@ -43,14 +44,33 @@ QTestState *qtest_init(const char *extra_args);
|
||||
*/
|
||||
void qtest_quit(QTestState *s);
|
||||
|
||||
/**
|
||||
* qtest_qmp_discard_response:
|
||||
* @s: #QTestState instance to operate on.
|
||||
* @fmt...: QMP message to send to qemu
|
||||
*
|
||||
* Sends a QMP message to QEMU and consumes the response.
|
||||
*/
|
||||
void qtest_qmp_discard_response(QTestState *s, const char *fmt, ...);
|
||||
|
||||
/**
|
||||
* qtest_qmp:
|
||||
* @s: #QTestState instance to operate on.
|
||||
* @fmt...: QMP message to send to qemu
|
||||
*
|
||||
* Sends a QMP message to QEMU
|
||||
* Sends a QMP message to QEMU and returns the response.
|
||||
*/
|
||||
void qtest_qmp(QTestState *s, const char *fmt, ...);
|
||||
QDict *qtest_qmp(QTestState *s, const char *fmt, ...);
|
||||
|
||||
/**
|
||||
* qtest_qmpv_discard_response:
|
||||
* @s: #QTestState instance to operate on.
|
||||
* @fmt: QMP message to send to QEMU
|
||||
* @ap: QMP message arguments
|
||||
*
|
||||
* Sends a QMP message to QEMU and consumes the response.
|
||||
*/
|
||||
void qtest_qmpv_discard_response(QTestState *s, const char *fmt, va_list ap);
|
||||
|
||||
/**
|
||||
* qtest_qmpv:
|
||||
@ -58,9 +78,9 @@ void qtest_qmp(QTestState *s, const char *fmt, ...);
|
||||
* @fmt: QMP message to send to QEMU
|
||||
* @ap: QMP message arguments
|
||||
*
|
||||
* Sends a QMP message to QEMU.
|
||||
* Sends a QMP message to QEMU and returns the response.
|
||||
*/
|
||||
void qtest_qmpv(QTestState *s, const char *fmt, va_list ap);
|
||||
QDict *qtest_qmpv(QTestState *s, const char *fmt, va_list ap);
|
||||
|
||||
/**
|
||||
* qtest_get_irq:
|
||||
@ -334,14 +354,31 @@ static inline void qtest_end(void)
|
||||
* qmp:
|
||||
* @fmt...: QMP message to send to qemu
|
||||
*
|
||||
* Sends a QMP message to QEMU
|
||||
* Sends a QMP message to QEMU and returns the response.
|
||||
*/
|
||||
static inline void qmp(const char *fmt, ...)
|
||||
static inline QDict *qmp(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
QDict *response;
|
||||
|
||||
va_start(ap, fmt);
|
||||
response = qtest_qmpv(global_qtest, fmt, ap);
|
||||
va_end(ap);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* qmp_discard_response:
|
||||
* @fmt...: QMP message to send to qemu
|
||||
*
|
||||
* Sends a QMP message to QEMU and consumes the response.
|
||||
*/
|
||||
static inline void qmp_discard_response(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
qtest_qmpv(global_qtest, fmt, ap);
|
||||
qtest_qmpv_discard_response(global_qtest, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
|
81
tests/qdev-monitor-test.c
Normal file
81
tests/qdev-monitor-test.c
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* qdev-monitor.c test cases
|
||||
*
|
||||
* Copyright (C) 2013 Red Hat Inc.
|
||||
*
|
||||
* Authors:
|
||||
* Stefan Hajnoczi <stefanha@redhat.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
|
||||
* See the COPYING.LIB file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <glib.h>
|
||||
#include "libqtest.h"
|
||||
#include "qapi/qmp/qjson.h"
|
||||
|
||||
static void test_device_add(void)
|
||||
{
|
||||
QDict *response;
|
||||
QDict *error;
|
||||
|
||||
qtest_start("-drive if=none,id=drive0");
|
||||
|
||||
/* Make device_add fail. If this leaks the virtio-blk-pci device then a
|
||||
* reference to drive0 will also be held (via qdev properties).
|
||||
*/
|
||||
response = qmp("{\"execute\": \"device_add\","
|
||||
" \"arguments\": {"
|
||||
" \"driver\": \"virtio-blk-pci\","
|
||||
" \"drive\": \"drive0\""
|
||||
"}}");
|
||||
g_assert(response);
|
||||
error = qdict_get_qdict(response, "error");
|
||||
g_assert(!strcmp(qdict_get_try_str(error, "class") ?: "",
|
||||
"GenericError"));
|
||||
g_assert(!strcmp(qdict_get_try_str(error, "desc") ?: "",
|
||||
"Device initialization failed."));
|
||||
QDECREF(response);
|
||||
|
||||
/* Delete the drive */
|
||||
response = qmp("{\"execute\": \"human-monitor-command\","
|
||||
" \"arguments\": {"
|
||||
" \"command-line\": \"drive_del drive0\""
|
||||
"}}");
|
||||
g_assert(response);
|
||||
g_assert(!strcmp(qdict_get_try_str(response, "return") ?: "(null)", ""));
|
||||
QDECREF(response);
|
||||
|
||||
/* Try to re-add the drive. This fails with duplicate IDs if a leaked
|
||||
* virtio-blk-pci exists that holds a reference to the old drive0.
|
||||
*/
|
||||
response = qmp("{\"execute\": \"human-monitor-command\","
|
||||
" \"arguments\": {"
|
||||
" \"command-line\": \"drive_add pci-addr=auto if=none,id=drive0\""
|
||||
"}}");
|
||||
g_assert(response);
|
||||
g_assert(!strcmp(qdict_get_try_str(response, "return") ?: "",
|
||||
"OK\r\n"));
|
||||
QDECREF(response);
|
||||
|
||||
qtest_end();
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *arch = qtest_get_arch();
|
||||
|
||||
/* Check architecture */
|
||||
if (strcmp(arch, "i386") && strcmp(arch, "x86_64")) {
|
||||
g_test_message("Skipping test for non-x86\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Run the tests */
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
qtest_add_func("/qmp/device_add", test_device_add);
|
||||
|
||||
return g_test_run();
|
||||
}
|
@ -66,7 +66,7 @@ echo "Creating test image with backing file"
|
||||
echo
|
||||
|
||||
TEST_IMG=$TEST_IMG_SAVE
|
||||
_make_test_img -b $TEST_IMG.base 6G
|
||||
_make_test_img -b "$TEST_IMG.base" 6G
|
||||
|
||||
echo "Filling test image"
|
||||
echo
|
||||
|
@ -90,12 +90,12 @@ mv "$TEST_IMG" "$TEST_IMG.orig"
|
||||
# Test the conversion twice: One test with the old-style -B option and another
|
||||
# one with -o backing_file
|
||||
|
||||
for backing_option in "-B $TEST_IMG.base" "-o backing_file=$TEST_IMG.base"; do
|
||||
for backing_option in "-B " "-o backing_file="; do
|
||||
|
||||
echo
|
||||
echo Testing conversion with $backing_option | _filter_testdir | _filter_imgfmt
|
||||
echo Testing conversion with $backing_option$TEST_IMG.base | _filter_testdir | _filter_imgfmt
|
||||
echo
|
||||
$QEMU_IMG convert -O $IMGFMT $backing_option "$TEST_IMG.orig" "$TEST_IMG"
|
||||
$QEMU_IMG convert -O $IMGFMT $backing_option"$TEST_IMG.base" "$TEST_IMG.orig" "$TEST_IMG"
|
||||
|
||||
echo "Checking if backing clusters are allocated when they shouldn't"
|
||||
echo
|
||||
|
@ -54,7 +54,7 @@ echo "== Checking that image is clean on shutdown =="
|
||||
IMGOPTS="compat=1.1,lazy_refcounts=on"
|
||||
_make_test_img $size
|
||||
|
||||
$QEMU_IO -c "write -P 0x5a 0 512" ""$TEST_IMG"" | _filter_qemu_io
|
||||
$QEMU_IO -c "write -P 0x5a 0 512" "$TEST_IMG" | _filter_qemu_io
|
||||
|
||||
# The dirty bit must not be set
|
||||
./qcow2.py "$TEST_IMG" dump-header | grep incompatible_features
|
||||
|
@ -64,9 +64,9 @@ function run_qemu()
|
||||
size=128M
|
||||
|
||||
_make_test_img $size
|
||||
cp $TEST_IMG $TEST_IMG.orig
|
||||
mv $TEST_IMG $TEST_IMG.base
|
||||
_make_test_img -b $TEST_IMG.base $size
|
||||
cp "$TEST_IMG" "$TEST_IMG.orig"
|
||||
mv "$TEST_IMG" "$TEST_IMG.base"
|
||||
_make_test_img -b "$TEST_IMG.base" $size
|
||||
|
||||
echo
|
||||
echo === Unknown option ===
|
||||
@ -81,7 +81,7 @@ echo
|
||||
echo === Overriding backing file ===
|
||||
echo
|
||||
|
||||
echo "info block" | run_qemu -drive file=$TEST_IMG,driver=qcow2,backing.file.filename=$TEST_IMG.orig -nodefaults
|
||||
echo "info block" | run_qemu -drive file="$TEST_IMG",driver=qcow2,backing.file.filename="$TEST_IMG.orig" -nodefaults
|
||||
|
||||
echo
|
||||
echo === Enable and disable lazy refcounting on the command line, plus some invalid values ===
|
||||
|
@ -163,7 +163,7 @@ echo "=== Testing zero expansion on backed image ==="
|
||||
echo
|
||||
IMGOPTS="compat=1.1" TEST_IMG="$TEST_IMG.base" _make_test_img 64M
|
||||
$QEMU_IO -c "write -P 0x2a 0 128k" "$TEST_IMG.base" | _filter_qemu_io
|
||||
IMGOPTS="compat=1.1,backing_file=$TEST_IMG.base" _make_test_img 64M
|
||||
IMGOPTS="compat=1.1" _make_test_img -b "$TEST_IMG.base" 64M
|
||||
$QEMU_IO -c "read -P 0x2a 0 128k" -c "write -z 0 64k" "$TEST_IMG" | _filter_qemu_io
|
||||
$QEMU_IMG amend -o "compat=0.10" "$TEST_IMG"
|
||||
_check_test_img
|
||||
@ -174,7 +174,7 @@ echo "=== Testing zero expansion on backed inactive clusters ==="
|
||||
echo
|
||||
IMGOPTS="compat=1.1" TEST_IMG="$TEST_IMG.base" _make_test_img 64M
|
||||
$QEMU_IO -c "write -P 0x2a 0 128k" "$TEST_IMG.base" | _filter_qemu_io
|
||||
IMGOPTS="compat=1.1,backing_file=$TEST_IMG.base" _make_test_img 64M
|
||||
IMGOPTS="compat=1.1" _make_test_img -b "$TEST_IMG.base" 64M
|
||||
$QEMU_IO -c "write -z 0 64k" "$TEST_IMG" | _filter_qemu_io
|
||||
$QEMU_IMG snapshot -c foo "$TEST_IMG"
|
||||
$QEMU_IO -c "write -P 0x42 0 128k" "$TEST_IMG" | _filter_qemu_io
|
||||
@ -190,7 +190,7 @@ echo "=== Testing zero expansion on backed image with shared L2 table ==="
|
||||
echo
|
||||
IMGOPTS="compat=1.1" TEST_IMG="$TEST_IMG.base" _make_test_img 64M
|
||||
$QEMU_IO -c "write -P 0x2a 0 128k" "$TEST_IMG.base" | _filter_qemu_io
|
||||
IMGOPTS="compat=1.1,backing_file=$TEST_IMG.base" _make_test_img 64M
|
||||
IMGOPTS="compat=1.1" _make_test_img -b "$TEST_IMG.base" 64M
|
||||
$QEMU_IO -c "write -z 0 128k" "$TEST_IMG" | _filter_qemu_io
|
||||
$QEMU_IMG snapshot -c foo "$TEST_IMG"
|
||||
$QEMU_IMG amend -o "compat=0.10" "$TEST_IMG"
|
||||
|
@ -56,6 +56,17 @@ echo
|
||||
echo "=== Verify pattern 0x00, 66M - 1024M ==="
|
||||
$QEMU_IO -r -c "read -pP 0x00 66M 958M" "$TEST_IMG" | _filter_qemu_io
|
||||
|
||||
echo
|
||||
echo "=== Verify pattern write, 0xc3 99M-157M ==="
|
||||
$QEMU_IO -c "write -pP 0xc3 99M 58M" "$TEST_IMG" | _filter_qemu_io
|
||||
# first verify we didn't write where we should not have
|
||||
$QEMU_IO -c "read -pP 0xa5 0 33M" "$TEST_IMG" | _filter_qemu_io
|
||||
$QEMU_IO -c "read -pP 0x96 33M 33M" "$TEST_IMG" | _filter_qemu_io
|
||||
$QEMU_IO -c "read -pP 0x00 66M 33M" "$TEST_IMG" | _filter_qemu_io
|
||||
$QEMU_IO -c "read -pP 0x00 157MM 867MM" "$TEST_IMG" | _filter_qemu_io
|
||||
# now verify what we should have actually written
|
||||
$QEMU_IO -c "read -pP 0xc3 99M 58M" "$TEST_IMG" | _filter_qemu_io
|
||||
|
||||
# success, all done
|
||||
echo "*** done"
|
||||
rm -f $seq.full
|
||||
|
@ -11,4 +11,18 @@ read 34603008/34603008 bytes at offset 34603008
|
||||
=== Verify pattern 0x00, 66M - 1024M ===
|
||||
read 1004535808/1004535808 bytes at offset 69206016
|
||||
958 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
|
||||
=== Verify pattern write, 0xc3 99M-157M ===
|
||||
wrote 60817408/60817408 bytes at offset 103809024
|
||||
58 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
read 34603008/34603008 bytes at offset 0
|
||||
33 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
read 34603008/34603008 bytes at offset 34603008
|
||||
33 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
read 34603008/34603008 bytes at offset 69206016
|
||||
33 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
read 909115392/909115392 bytes at offset 164626432
|
||||
867 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
read 60817408/60817408 bytes at offset 103809024
|
||||
58 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
*** done
|
||||
|
@ -45,7 +45,7 @@ function do_run_qemu()
|
||||
|
||||
function run_qemu()
|
||||
{
|
||||
do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qmp
|
||||
do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qmp | sed -e 's/\("actual-size":\s*\)[0-9]\+/\1SIZE/g'
|
||||
}
|
||||
|
||||
size=128M
|
||||
|
@ -6,7 +6,7 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728
|
||||
Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,if=none,id=disk -device virtio-blk-pci,drive=disk,id=virtio0
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"return": [{"io-status": "ok", "device": "disk", "locked": false, "removable": false, "inserted": {"iops_rd": 0, "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": 139264, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "type": "unknown"}, {"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}]}
|
||||
{"return": [{"io-status": "ok", "device": "disk", "locked": false, "removable": false, "inserted": {"iops_rd": 0, "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": SIZE, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "type": "unknown"}, {"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}]}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_DELETED", "data": {"path": "/machine/peripheral/virtio0/virtio-backend"}}
|
||||
@ -24,7 +24,7 @@ QMP_VERSION
|
||||
Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,if=none,id=disk
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"return": [{"device": "disk", "locked": false, "removable": true, "inserted": {"iops_rd": 0, "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": 139264, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "tray_open": false, "type": "unknown"}, {"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}]}
|
||||
{"return": [{"device": "disk", "locked": false, "removable": true, "inserted": {"iops_rd": 0, "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": SIZE, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "tray_open": false, "type": "unknown"}, {"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}]}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
@ -44,7 +44,7 @@ Testing:
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"return": "OK\r\n"}
|
||||
{"return": [{"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "disk", "locked": false, "removable": true, "inserted": {"iops_rd": 0, "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": 139264, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "tray_open": false, "type": "unknown"}]}
|
||||
{"return": [{"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "disk", "locked": false, "removable": true, "inserted": {"iops_rd": 0, "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": SIZE, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "tray_open": false, "type": "unknown"}]}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
@ -64,14 +64,14 @@ Testing:
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": [{"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "disk", "locked": false, "removable": true, "inserted": {"iops_rd": 0, "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": 139264, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "tray_open": false, "type": "unknown"}]}
|
||||
{"return": [{"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "disk", "locked": false, "removable": true, "inserted": {"iops_rd": 0, "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": SIZE, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "tray_open": false, "type": "unknown"}]}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_DELETED", "data": {"path": "/machine/peripheral/virtio0/virtio-backend"}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_DELETED", "data": {"device": "virtio0", "path": "/machine/peripheral/virtio0"}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "RESET"}
|
||||
{"return": [{"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"io-status": "ok", "device": "disk", "locked": false, "removable": true, "inserted": {"iops_rd": 0, "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": 139264, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "tray_open": false, "type": "unknown"}]}
|
||||
{"return": [{"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"io-status": "ok", "device": "disk", "locked": false, "removable": true, "inserted": {"iops_rd": 0, "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": SIZE, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "tray_open": false, "type": "unknown"}]}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN"}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_TRAY_MOVED", "data": {"device": "ide1-cd0", "tray-open": true}}
|
||||
|
67
tests/qemu-iotests/070
Executable file
67
tests/qemu-iotests/070
Executable file
@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Test VHDX log replay from an image with a journal that needs to be
|
||||
# replayed
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat, Inc.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# creator
|
||||
owner=jcody@redhat.com
|
||||
|
||||
seq=`basename $0`
|
||||
echo "QA output created by $seq"
|
||||
|
||||
here=`pwd`
|
||||
tmp=/tmp/$$
|
||||
status=1 # failure is the default!
|
||||
|
||||
_cleanup()
|
||||
{
|
||||
_cleanup_test_img
|
||||
}
|
||||
trap "_cleanup; exit \$status" 0 1 2 3 15
|
||||
|
||||
# get standard environment, filters and checks
|
||||
. ./common.rc
|
||||
. ./common.filter
|
||||
|
||||
_supported_fmt vhdx
|
||||
_supported_proto generic
|
||||
_supported_os Linux
|
||||
|
||||
# With the log replayed, the pattern 0xa5 extends to 0xc025000
|
||||
# If the log was not replayed, it would only extend to 0xc000000
|
||||
#
|
||||
# This image is a 10G dynamic image, with 4M block size, and 1 unplayed
|
||||
# data sector in the log
|
||||
#
|
||||
# This image was created with qemu-img, however it was verified using
|
||||
# Hyper-V to properly replay the logs and give the same post-replay
|
||||
# image as qemu.
|
||||
_use_sample_img iotest-dirtylog-10G-4M.vhdx.bz2
|
||||
|
||||
echo
|
||||
echo "=== Verify open image read-only fails, due to dirty log ==="
|
||||
$QEMU_IO -r -c "read -pP 0xa5 0 18M" "$TEST_IMG" 2>&1 | grep -o "Permission denied"
|
||||
|
||||
echo "=== Verify open image replays log ==="
|
||||
$QEMU_IO -c "read -pP 0xa5 0 18M" "$TEST_IMG" | _filter_qemu_io
|
||||
|
||||
# success, all done
|
||||
echo "*** done"
|
||||
rm -f $seq.full
|
||||
status=0
|
8
tests/qemu-iotests/070.out
Normal file
8
tests/qemu-iotests/070.out
Normal file
@ -0,0 +1,8 @@
|
||||
QA output created by 070
|
||||
|
||||
=== Verify open image read-only fails, due to dirty log ===
|
||||
Permission denied
|
||||
=== Verify open image replays log ===
|
||||
read 18874368/18874368 bytes at offset 0
|
||||
18 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
*** done
|
@ -200,7 +200,6 @@ testlist options
|
||||
-vhdx)
|
||||
IMGFMT=vhdx
|
||||
xpand=false
|
||||
IMGFMT_GENERIC=false
|
||||
;;
|
||||
|
||||
-rbd)
|
||||
|
@ -28,7 +28,7 @@ function do_is_allocated() {
|
||||
}
|
||||
|
||||
function is_allocated() {
|
||||
do_is_allocated "$@" | $QEMU_IO $TEST_IMG | _filter_qemu_io
|
||||
do_is_allocated "$@" | $QEMU_IO "$TEST_IMG" | _filter_qemu_io
|
||||
}
|
||||
|
||||
function do_io() {
|
||||
@ -46,18 +46,18 @@ function do_io() {
|
||||
}
|
||||
|
||||
function io_pattern() {
|
||||
do_io "$@" | $QEMU_IO $TEST_IMG | _filter_qemu_io
|
||||
do_io "$@" | $QEMU_IO "$TEST_IMG" | _filter_qemu_io
|
||||
}
|
||||
|
||||
function io() {
|
||||
local start=$2
|
||||
local pattern=$(( (start >> 9) % 256 ))
|
||||
|
||||
do_io "$@" $pattern | $QEMU_IO $TEST_IMG | _filter_qemu_io
|
||||
do_io "$@" $pattern | $QEMU_IO "$TEST_IMG" | _filter_qemu_io
|
||||
}
|
||||
|
||||
function io_zero() {
|
||||
do_io "$@" 0 | $QEMU_IO $TEST_IMG | _filter_qemu_io
|
||||
do_io "$@" 0 | $QEMU_IO "$TEST_IMG" | _filter_qemu_io
|
||||
}
|
||||
|
||||
function io_test() {
|
||||
@ -117,8 +117,8 @@ function io_test2() {
|
||||
echo === Clusters to be compressed [3]
|
||||
io_pattern writev $((offset + 8 * $cluster_size)) $cluster_size $((9 * $cluster_size)) $num 165
|
||||
|
||||
mv $TEST_IMG $TEST_IMG.orig
|
||||
$QEMU_IMG convert -f $IMGFMT -O $IMGFMT -c $TEST_IMG.orig $TEST_IMG
|
||||
mv "$TEST_IMG" "$TEST_IMG.orig"
|
||||
$QEMU_IMG convert -f $IMGFMT -O $IMGFMT -c "$TEST_IMG.orig" "$TEST_IMG"
|
||||
|
||||
# Write the used clusters
|
||||
echo === Used clusters [1]
|
||||
|
@ -111,6 +111,8 @@ _make_test_img()
|
||||
local image_size=$*
|
||||
local optstr=""
|
||||
local img_name=""
|
||||
local use_backing=0
|
||||
local backing_file=""
|
||||
|
||||
if [ -n "$TEST_IMG_FILE" ]; then
|
||||
img_name=$TEST_IMG_FILE
|
||||
@ -123,7 +125,8 @@ _make_test_img()
|
||||
fi
|
||||
|
||||
if [ "$1" = "-b" ]; then
|
||||
extra_img_options="$1 $2"
|
||||
use_backing=1
|
||||
backing_file=$2
|
||||
image_size=$3
|
||||
fi
|
||||
if [ \( "$IMGFMT" = "qcow2" -o "$IMGFMT" = "qed" \) -a -n "$CLUSTER_SIZE" ]; then
|
||||
@ -135,7 +138,13 @@ _make_test_img()
|
||||
fi
|
||||
|
||||
# XXX(hch): have global image options?
|
||||
$QEMU_IMG create -f $IMGFMT $extra_img_options $img_name $image_size 2>&1 | \
|
||||
(
|
||||
if [ $use_backing = 1 ]; then
|
||||
$QEMU_IMG create -f $IMGFMT $extra_img_options -b "$backing_file" "$img_name" $image_size 2>&1
|
||||
else
|
||||
$QEMU_IMG create -f $IMGFMT $extra_img_options "$img_name" $image_size 2>&1
|
||||
fi
|
||||
) | \
|
||||
sed -e "s#$IMGPROTO:$TEST_DIR#TEST_DIR#g" \
|
||||
-e "s#$TEST_DIR#TEST_DIR#g" \
|
||||
-e "s#$IMGFMT#IMGFMT#g" \
|
||||
@ -148,7 +157,10 @@ _make_test_img()
|
||||
-e "s# zeroed_grain=\\(on\\|off\\)##g" \
|
||||
-e "s# subformat='[^']*'##g" \
|
||||
-e "s# adapter_type='[^']*'##g" \
|
||||
-e "s# lazy_refcounts=\\(on\\|off\\)##g"
|
||||
-e "s# lazy_refcounts=\\(on\\|off\\)##g" \
|
||||
-e "s# block_size=[0-9]\\+##g" \
|
||||
-e "s# block_state_zero=\\(on\\|off\\)##g" \
|
||||
-e "s# log_size=[0-9]\\+##g"
|
||||
|
||||
# Start an NBD server on the image file, which is what we'll be talking to
|
||||
if [ $IMGPROTO = "nbd" ]; then
|
||||
|
@ -75,3 +75,4 @@
|
||||
067 rw auto
|
||||
068 rw auto
|
||||
069 rw auto
|
||||
070 rw auto
|
||||
|
BIN
tests/qemu-iotests/sample_images/iotest-dirtylog-10G-4M.vhdx.bz2
Normal file
BIN
tests/qemu-iotests/sample_images/iotest-dirtylog-10G-4M.vhdx.bz2
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user