mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-07 01:33:56 -06:00
Merge remote-tracking branch 'stefanha/block' into staging
# By Kevin Wolf (22) and Fam Zheng (1) # Via Stefan Hajnoczi * stefanha/block: (23 commits) vmdk: refuse to open higher version than supported block: Always enable discard on the protocol level qcow2: Batch discards qcow2: Options to enable discard for freed clusters qcow2: Add refcount update reason to all callers Revert "block: Disable driver-specific options for 1.5" ide: Clean up ide_exec_cmd() ide: Convert SMART commands to ide_cmd_table handler ide: Convert CF-ATA commands to ide_cmd_table handler ide: Convert ATAPI commands to ide_cmd_table handler ide: Convert SEEK to ide_cmd_table handler ide: Convert FLUSH CACHE to ide_cmd_table handler ide: Convert SET FEATURES to ide_cmd_table handler ide: Convert CHECK POWER MDOE to ide_cmd_table handler ide: Convert READ NATIVE MAX ADDRESS to ide_cmd_table handler ide: Convert DMA read/write commands to ide_cmd_table handler ide: Convert PIO read/write commands to ide_cmd_table handler ide: Convert read/write multiple commands to ide_cmd_table handler ide: Convert verify commands to ide_cmd_table handler ide: Convert cmd_nop commands to ide_cmd_table handler ... Message-id: 1372065035-19601-1-git-send-email-stefanha@redhat.com Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
This commit is contained in:
commit
baf8673ca8
10 changed files with 912 additions and 746 deletions
2
block.c
2
block.c
|
@ -1045,7 +1045,7 @@ int bdrv_open(BlockDriverState *bs, const char *filename, QDict *options,
|
||||||
extract_subqdict(options, &file_options, "file.");
|
extract_subqdict(options, &file_options, "file.");
|
||||||
|
|
||||||
ret = bdrv_file_open(&file, filename, file_options,
|
ret = bdrv_file_open(&file, filename, file_options,
|
||||||
bdrv_open_flags(bs, flags));
|
bdrv_open_flags(bs, flags | BDRV_O_UNMAP));
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,14 +98,16 @@ int qcow2_grow_l1_table(BlockDriverState *bs, uint64_t min_size,
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
g_free(s->l1_table);
|
g_free(s->l1_table);
|
||||||
qcow2_free_clusters(bs, s->l1_table_offset, s->l1_size * sizeof(uint64_t));
|
qcow2_free_clusters(bs, s->l1_table_offset, s->l1_size * sizeof(uint64_t),
|
||||||
|
QCOW2_DISCARD_OTHER);
|
||||||
s->l1_table_offset = new_l1_table_offset;
|
s->l1_table_offset = new_l1_table_offset;
|
||||||
s->l1_table = new_l1_table;
|
s->l1_table = new_l1_table;
|
||||||
s->l1_size = new_l1_size;
|
s->l1_size = new_l1_size;
|
||||||
return 0;
|
return 0;
|
||||||
fail:
|
fail:
|
||||||
g_free(new_l1_table);
|
g_free(new_l1_table);
|
||||||
qcow2_free_clusters(bs, new_l1_table_offset, new_l1_size2);
|
qcow2_free_clusters(bs, new_l1_table_offset, new_l1_size2,
|
||||||
|
QCOW2_DISCARD_OTHER);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -548,7 +550,8 @@ static int get_cluster_table(BlockDriverState *bs, uint64_t offset,
|
||||||
|
|
||||||
/* Then decrease the refcount of the old table */
|
/* Then decrease the refcount of the old table */
|
||||||
if (l2_offset) {
|
if (l2_offset) {
|
||||||
qcow2_free_clusters(bs, l2_offset, s->l2_size * sizeof(uint64_t));
|
qcow2_free_clusters(bs, l2_offset, s->l2_size * sizeof(uint64_t),
|
||||||
|
QCOW2_DISCARD_OTHER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -715,10 +718,14 @@ int qcow2_alloc_cluster_link_l2(BlockDriverState *bs, QCowL2Meta *m)
|
||||||
/*
|
/*
|
||||||
* If this was a COW, we need to decrease the refcount of the old cluster.
|
* If this was a COW, we need to decrease the refcount of the old cluster.
|
||||||
* Also flush bs->file to get the right order for L2 and refcount update.
|
* Also flush bs->file to get the right order for L2 and refcount update.
|
||||||
|
*
|
||||||
|
* Don't discard clusters that reach a refcount of 0 (e.g. compressed
|
||||||
|
* clusters), the next write will reuse them anyway.
|
||||||
*/
|
*/
|
||||||
if (j != 0) {
|
if (j != 0) {
|
||||||
for (i = 0; i < j; i++) {
|
for (i = 0; i < j; i++) {
|
||||||
qcow2_free_any_clusters(bs, be64_to_cpu(old_cluster[i]), 1);
|
qcow2_free_any_clusters(bs, be64_to_cpu(old_cluster[i]), 1,
|
||||||
|
QCOW2_DISCARD_NEVER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1339,7 +1346,7 @@ static int discard_single_l2(BlockDriverState *bs, uint64_t offset,
|
||||||
l2_table[l2_index + i] = cpu_to_be64(0);
|
l2_table[l2_index + i] = cpu_to_be64(0);
|
||||||
|
|
||||||
/* Then decrease the refcount */
|
/* Then decrease the refcount */
|
||||||
qcow2_free_any_clusters(bs, old_offset, 1);
|
qcow2_free_any_clusters(bs, old_offset, 1, QCOW2_DISCARD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table);
|
ret = qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table);
|
||||||
|
@ -1370,18 +1377,25 @@ int qcow2_discard_clusters(BlockDriverState *bs, uint64_t offset,
|
||||||
|
|
||||||
nb_clusters = size_to_clusters(s, end_offset - offset);
|
nb_clusters = size_to_clusters(s, end_offset - offset);
|
||||||
|
|
||||||
|
s->cache_discards = true;
|
||||||
|
|
||||||
/* Each L2 table is handled by its own loop iteration */
|
/* Each L2 table is handled by its own loop iteration */
|
||||||
while (nb_clusters > 0) {
|
while (nb_clusters > 0) {
|
||||||
ret = discard_single_l2(bs, offset, nb_clusters);
|
ret = discard_single_l2(bs, offset, nb_clusters);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return ret;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
nb_clusters -= ret;
|
nb_clusters -= ret;
|
||||||
offset += (ret * s->cluster_size);
|
offset += (ret * s->cluster_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
ret = 0;
|
||||||
|
fail:
|
||||||
|
s->cache_discards = false;
|
||||||
|
qcow2_process_discards(bs, ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1415,7 +1429,7 @@ static int zero_single_l2(BlockDriverState *bs, uint64_t offset,
|
||||||
qcow2_cache_entry_mark_dirty(s->l2_table_cache, l2_table);
|
qcow2_cache_entry_mark_dirty(s->l2_table_cache, l2_table);
|
||||||
if (old_offset & QCOW_OFLAG_COMPRESSED) {
|
if (old_offset & QCOW_OFLAG_COMPRESSED) {
|
||||||
l2_table[l2_index + i] = cpu_to_be64(QCOW_OFLAG_ZERO);
|
l2_table[l2_index + i] = cpu_to_be64(QCOW_OFLAG_ZERO);
|
||||||
qcow2_free_any_clusters(bs, old_offset, 1);
|
qcow2_free_any_clusters(bs, old_offset, 1, QCOW2_DISCARD_REQUEST);
|
||||||
} else {
|
} else {
|
||||||
l2_table[l2_index + i] |= cpu_to_be64(QCOW_OFLAG_ZERO);
|
l2_table[l2_index + i] |= cpu_to_be64(QCOW_OFLAG_ZERO);
|
||||||
}
|
}
|
||||||
|
@ -1443,15 +1457,22 @@ int qcow2_zero_clusters(BlockDriverState *bs, uint64_t offset, int nb_sectors)
|
||||||
/* Each L2 table is handled by its own loop iteration */
|
/* Each L2 table is handled by its own loop iteration */
|
||||||
nb_clusters = size_to_clusters(s, nb_sectors << BDRV_SECTOR_BITS);
|
nb_clusters = size_to_clusters(s, nb_sectors << BDRV_SECTOR_BITS);
|
||||||
|
|
||||||
|
s->cache_discards = true;
|
||||||
|
|
||||||
while (nb_clusters > 0) {
|
while (nb_clusters > 0) {
|
||||||
ret = zero_single_l2(bs, offset, nb_clusters);
|
ret = zero_single_l2(bs, offset, nb_clusters);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return ret;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
nb_clusters -= ret;
|
nb_clusters -= ret;
|
||||||
offset += (ret * s->cluster_size);
|
offset += (ret * s->cluster_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
ret = 0;
|
||||||
|
fail:
|
||||||
|
s->cache_discards = false;
|
||||||
|
qcow2_process_discards(bs, ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
static int64_t alloc_clusters_noref(BlockDriverState *bs, int64_t size);
|
static int64_t alloc_clusters_noref(BlockDriverState *bs, int64_t size);
|
||||||
static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
|
static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
|
||||||
int64_t offset, int64_t length,
|
int64_t offset, int64_t length,
|
||||||
int addend);
|
int addend, enum qcow2_discard_type type);
|
||||||
|
|
||||||
|
|
||||||
/*********************************************************/
|
/*********************************************************/
|
||||||
|
@ -235,7 +235,8 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||||
} else {
|
} else {
|
||||||
/* Described somewhere else. This can recurse at most twice before we
|
/* Described somewhere else. This can recurse at most twice before we
|
||||||
* arrive at a block that describes itself. */
|
* arrive at a block that describes itself. */
|
||||||
ret = update_refcount(bs, new_block, s->cluster_size, 1);
|
ret = update_refcount(bs, new_block, s->cluster_size, 1,
|
||||||
|
QCOW2_DISCARD_NEVER);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail_block;
|
goto fail_block;
|
||||||
}
|
}
|
||||||
|
@ -399,7 +400,8 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||||
|
|
||||||
/* Free old table. Remember, we must not change free_cluster_index */
|
/* Free old table. Remember, we must not change free_cluster_index */
|
||||||
uint64_t old_free_cluster_index = s->free_cluster_index;
|
uint64_t old_free_cluster_index = s->free_cluster_index;
|
||||||
qcow2_free_clusters(bs, old_table_offset, old_table_size * sizeof(uint64_t));
|
qcow2_free_clusters(bs, old_table_offset, old_table_size * sizeof(uint64_t),
|
||||||
|
QCOW2_DISCARD_OTHER);
|
||||||
s->free_cluster_index = old_free_cluster_index;
|
s->free_cluster_index = old_free_cluster_index;
|
||||||
|
|
||||||
ret = load_refcount_block(bs, new_block, (void**) refcount_block);
|
ret = load_refcount_block(bs, new_block, (void**) refcount_block);
|
||||||
|
@ -418,9 +420,77 @@ fail_block:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void qcow2_process_discards(BlockDriverState *bs, int ret)
|
||||||
|
{
|
||||||
|
BDRVQcowState *s = bs->opaque;
|
||||||
|
Qcow2DiscardRegion *d, *next;
|
||||||
|
|
||||||
|
QTAILQ_FOREACH_SAFE(d, &s->discards, next, next) {
|
||||||
|
QTAILQ_REMOVE(&s->discards, d, next);
|
||||||
|
|
||||||
|
/* Discard is optional, ignore the return value */
|
||||||
|
if (ret >= 0) {
|
||||||
|
bdrv_discard(bs->file,
|
||||||
|
d->offset >> BDRV_SECTOR_BITS,
|
||||||
|
d->bytes >> BDRV_SECTOR_BITS);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_refcount_discard(BlockDriverState *bs,
|
||||||
|
uint64_t offset, uint64_t length)
|
||||||
|
{
|
||||||
|
BDRVQcowState *s = bs->opaque;
|
||||||
|
Qcow2DiscardRegion *d, *p, *next;
|
||||||
|
|
||||||
|
QTAILQ_FOREACH(d, &s->discards, next) {
|
||||||
|
uint64_t new_start = MIN(offset, d->offset);
|
||||||
|
uint64_t new_end = MAX(offset + length, d->offset + d->bytes);
|
||||||
|
|
||||||
|
if (new_end - new_start <= length + d->bytes) {
|
||||||
|
/* There can't be any overlap, areas ending up here have no
|
||||||
|
* references any more and therefore shouldn't get freed another
|
||||||
|
* time. */
|
||||||
|
assert(d->bytes + length == new_end - new_start);
|
||||||
|
d->offset = new_start;
|
||||||
|
d->bytes = new_end - new_start;
|
||||||
|
goto found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d = g_malloc(sizeof(*d));
|
||||||
|
*d = (Qcow2DiscardRegion) {
|
||||||
|
.bs = bs,
|
||||||
|
.offset = offset,
|
||||||
|
.bytes = length,
|
||||||
|
};
|
||||||
|
QTAILQ_INSERT_TAIL(&s->discards, d, next);
|
||||||
|
|
||||||
|
found:
|
||||||
|
/* Merge discard requests if they are adjacent now */
|
||||||
|
QTAILQ_FOREACH_SAFE(p, &s->discards, next, next) {
|
||||||
|
if (p == d
|
||||||
|
|| p->offset > d->offset + d->bytes
|
||||||
|
|| d->offset > p->offset + p->bytes)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Still no overlap possible */
|
||||||
|
assert(p->offset == d->offset + d->bytes
|
||||||
|
|| d->offset == p->offset + p->bytes);
|
||||||
|
|
||||||
|
QTAILQ_REMOVE(&s->discards, p, next);
|
||||||
|
d->offset = MIN(d->offset, p->offset);
|
||||||
|
d->bytes += p->bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* XXX: cache several refcount block clusters ? */
|
/* XXX: cache several refcount block clusters ? */
|
||||||
static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
|
static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
|
||||||
int64_t offset, int64_t length, int addend)
|
int64_t offset, int64_t length, int addend, enum qcow2_discard_type type)
|
||||||
{
|
{
|
||||||
BDRVQcowState *s = bs->opaque;
|
BDRVQcowState *s = bs->opaque;
|
||||||
int64_t start, last, cluster_offset;
|
int64_t start, last, cluster_offset;
|
||||||
|
@ -486,10 +556,18 @@ static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
|
||||||
s->free_cluster_index = cluster_index;
|
s->free_cluster_index = cluster_index;
|
||||||
}
|
}
|
||||||
refcount_block[block_index] = cpu_to_be16(refcount);
|
refcount_block[block_index] = cpu_to_be16(refcount);
|
||||||
|
|
||||||
|
if (refcount == 0 && s->discard_passthrough[type]) {
|
||||||
|
update_refcount_discard(bs, cluster_offset, s->cluster_size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = 0;
|
ret = 0;
|
||||||
fail:
|
fail:
|
||||||
|
if (!s->cache_discards) {
|
||||||
|
qcow2_process_discards(bs, ret);
|
||||||
|
}
|
||||||
|
|
||||||
/* Write last changed block to disk */
|
/* Write last changed block to disk */
|
||||||
if (refcount_block) {
|
if (refcount_block) {
|
||||||
int wret;
|
int wret;
|
||||||
|
@ -506,7 +584,8 @@ fail:
|
||||||
*/
|
*/
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
int dummy;
|
int dummy;
|
||||||
dummy = update_refcount(bs, offset, cluster_offset - offset, -addend);
|
dummy = update_refcount(bs, offset, cluster_offset - offset, -addend,
|
||||||
|
QCOW2_DISCARD_NEVER);
|
||||||
(void)dummy;
|
(void)dummy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -522,12 +601,14 @@ fail:
|
||||||
*/
|
*/
|
||||||
static int update_cluster_refcount(BlockDriverState *bs,
|
static int update_cluster_refcount(BlockDriverState *bs,
|
||||||
int64_t cluster_index,
|
int64_t cluster_index,
|
||||||
int addend)
|
int addend,
|
||||||
|
enum qcow2_discard_type type)
|
||||||
{
|
{
|
||||||
BDRVQcowState *s = bs->opaque;
|
BDRVQcowState *s = bs->opaque;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = update_refcount(bs, cluster_index << s->cluster_bits, 1, addend);
|
ret = update_refcount(bs, cluster_index << s->cluster_bits, 1, addend,
|
||||||
|
type);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -579,7 +660,7 @@ int64_t qcow2_alloc_clusters(BlockDriverState *bs, int64_t size)
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = update_refcount(bs, offset, size, 1);
|
ret = update_refcount(bs, offset, size, 1, QCOW2_DISCARD_NEVER);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -611,7 +692,8 @@ int qcow2_alloc_clusters_at(BlockDriverState *bs, uint64_t offset,
|
||||||
old_free_cluster_index = s->free_cluster_index;
|
old_free_cluster_index = s->free_cluster_index;
|
||||||
s->free_cluster_index = cluster_index + i;
|
s->free_cluster_index = cluster_index + i;
|
||||||
|
|
||||||
ret = update_refcount(bs, offset, i << s->cluster_bits, 1);
|
ret = update_refcount(bs, offset, i << s->cluster_bits, 1,
|
||||||
|
QCOW2_DISCARD_NEVER);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -649,7 +731,8 @@ int64_t qcow2_alloc_bytes(BlockDriverState *bs, int size)
|
||||||
if (free_in_cluster == 0)
|
if (free_in_cluster == 0)
|
||||||
s->free_byte_offset = 0;
|
s->free_byte_offset = 0;
|
||||||
if ((offset & (s->cluster_size - 1)) != 0)
|
if ((offset & (s->cluster_size - 1)) != 0)
|
||||||
update_cluster_refcount(bs, offset >> s->cluster_bits, 1);
|
update_cluster_refcount(bs, offset >> s->cluster_bits, 1,
|
||||||
|
QCOW2_DISCARD_NEVER);
|
||||||
} else {
|
} else {
|
||||||
offset = qcow2_alloc_clusters(bs, s->cluster_size);
|
offset = qcow2_alloc_clusters(bs, s->cluster_size);
|
||||||
if (offset < 0) {
|
if (offset < 0) {
|
||||||
|
@ -659,7 +742,8 @@ int64_t qcow2_alloc_bytes(BlockDriverState *bs, int size)
|
||||||
if ((cluster_offset + s->cluster_size) == offset) {
|
if ((cluster_offset + s->cluster_size) == offset) {
|
||||||
/* we are lucky: contiguous data */
|
/* we are lucky: contiguous data */
|
||||||
offset = s->free_byte_offset;
|
offset = s->free_byte_offset;
|
||||||
update_cluster_refcount(bs, offset >> s->cluster_bits, 1);
|
update_cluster_refcount(bs, offset >> s->cluster_bits, 1,
|
||||||
|
QCOW2_DISCARD_NEVER);
|
||||||
s->free_byte_offset += size;
|
s->free_byte_offset += size;
|
||||||
} else {
|
} else {
|
||||||
s->free_byte_offset = offset;
|
s->free_byte_offset = offset;
|
||||||
|
@ -676,12 +760,13 @@ int64_t qcow2_alloc_bytes(BlockDriverState *bs, int size)
|
||||||
}
|
}
|
||||||
|
|
||||||
void qcow2_free_clusters(BlockDriverState *bs,
|
void qcow2_free_clusters(BlockDriverState *bs,
|
||||||
int64_t offset, int64_t size)
|
int64_t offset, int64_t size,
|
||||||
|
enum qcow2_discard_type type)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
BLKDBG_EVENT(bs->file, BLKDBG_CLUSTER_FREE);
|
BLKDBG_EVENT(bs->file, BLKDBG_CLUSTER_FREE);
|
||||||
ret = update_refcount(bs, offset, size, -1);
|
ret = update_refcount(bs, offset, size, -1, type);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
fprintf(stderr, "qcow2_free_clusters failed: %s\n", strerror(-ret));
|
fprintf(stderr, "qcow2_free_clusters failed: %s\n", strerror(-ret));
|
||||||
/* TODO Remember the clusters to free them later and avoid leaking */
|
/* TODO Remember the clusters to free them later and avoid leaking */
|
||||||
|
@ -692,8 +777,8 @@ void qcow2_free_clusters(BlockDriverState *bs,
|
||||||
* Free a cluster using its L2 entry (handles clusters of all types, e.g.
|
* Free a cluster using its L2 entry (handles clusters of all types, e.g.
|
||||||
* normal cluster, compressed cluster, etc.)
|
* normal cluster, compressed cluster, etc.)
|
||||||
*/
|
*/
|
||||||
void qcow2_free_any_clusters(BlockDriverState *bs,
|
void qcow2_free_any_clusters(BlockDriverState *bs, uint64_t l2_entry,
|
||||||
uint64_t l2_entry, int nb_clusters)
|
int nb_clusters, enum qcow2_discard_type type)
|
||||||
{
|
{
|
||||||
BDRVQcowState *s = bs->opaque;
|
BDRVQcowState *s = bs->opaque;
|
||||||
|
|
||||||
|
@ -705,12 +790,12 @@ void qcow2_free_any_clusters(BlockDriverState *bs,
|
||||||
s->csize_mask) + 1;
|
s->csize_mask) + 1;
|
||||||
qcow2_free_clusters(bs,
|
qcow2_free_clusters(bs,
|
||||||
(l2_entry & s->cluster_offset_mask) & ~511,
|
(l2_entry & s->cluster_offset_mask) & ~511,
|
||||||
nb_csectors * 512);
|
nb_csectors * 512, type);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case QCOW2_CLUSTER_NORMAL:
|
case QCOW2_CLUSTER_NORMAL:
|
||||||
qcow2_free_clusters(bs, l2_entry & L2E_OFFSET_MASK,
|
qcow2_free_clusters(bs, l2_entry & L2E_OFFSET_MASK,
|
||||||
nb_clusters << s->cluster_bits);
|
nb_clusters << s->cluster_bits, type);
|
||||||
break;
|
break;
|
||||||
case QCOW2_CLUSTER_UNALLOCATED:
|
case QCOW2_CLUSTER_UNALLOCATED:
|
||||||
case QCOW2_CLUSTER_ZERO:
|
case QCOW2_CLUSTER_ZERO:
|
||||||
|
@ -741,6 +826,8 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
||||||
l1_table = NULL;
|
l1_table = NULL;
|
||||||
l1_size2 = l1_size * sizeof(uint64_t);
|
l1_size2 = l1_size * sizeof(uint64_t);
|
||||||
|
|
||||||
|
s->cache_discards = true;
|
||||||
|
|
||||||
/* WARNING: qcow2_snapshot_goto relies on this function not using the
|
/* WARNING: qcow2_snapshot_goto relies on this function not using the
|
||||||
* l1_table_offset when it is the current s->l1_table_offset! Be careful
|
* l1_table_offset when it is the current s->l1_table_offset! Be careful
|
||||||
* when changing this! */
|
* when changing this! */
|
||||||
|
@ -785,7 +872,8 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
||||||
int ret;
|
int ret;
|
||||||
ret = update_refcount(bs,
|
ret = update_refcount(bs,
|
||||||
(offset & s->cluster_offset_mask) & ~511,
|
(offset & s->cluster_offset_mask) & ~511,
|
||||||
nb_csectors * 512, addend);
|
nb_csectors * 512, addend,
|
||||||
|
QCOW2_DISCARD_SNAPSHOT);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
@ -795,7 +883,8 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
||||||
} else {
|
} else {
|
||||||
uint64_t cluster_index = (offset & L2E_OFFSET_MASK) >> s->cluster_bits;
|
uint64_t cluster_index = (offset & L2E_OFFSET_MASK) >> s->cluster_bits;
|
||||||
if (addend != 0) {
|
if (addend != 0) {
|
||||||
refcount = update_cluster_refcount(bs, cluster_index, addend);
|
refcount = update_cluster_refcount(bs, cluster_index, addend,
|
||||||
|
QCOW2_DISCARD_SNAPSHOT);
|
||||||
} else {
|
} else {
|
||||||
refcount = get_refcount(bs, cluster_index);
|
refcount = get_refcount(bs, cluster_index);
|
||||||
}
|
}
|
||||||
|
@ -827,7 +916,8 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
||||||
|
|
||||||
|
|
||||||
if (addend != 0) {
|
if (addend != 0) {
|
||||||
refcount = update_cluster_refcount(bs, l2_offset >> s->cluster_bits, addend);
|
refcount = update_cluster_refcount(bs, l2_offset >> s->cluster_bits, addend,
|
||||||
|
QCOW2_DISCARD_SNAPSHOT);
|
||||||
} else {
|
} else {
|
||||||
refcount = get_refcount(bs, l2_offset >> s->cluster_bits);
|
refcount = get_refcount(bs, l2_offset >> s->cluster_bits);
|
||||||
}
|
}
|
||||||
|
@ -850,6 +940,9 @@ fail:
|
||||||
qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table);
|
qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s->cache_discards = false;
|
||||||
|
qcow2_process_discards(bs, ret);
|
||||||
|
|
||||||
/* Update L1 only if it isn't deleted anyway (addend = -1) */
|
/* Update L1 only if it isn't deleted anyway (addend = -1) */
|
||||||
if (ret == 0 && addend >= 0 && l1_modified) {
|
if (ret == 0 && addend >= 0 && l1_modified) {
|
||||||
for (i = 0; i < l1_size; i++) {
|
for (i = 0; i < l1_size; i++) {
|
||||||
|
@ -1253,7 +1346,8 @@ int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
||||||
|
|
||||||
if (num_fixed) {
|
if (num_fixed) {
|
||||||
ret = update_refcount(bs, i << s->cluster_bits, 1,
|
ret = update_refcount(bs, i << s->cluster_bits, 1,
|
||||||
refcount2 - refcount1);
|
refcount2 - refcount1,
|
||||||
|
QCOW2_DISCARD_ALWAYS);
|
||||||
if (ret >= 0) {
|
if (ret >= 0) {
|
||||||
(*num_fixed)++;
|
(*num_fixed)++;
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -262,7 +262,8 @@ static int qcow2_write_snapshots(BlockDriverState *bs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* free the old snapshot table */
|
/* free the old snapshot table */
|
||||||
qcow2_free_clusters(bs, s->snapshots_offset, s->snapshots_size);
|
qcow2_free_clusters(bs, s->snapshots_offset, s->snapshots_size,
|
||||||
|
QCOW2_DISCARD_SNAPSHOT);
|
||||||
s->snapshots_offset = snapshots_offset;
|
s->snapshots_offset = snapshots_offset;
|
||||||
s->snapshots_size = snapshots_size;
|
s->snapshots_size = snapshots_size;
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -569,7 +570,8 @@ int qcow2_snapshot_delete(BlockDriverState *bs, const char *snapshot_id)
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
qcow2_free_clusters(bs, sn.l1_table_offset, sn.l1_size * sizeof(uint64_t));
|
qcow2_free_clusters(bs, sn.l1_table_offset, sn.l1_size * sizeof(uint64_t),
|
||||||
|
QCOW2_DISCARD_SNAPSHOT);
|
||||||
|
|
||||||
/* must update the copied flag on the current cluster offsets */
|
/* must update the copied flag on the current cluster offsets */
|
||||||
ret = qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, 0);
|
ret = qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, 0);
|
||||||
|
|
|
@ -295,6 +295,22 @@ static QemuOptsList qcow2_runtime_opts = {
|
||||||
.type = QEMU_OPT_BOOL,
|
.type = QEMU_OPT_BOOL,
|
||||||
.help = "Postpone refcount updates",
|
.help = "Postpone refcount updates",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.name = QCOW2_OPT_DISCARD_REQUEST,
|
||||||
|
.type = QEMU_OPT_BOOL,
|
||||||
|
.help = "Pass guest discard requests to the layer below",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = QCOW2_OPT_DISCARD_SNAPSHOT,
|
||||||
|
.type = QEMU_OPT_BOOL,
|
||||||
|
.help = "Generate discard requests when snapshot related space "
|
||||||
|
"is freed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = QCOW2_OPT_DISCARD_OTHER,
|
||||||
|
.type = QEMU_OPT_BOOL,
|
||||||
|
.help = "Generate discard requests when other clusters are freed",
|
||||||
|
},
|
||||||
{ /* end of list */ }
|
{ /* end of list */ }
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -470,6 +486,7 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
QLIST_INIT(&s->cluster_allocs);
|
QLIST_INIT(&s->cluster_allocs);
|
||||||
|
QTAILQ_INIT(&s->discards);
|
||||||
|
|
||||||
/* read qcow2 extensions */
|
/* read qcow2 extensions */
|
||||||
if (qcow2_read_extensions(bs, header.header_length, ext_end, NULL)) {
|
if (qcow2_read_extensions(bs, header.header_length, ext_end, NULL)) {
|
||||||
|
@ -532,6 +549,16 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags)
|
||||||
s->use_lazy_refcounts = qemu_opt_get_bool(opts, QCOW2_OPT_LAZY_REFCOUNTS,
|
s->use_lazy_refcounts = qemu_opt_get_bool(opts, QCOW2_OPT_LAZY_REFCOUNTS,
|
||||||
(s->compatible_features & QCOW2_COMPAT_LAZY_REFCOUNTS));
|
(s->compatible_features & QCOW2_COMPAT_LAZY_REFCOUNTS));
|
||||||
|
|
||||||
|
s->discard_passthrough[QCOW2_DISCARD_NEVER] = false;
|
||||||
|
s->discard_passthrough[QCOW2_DISCARD_ALWAYS] = true;
|
||||||
|
s->discard_passthrough[QCOW2_DISCARD_REQUEST] =
|
||||||
|
qemu_opt_get_bool(opts, QCOW2_OPT_DISCARD_REQUEST,
|
||||||
|
flags & BDRV_O_UNMAP);
|
||||||
|
s->discard_passthrough[QCOW2_DISCARD_SNAPSHOT] =
|
||||||
|
qemu_opt_get_bool(opts, QCOW2_OPT_DISCARD_SNAPSHOT, true);
|
||||||
|
s->discard_passthrough[QCOW2_DISCARD_OTHER] =
|
||||||
|
qemu_opt_get_bool(opts, QCOW2_OPT_DISCARD_OTHER, false);
|
||||||
|
|
||||||
qemu_opts_del(opts);
|
qemu_opts_del(opts);
|
||||||
|
|
||||||
if (s->use_lazy_refcounts && s->qcow_version < 3) {
|
if (s->use_lazy_refcounts && s->qcow_version < 3) {
|
||||||
|
@ -1196,7 +1223,8 @@ static int preallocate(BlockDriverState *bs)
|
||||||
|
|
||||||
ret = qcow2_alloc_cluster_link_l2(bs, meta);
|
ret = qcow2_alloc_cluster_link_l2(bs, meta);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
qcow2_free_any_clusters(bs, meta->alloc_offset, meta->nb_clusters);
|
qcow2_free_any_clusters(bs, meta->alloc_offset, meta->nb_clusters,
|
||||||
|
QCOW2_DISCARD_NEVER);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,9 @@
|
||||||
|
|
||||||
|
|
||||||
#define QCOW2_OPT_LAZY_REFCOUNTS "lazy_refcounts"
|
#define QCOW2_OPT_LAZY_REFCOUNTS "lazy_refcounts"
|
||||||
|
#define QCOW2_OPT_DISCARD_REQUEST "pass_discard_request"
|
||||||
|
#define QCOW2_OPT_DISCARD_SNAPSHOT "pass_discard_snapshot"
|
||||||
|
#define QCOW2_OPT_DISCARD_OTHER "pass_discard_other"
|
||||||
|
|
||||||
typedef struct QCowHeader {
|
typedef struct QCowHeader {
|
||||||
uint32_t magic;
|
uint32_t magic;
|
||||||
|
@ -129,12 +132,28 @@ enum {
|
||||||
QCOW2_COMPAT_FEAT_MASK = QCOW2_COMPAT_LAZY_REFCOUNTS,
|
QCOW2_COMPAT_FEAT_MASK = QCOW2_COMPAT_LAZY_REFCOUNTS,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum qcow2_discard_type {
|
||||||
|
QCOW2_DISCARD_NEVER = 0,
|
||||||
|
QCOW2_DISCARD_ALWAYS,
|
||||||
|
QCOW2_DISCARD_REQUEST,
|
||||||
|
QCOW2_DISCARD_SNAPSHOT,
|
||||||
|
QCOW2_DISCARD_OTHER,
|
||||||
|
QCOW2_DISCARD_MAX
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct Qcow2Feature {
|
typedef struct Qcow2Feature {
|
||||||
uint8_t type;
|
uint8_t type;
|
||||||
uint8_t bit;
|
uint8_t bit;
|
||||||
char name[46];
|
char name[46];
|
||||||
} QEMU_PACKED Qcow2Feature;
|
} QEMU_PACKED Qcow2Feature;
|
||||||
|
|
||||||
|
typedef struct Qcow2DiscardRegion {
|
||||||
|
BlockDriverState *bs;
|
||||||
|
uint64_t offset;
|
||||||
|
uint64_t bytes;
|
||||||
|
QTAILQ_ENTRY(Qcow2DiscardRegion) next;
|
||||||
|
} Qcow2DiscardRegion;
|
||||||
|
|
||||||
typedef struct BDRVQcowState {
|
typedef struct BDRVQcowState {
|
||||||
int cluster_bits;
|
int cluster_bits;
|
||||||
int cluster_size;
|
int cluster_size;
|
||||||
|
@ -178,6 +197,8 @@ typedef struct BDRVQcowState {
|
||||||
int qcow_version;
|
int qcow_version;
|
||||||
bool use_lazy_refcounts;
|
bool use_lazy_refcounts;
|
||||||
|
|
||||||
|
bool discard_passthrough[QCOW2_DISCARD_MAX];
|
||||||
|
|
||||||
uint64_t incompatible_features;
|
uint64_t incompatible_features;
|
||||||
uint64_t compatible_features;
|
uint64_t compatible_features;
|
||||||
uint64_t autoclear_features;
|
uint64_t autoclear_features;
|
||||||
|
@ -185,6 +206,8 @@ typedef struct BDRVQcowState {
|
||||||
size_t unknown_header_fields_size;
|
size_t unknown_header_fields_size;
|
||||||
void* unknown_header_fields;
|
void* unknown_header_fields;
|
||||||
QLIST_HEAD(, Qcow2UnknownHeaderExtension) unknown_header_ext;
|
QLIST_HEAD(, Qcow2UnknownHeaderExtension) unknown_header_ext;
|
||||||
|
QTAILQ_HEAD (, Qcow2DiscardRegion) discards;
|
||||||
|
bool cache_discards;
|
||||||
} BDRVQcowState;
|
} BDRVQcowState;
|
||||||
|
|
||||||
/* XXX: use std qcow open function ? */
|
/* XXX: use std qcow open function ? */
|
||||||
|
@ -349,9 +372,10 @@ int qcow2_alloc_clusters_at(BlockDriverState *bs, uint64_t offset,
|
||||||
int nb_clusters);
|
int nb_clusters);
|
||||||
int64_t qcow2_alloc_bytes(BlockDriverState *bs, int size);
|
int64_t qcow2_alloc_bytes(BlockDriverState *bs, int size);
|
||||||
void qcow2_free_clusters(BlockDriverState *bs,
|
void qcow2_free_clusters(BlockDriverState *bs,
|
||||||
int64_t offset, int64_t size);
|
int64_t offset, int64_t size,
|
||||||
void qcow2_free_any_clusters(BlockDriverState *bs,
|
enum qcow2_discard_type type);
|
||||||
uint64_t cluster_offset, int nb_clusters);
|
void qcow2_free_any_clusters(BlockDriverState *bs, uint64_t l2_entry,
|
||||||
|
int nb_clusters, enum qcow2_discard_type type);
|
||||||
|
|
||||||
int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
||||||
int64_t l1_table_offset, int l1_size, int addend);
|
int64_t l1_table_offset, int l1_size, int addend);
|
||||||
|
@ -359,6 +383,8 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
||||||
int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
||||||
BdrvCheckMode fix);
|
BdrvCheckMode fix);
|
||||||
|
|
||||||
|
void qcow2_process_discards(BlockDriverState *bs, int ret);
|
||||||
|
|
||||||
/* qcow2-cluster.c functions */
|
/* qcow2-cluster.c functions */
|
||||||
int qcow2_grow_l1_table(BlockDriverState *bs, uint64_t min_size,
|
int qcow2_grow_l1_table(BlockDriverState *bs, uint64_t min_size,
|
||||||
bool exact_size);
|
bool exact_size);
|
||||||
|
|
|
@ -561,6 +561,15 @@ static int vmdk_open_vmdk4(BlockDriverState *bs,
|
||||||
header = footer.header;
|
header = footer.header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (le32_to_cpu(header.version) >= 3) {
|
||||||
|
char buf[64];
|
||||||
|
snprintf(buf, sizeof(buf), "VMDK version %d",
|
||||||
|
le32_to_cpu(header.version));
|
||||||
|
qerror_report(QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,
|
||||||
|
bs->device_name, "vmdk", buf);
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
|
||||||
l1_entry_sectors = le32_to_cpu(header.num_gtes_per_gte)
|
l1_entry_sectors = le32_to_cpu(header.num_gtes_per_gte)
|
||||||
* le64_to_cpu(header.granularity);
|
* le64_to_cpu(header.granularity);
|
||||||
if (l1_entry_sectors == 0) {
|
if (l1_entry_sectors == 0) {
|
||||||
|
|
118
blockdev.c
118
blockdev.c
|
@ -1737,120 +1737,10 @@ QemuOptsList qemu_drive_opts = {
|
||||||
.name = "drive",
|
.name = "drive",
|
||||||
.head = QTAILQ_HEAD_INITIALIZER(qemu_drive_opts.head),
|
.head = QTAILQ_HEAD_INITIALIZER(qemu_drive_opts.head),
|
||||||
.desc = {
|
.desc = {
|
||||||
{
|
/*
|
||||||
.name = "bus",
|
* no elements => accept any params
|
||||||
.type = QEMU_OPT_NUMBER,
|
* validation will happen later
|
||||||
.help = "bus number",
|
*/
|
||||||
},{
|
|
||||||
.name = "unit",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "unit number (i.e. lun for scsi)",
|
|
||||||
},{
|
|
||||||
.name = "if",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "interface (ide, scsi, sd, mtd, floppy, pflash, virtio)",
|
|
||||||
},{
|
|
||||||
.name = "index",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "index number",
|
|
||||||
},{
|
|
||||||
.name = "cyls",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "number of cylinders (ide disk geometry)",
|
|
||||||
},{
|
|
||||||
.name = "heads",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "number of heads (ide disk geometry)",
|
|
||||||
},{
|
|
||||||
.name = "secs",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "number of sectors (ide disk geometry)",
|
|
||||||
},{
|
|
||||||
.name = "trans",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "chs translation (auto, lba. none)",
|
|
||||||
},{
|
|
||||||
.name = "media",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "media type (disk, cdrom)",
|
|
||||||
},{
|
|
||||||
.name = "snapshot",
|
|
||||||
.type = QEMU_OPT_BOOL,
|
|
||||||
.help = "enable/disable snapshot mode",
|
|
||||||
},{
|
|
||||||
.name = "file",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "disk image",
|
|
||||||
},{
|
|
||||||
.name = "discard",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "discard operation (ignore/off, unmap/on)",
|
|
||||||
},{
|
|
||||||
.name = "cache",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "host cache usage (none, writeback, writethrough, "
|
|
||||||
"directsync, unsafe)",
|
|
||||||
},{
|
|
||||||
.name = "aio",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "host AIO implementation (threads, native)",
|
|
||||||
},{
|
|
||||||
.name = "format",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "disk format (raw, qcow2, ...)",
|
|
||||||
},{
|
|
||||||
.name = "serial",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "disk serial number",
|
|
||||||
},{
|
|
||||||
.name = "rerror",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "read error action",
|
|
||||||
},{
|
|
||||||
.name = "werror",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "write error action",
|
|
||||||
},{
|
|
||||||
.name = "addr",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "pci address (virtio only)",
|
|
||||||
},{
|
|
||||||
.name = "readonly",
|
|
||||||
.type = QEMU_OPT_BOOL,
|
|
||||||
.help = "open drive file as read-only",
|
|
||||||
},{
|
|
||||||
.name = "iops",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "limit total I/O operations per second",
|
|
||||||
},{
|
|
||||||
.name = "iops_rd",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "limit read operations per second",
|
|
||||||
},{
|
|
||||||
.name = "iops_wr",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "limit write operations per second",
|
|
||||||
},{
|
|
||||||
.name = "bps",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "limit total bytes per second",
|
|
||||||
},{
|
|
||||||
.name = "bps_rd",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "limit read bytes per second",
|
|
||||||
},{
|
|
||||||
.name = "bps_wr",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "limit write bytes per second",
|
|
||||||
},{
|
|
||||||
.name = "copy-on-read",
|
|
||||||
.type = QEMU_OPT_BOOL,
|
|
||||||
.help = "copy read data from backing file into image file",
|
|
||||||
},{
|
|
||||||
.name = "boot",
|
|
||||||
.type = QEMU_OPT_BOOL,
|
|
||||||
.help = "(deprecated, ignored)",
|
|
||||||
},
|
|
||||||
{ /* end of list */ }
|
{ /* end of list */ }
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
1282
hw/ide/core.c
1282
hw/ide/core.c
File diff suppressed because it is too large
Load diff
|
@ -57,7 +57,7 @@
|
||||||
048 img auto quick
|
048 img auto quick
|
||||||
049 rw auto
|
049 rw auto
|
||||||
050 rw auto backing quick
|
050 rw auto backing quick
|
||||||
#051 rw auto
|
051 rw auto
|
||||||
052 rw auto backing
|
052 rw auto backing
|
||||||
053 rw auto
|
053 rw auto
|
||||||
054 rw auto
|
054 rw auto
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue