Block patches for 2.11.0-rc2

-----BEGIN PGP SIGNATURE-----
 
 iQEcBAABAgAGBQJaDxq/AAoJEPQH2wBh1c9AyUYIAKbmGi0lsCPhJWZF2AXlN1Gl
 wdcEn2mRnYnDAiIagoxr8oa6w0LsFBrg2dJBSSeITNTZnw7/6XWpBn3ybDCedFcU
 9Fn81sCwJeNVHWYytpeOn+us/FokHdYxfvL0dGZg5XCvXF60FFTTIIfjeTLCTqlZ
 tQ0AQTxPp61C6l2GFMSLHHOXkOvGaWh8+Q4nnlNfRoV24Kqz1UT06fjxvKOiZJdd
 o8UtZX17kvx9VYnJ6jzk4sj9AiBRVxWmd9jRT61B4QrXwv9TYbilNqBPMwp+crmo
 UBO/scS5HfnQlL9KNG9mjFbY0DwN+Vpe6bkvtNmpcJj9QoLbB3ZhZafgjVPq8Hw=
 =7brn
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'mreitz/tags/pull-block-2017-11-17' into queue-block

Block patches for 2.11.0-rc2

# gpg: Signature made Fri Nov 17 18:22:07 2017 CET
# gpg:                using RSA key F407DB0061D5CF40
# gpg: Good signature from "Max Reitz <mreitz@redhat.com>"
# Primary key fingerprint: 91BE B60A 30DB 3E88 57D1  1829 F407 DB00 61D5 CF40

* mreitz/tags/pull-block-2017-11-17:
  iotests: Make 087 pass without AIO enabled
  block: Make bdrv_next() keep strong references
  qcow2: Fix overly broad madvise()
  qcow2: Refuse to get unaligned offsets from cache
  qcow2: Add bounds check to get_refblock_offset()
  block: Guard against NULL bs->drv
  qcow2: Unaligned zero cluster in handle_alloc()
  qcow2: check_errors are fatal
  qcow2: reject unaligned offsets in write compressed
  iotests: Add test for failing qemu-img commit
  tests: Add check-qobject for equality tests
  iotests: Add test for non-string option reopening
  block: qobject_is_equal() in bdrv_reopen_prepare()
  qapi: Add qobject_is_equal()
  qapi/qlist: Add qlist_append_null() macro
  qapi/qnull: Add own header

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
This commit is contained in:
Kevin Wolf 2017-11-17 18:24:30 +01:00
commit d5a49c6e7d
43 changed files with 1086 additions and 43 deletions

53
block.c
View file

@ -720,6 +720,10 @@ static int refresh_total_sectors(BlockDriverState *bs, int64_t hint)
{
BlockDriver *drv = bs->drv;
if (!drv) {
return -ENOMEDIUM;
}
/* Do not attempt drv->bdrv_getlength() on scsi-generic devices */
if (bdrv_is_sg(bs))
return 0;
@ -3074,19 +3078,26 @@ int bdrv_reopen_prepare(BDRVReopenState *reopen_state, BlockReopenQueue *queue,
const QDictEntry *entry = qdict_first(reopen_state->options);
do {
QString *new_obj = qobject_to_qstring(entry->value);
const char *new = qstring_get_str(new_obj);
/*
* Caution: while qdict_get_try_str() is fine, getting
* non-string types would require more care. When
* bs->options come from -blockdev or blockdev_add, its
* members are typed according to the QAPI schema, but
* when they come from -drive, they're all QString.
*/
const char *old = qdict_get_try_str(reopen_state->bs->options,
entry->key);
QObject *new = entry->value;
QObject *old = qdict_get(reopen_state->bs->options, entry->key);
if (!old || strcmp(new, old)) {
/*
* TODO: When using -drive to specify blockdev options, all values
* will be strings; however, when using -blockdev, blockdev-add or
* filenames using the json:{} pseudo-protocol, they will be
* correctly typed.
* In contrast, reopening options are (currently) always strings
* (because you can only specify them through qemu-io; all other
* callers do not specify any options).
* Therefore, when using anything other than -drive to create a BDS,
* this cannot detect non-string options as unchanged, because
* qobject_is_equal() always returns false for objects of different
* type. In the future, this should be remedied by correctly typing
* all options. For now, this is not too big of an issue because
* the user can simply omit options which cannot be changed anyway,
* so they will stay unchanged.
*/
if (!qobject_is_equal(new, old)) {
error_setg(errp, "Cannot change the option '%s'", entry->key);
ret = -EINVAL;
goto error;
@ -3424,6 +3435,10 @@ int bdrv_change_backing_file(BlockDriverState *bs,
BlockDriver *drv = bs->drv;
int ret;
if (!drv) {
return -ENOMEDIUM;
}
/* Backing file format doesn't make sense without a backing file */
if (backing_fmt && !backing_file) {
return -EINVAL;
@ -3909,7 +3924,9 @@ int bdrv_has_zero_init_1(BlockDriverState *bs)
int bdrv_has_zero_init(BlockDriverState *bs)
{
assert(bs->drv);
if (!bs->drv) {
return 0;
}
/* If BS is a copy on write image, it is initialized to
the contents of the base image, which may not be zeroes. */
@ -4238,6 +4255,7 @@ void bdrv_invalidate_cache_all(Error **errp)
aio_context_release(aio_context);
if (local_err) {
error_propagate(errp, local_err);
bdrv_next_cleanup(&it);
return;
}
}
@ -4249,6 +4267,10 @@ static int bdrv_inactivate_recurse(BlockDriverState *bs,
BdrvChild *child, *parent;
int ret;
if (!bs->drv) {
return -ENOMEDIUM;
}
if (!setting_flag && bs->drv->bdrv_inactivate) {
ret = bs->drv->bdrv_inactivate(bs);
if (ret < 0) {
@ -4309,6 +4331,7 @@ int bdrv_inactivate_all(void)
for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) {
ret = bdrv_inactivate_recurse(bs, pass);
if (ret < 0) {
bdrv_next_cleanup(&it);
goto out;
}
}
@ -4783,6 +4806,9 @@ void bdrv_remove_aio_context_notifier(BlockDriverState *bs,
int bdrv_amend_options(BlockDriverState *bs, QemuOpts *opts,
BlockDriverAmendStatusCB *status_cb, void *cb_opaque)
{
if (!bs->drv) {
return -ENOMEDIUM;
}
if (!bs->drv->bdrv_amend_options) {
return -ENOTSUP;
}
@ -4840,6 +4866,7 @@ bool bdrv_is_first_non_filter(BlockDriverState *candidate)
/* candidate is the first non filter */
if (perm) {
bdrv_next_cleanup(&it);
return true;
}
}

View file

@ -442,21 +442,37 @@ BlockBackend *blk_next(BlockBackend *blk)
* the monitor or attached to a BlockBackend */
BlockDriverState *bdrv_next(BdrvNextIterator *it)
{
BlockDriverState *bs;
BlockDriverState *bs, *old_bs;
/* Must be called from the main loop */
assert(qemu_get_current_aio_context() == qemu_get_aio_context());
/* First, return all root nodes of BlockBackends. In order to avoid
* returning a BDS twice when multiple BBs refer to it, we only return it
* if the BB is the first one in the parent list of the BDS. */
if (it->phase == BDRV_NEXT_BACKEND_ROOTS) {
BlockBackend *old_blk = it->blk;
old_bs = old_blk ? blk_bs(old_blk) : NULL;
do {
it->blk = blk_all_next(it->blk);
bs = it->blk ? blk_bs(it->blk) : NULL;
} while (it->blk && (bs == NULL || bdrv_first_blk(bs) != it->blk));
if (it->blk) {
blk_ref(it->blk);
}
blk_unref(old_blk);
if (bs) {
bdrv_ref(bs);
bdrv_unref(old_bs);
return bs;
}
it->phase = BDRV_NEXT_MONITOR_OWNED;
} else {
old_bs = it->bs;
}
/* Then return the monitor-owned BDSes without a BB attached. Ignore all
@ -467,18 +483,46 @@ BlockDriverState *bdrv_next(BdrvNextIterator *it)
bs = it->bs;
} while (bs && bdrv_has_blk(bs));
if (bs) {
bdrv_ref(bs);
}
bdrv_unref(old_bs);
return bs;
}
BlockDriverState *bdrv_first(BdrvNextIterator *it)
static void bdrv_next_reset(BdrvNextIterator *it)
{
*it = (BdrvNextIterator) {
.phase = BDRV_NEXT_BACKEND_ROOTS,
};
}
BlockDriverState *bdrv_first(BdrvNextIterator *it)
{
bdrv_next_reset(it);
return bdrv_next(it);
}
/* Must be called when aborting a bdrv_next() iteration before
* bdrv_next() returns NULL */
void bdrv_next_cleanup(BdrvNextIterator *it)
{
/* Must be called from the main loop */
assert(qemu_get_current_aio_context() == qemu_get_aio_context());
if (it->phase == BDRV_NEXT_BACKEND_ROOTS) {
if (it->blk) {
bdrv_unref(blk_bs(it->blk));
blk_unref(it->blk);
}
} else {
bdrv_unref(it->bs);
}
bdrv_next_reset(it);
}
/*
* Add a BlockBackend into the list of backends referenced by the monitor, with
* the given @name acting as the handle for the monitor.

View file

@ -853,6 +853,10 @@ static int coroutine_fn bdrv_driver_preadv(BlockDriverState *bs,
assert(!(flags & ~BDRV_REQ_MASK));
if (!drv) {
return -ENOMEDIUM;
}
if (drv->bdrv_co_preadv) {
return drv->bdrv_co_preadv(bs, offset, bytes, qiov, flags);
}
@ -894,6 +898,10 @@ static int coroutine_fn bdrv_driver_pwritev(BlockDriverState *bs,
assert(!(flags & ~BDRV_REQ_MASK));
if (!drv) {
return -ENOMEDIUM;
}
if (drv->bdrv_co_pwritev) {
ret = drv->bdrv_co_pwritev(bs, offset, bytes, qiov,
flags & bs->supported_write_flags);
@ -945,6 +953,10 @@ bdrv_driver_pwritev_compressed(BlockDriverState *bs, uint64_t offset,
{
BlockDriver *drv = bs->drv;
if (!drv) {
return -ENOMEDIUM;
}
if (!drv->bdrv_co_pwritev_compressed) {
return -ENOTSUP;
}
@ -975,6 +987,10 @@ static int coroutine_fn bdrv_co_do_copy_on_readv(BdrvChild *child,
BDRV_REQUEST_MAX_BYTES);
unsigned int progress = 0;
if (!drv) {
return -ENOMEDIUM;
}
/* FIXME We cannot require callers to have write permissions when all they
* are doing is a read request. If we did things right, write permissions
* would be obtained anyway, but internally by the copy-on-read code. As
@ -1291,6 +1307,10 @@ static int coroutine_fn bdrv_co_do_pwrite_zeroes(BlockDriverState *bs,
bs->bl.request_alignment);
int max_transfer = MIN_NON_ZERO(bs->bl.max_transfer, MAX_BOUNCE_BUFFER);
if (!drv) {
return -ENOMEDIUM;
}
assert(alignment % bs->bl.request_alignment == 0);
head = offset % alignment;
tail = (offset + bytes) % alignment;
@ -1397,6 +1417,10 @@ static int coroutine_fn bdrv_aligned_pwritev(BdrvChild *child,
uint64_t bytes_remaining = bytes;
int max_transfer;
if (!drv) {
return -ENOMEDIUM;
}
if (bdrv_has_readonly_bitmaps(bs)) {
return -EPERM;
}
@ -1863,6 +1887,8 @@ static int coroutine_fn bdrv_co_block_status(BlockDriverState *bs,
bytes = n;
}
/* Must be non-NULL or bdrv_getlength() would have failed */
assert(bs->drv);
if (!bs->drv->bdrv_co_get_block_status) {
*pnum = bytes;
ret = BDRV_BLOCK_DATA | BDRV_BLOCK_ALLOCATED;
@ -2373,6 +2399,12 @@ int coroutine_fn bdrv_co_flush(BlockDriverState *bs)
}
BLKDBG_EVENT(bs->file, BLKDBG_FLUSH_TO_DISK);
if (!bs->drv) {
/* bs->drv->bdrv_co_flush() might have ejected the BDS
* (even in case of apparent success) */
ret = -ENOMEDIUM;
goto out;
}
if (bs->drv->bdrv_co_flush_to_disk) {
ret = bs->drv->bdrv_co_flush_to_disk(bs);
} else if (bs->drv->bdrv_aio_flush) {
@ -2542,6 +2574,10 @@ int coroutine_fn bdrv_co_pdiscard(BlockDriverState *bs, int64_t offset,
num = max_pdiscard;
}
if (!bs->drv) {
ret = -ENOMEDIUM;
goto out;
}
if (bs->drv->bdrv_co_pdiscard) {
ret = bs->drv->bdrv_co_pdiscard(bs, offset, num);
} else {

View file

@ -39,8 +39,14 @@ BlockDeviceInfo *bdrv_block_device_info(BlockBackend *blk,
{
ImageInfo **p_image_info;
BlockDriverState *bs0;
BlockDeviceInfo *info = g_malloc0(sizeof(*info));
BlockDeviceInfo *info;
if (!bs->drv) {
error_setg(errp, "Block device %s is ejected", bs->node_name);
return NULL;
}
info = g_malloc0(sizeof(*info));
info->file = g_strdup(bs->filename);
info->ro = bs->read_only;
info->drv = g_strdup(bs->drv->format_name);

View file

@ -62,6 +62,18 @@ static inline int qcow2_cache_get_table_idx(BlockDriverState *bs,
return idx;
}
static inline const char *qcow2_cache_get_name(BDRVQcow2State *s, Qcow2Cache *c)
{
if (c == s->refcount_block_cache) {
return "refcount block";
} else if (c == s->l2_table_cache) {
return "L2 table";
} else {
/* Do not abort, because this is not critical */
return "unknown";
}
}
static void qcow2_cache_table_release(BlockDriverState *bs, Qcow2Cache *c,
int i, int num_tables)
{
@ -73,7 +85,7 @@ static void qcow2_cache_table_release(BlockDriverState *bs, Qcow2Cache *c,
size_t mem_size = (size_t) s->cluster_size * num_tables;
size_t offset = QEMU_ALIGN_UP((uintptr_t) t, align) - (uintptr_t) t;
size_t length = QEMU_ALIGN_DOWN(mem_size - offset, align);
if (length > 0) {
if (mem_size > offset && length > 0) {
madvise((uint8_t *) t + offset, length, MADV_DONTNEED);
}
#endif
@ -314,9 +326,18 @@ static int qcow2_cache_do_get(BlockDriverState *bs, Qcow2Cache *c,
uint64_t min_lru_counter = UINT64_MAX;
int min_lru_index = -1;
assert(offset != 0);
trace_qcow2_cache_get(qemu_coroutine_self(), c == s->l2_table_cache,
offset, read_from_disk);
if (offset_into_cluster(s, offset)) {
qcow2_signal_corruption(bs, true, -1, -1, "Cannot get entry from %s "
"cache: Offset %#" PRIx64 " is unaligned",
qcow2_cache_get_name(s, c), offset);
return -EIO;
}
/* Check if the table is already cached */
i = lookup_index = (offset / s->cluster_size * 4) % c->size;
do {

View file

@ -1308,10 +1308,21 @@ static int handle_alloc(BlockDriverState *bs, uint64_t guest_offset,
(!*host_offset ||
start_of_cluster(s, *host_offset) == (entry & L2E_OFFSET_MASK)))
{
int preallocated_nb_clusters;
if (offset_into_cluster(s, entry & L2E_OFFSET_MASK)) {
qcow2_signal_corruption(bs, true, -1, -1, "Preallocated zero "
"cluster offset %#llx unaligned (guest "
"offset: %#" PRIx64 ")",
entry & L2E_OFFSET_MASK, guest_offset);
ret = -EIO;
goto fail;
}
/* Try to reuse preallocated zero clusters; contiguous normal clusters
* would be fine, too, but count_cow_clusters() above has limited
* nb_clusters already to a range of COW clusters */
int preallocated_nb_clusters =
preallocated_nb_clusters =
count_contiguous_clusters(nb_clusters, s->cluster_size,
&l2_table[l2_index], QCOW_OFLAG_COPIED);
assert(preallocated_nb_clusters > 0);

View file

@ -3077,16 +3077,40 @@ done:
return ret;
}
static int64_t get_refblock_offset(BlockDriverState *bs, uint64_t offset)
{
BDRVQcow2State *s = bs->opaque;
uint32_t index = offset_to_reftable_index(s, offset);
int64_t covering_refblock_offset = 0;
if (index < s->refcount_table_size) {
covering_refblock_offset = s->refcount_table[index] & REFT_OFFSET_MASK;
}
if (!covering_refblock_offset) {
qcow2_signal_corruption(bs, true, -1, -1, "Refblock at %#" PRIx64 " is "
"not covered by the refcount structures",
offset);
return -EIO;
}
return covering_refblock_offset;
}
static int qcow2_discard_refcount_block(BlockDriverState *bs,
uint64_t discard_block_offs)
{
BDRVQcow2State *s = bs->opaque;
uint64_t refblock_offs = get_refblock_offset(s, discard_block_offs);
int64_t refblock_offs;
uint64_t cluster_index = discard_block_offs >> s->cluster_bits;
uint32_t block_index = cluster_index & (s->refcount_block_size - 1);
void *refblock;
int ret;
refblock_offs = get_refblock_offset(bs, discard_block_offs);
if (refblock_offs < 0) {
return refblock_offs;
}
assert(discard_block_offs != 0);
ret = qcow2_cache_get(bs, s->refcount_block_cache, refblock_offs,

View file

@ -1477,7 +1477,10 @@ static int qcow2_do_open(BlockDriverState *bs, QDict *options, int flags,
BdrvCheckResult result = {0};
ret = qcow2_check(bs, &result, BDRV_FIX_ERRORS | BDRV_FIX_LEAKS);
if (ret < 0) {
if (ret < 0 || result.check_errors) {
if (ret >= 0) {
ret = -EIO;
}
error_setg_errno(errp, -ret, "Could not repair dirty image");
goto fail;
}
@ -3358,6 +3361,10 @@ qcow2_co_pwritev_compressed(BlockDriverState *bs, uint64_t offset,
return bdrv_truncate(bs->file, cluster_offset, PREALLOC_MODE_OFF, NULL);
}
if (offset_into_cluster(s, offset)) {
return -EINVAL;
}
buf = qemu_blockalign(bs, s->cluster_size);
if (bytes != s->cluster_size) {
if (bytes > s->cluster_size ||

View file

@ -527,12 +527,6 @@ uint32_t offset_to_reftable_index(BDRVQcow2State *s, uint64_t offset)
return offset >> (s->refcount_block_bits + s->cluster_bits);
}
static inline uint64_t get_refblock_offset(BDRVQcow2State *s, uint64_t offset)
{
uint32_t index = offset_to_reftable_index(s, offset);
return s->refcount_table[index] & REFT_OFFSET_MASK;
}
/* qcow2.c functions */
int qcow2_backing_read1(BlockDriverState *bs, QEMUIOVector *qiov,
int64_t sector_num, int nb_sectors);

View file

@ -342,12 +342,24 @@ static void secondary_do_checkpoint(BDRVReplicationState *s, Error **errp)
return;
}
if (!s->active_disk->bs->drv) {
error_setg(errp, "Active disk %s is ejected",
s->active_disk->bs->node_name);
return;
}
ret = s->active_disk->bs->drv->bdrv_make_empty(s->active_disk->bs);
if (ret < 0) {
error_setg(errp, "Cannot make active disk empty");
return;
}
if (!s->hidden_disk->bs->drv) {
error_setg(errp, "Hidden disk %s is ejected",
s->hidden_disk->bs->node_name);
return;
}
ret = s->hidden_disk->bs->drv->bdrv_make_empty(s->hidden_disk->bs);
if (ret < 0) {
error_setg(errp, "Cannot make hidden disk empty");
@ -511,6 +523,9 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
return;
}
/* Must be true, or the bdrv_getlength() calls would have failed */
assert(s->active_disk->bs->drv && s->hidden_disk->bs->drv);
if (!s->active_disk->bs->drv->bdrv_make_empty ||
!s->hidden_disk->bs->drv->bdrv_make_empty) {
error_setg(errp,

View file

@ -417,6 +417,7 @@ bool bdrv_all_can_snapshot(BlockDriverState **first_bad_bs)
}
aio_context_release(ctx);
if (!ok) {
bdrv_next_cleanup(&it);
goto fail;
}
}
@ -444,6 +445,7 @@ int bdrv_all_delete_snapshot(const char *name, BlockDriverState **first_bad_bs,
}
aio_context_release(ctx);
if (ret < 0) {
bdrv_next_cleanup(&it);
goto fail;
}
}
@ -469,6 +471,7 @@ int bdrv_all_goto_snapshot(const char *name, BlockDriverState **first_bad_bs)
}
aio_context_release(ctx);
if (err < 0) {
bdrv_next_cleanup(&it);
goto fail;
}
}
@ -494,6 +497,7 @@ int bdrv_all_find_snapshot(const char *name, BlockDriverState **first_bad_bs)
}
aio_context_release(ctx);
if (err < 0) {
bdrv_next_cleanup(&it);
goto fail;
}
}
@ -525,6 +529,7 @@ int bdrv_all_create_snapshot(QEMUSnapshotInfo *sn,
}
aio_context_release(ctx);
if (err < 0) {
bdrv_next_cleanup(&it);
goto fail;
}
}
@ -548,6 +553,7 @@ BlockDriverState *bdrv_all_find_vmstate_bs(void)
aio_context_release(ctx);
if (found) {
bdrv_next_cleanup(&it);
break;
}
}

View file

@ -2947,7 +2947,7 @@ static int do_commit(BDRVVVFATState* s)
return ret;
}
if (s->qcow->bs->drv->bdrv_make_empty) {
if (s->qcow->bs->drv && s->qcow->bs->drv->bdrv_make_empty) {
s->qcow->bs->drv->bdrv_make_empty(s->qcow->bs);
}

View file

@ -461,6 +461,7 @@ typedef struct BdrvNextIterator {
BlockDriverState *bdrv_first(BdrvNextIterator *it);
BlockDriverState *bdrv_next(BdrvNextIterator *it);
void bdrv_next_cleanup(BdrvNextIterator *it);
BlockDriverState *bdrv_next_monitor_owned(BlockDriverState *bs);
bool bdrv_is_encrypted(BlockDriverState *bs);

View file

@ -24,6 +24,7 @@ typedef struct QBool {
QBool *qbool_from_bool(bool value);
bool qbool_get_bool(const QBool *qb);
QBool *qobject_to_qbool(const QObject *obj);
bool qbool_is_equal(const QObject *x, const QObject *y);
void qbool_destroy_obj(QObject *obj);
#endif /* QBOOL_H */

View file

@ -15,6 +15,7 @@
#include "qapi/qmp/qobject.h"
#include "qapi/qmp/qlist.h"
#include "qapi/qmp/qnull.h"
#include "qapi/qmp/qnum.h"
#include "qemu/queue.h"
@ -42,6 +43,7 @@ void qdict_del(QDict *qdict, const char *key);
int qdict_haskey(const QDict *qdict, const char *key);
QObject *qdict_get(const QDict *qdict, const char *key);
QDict *qobject_to_qdict(const QObject *obj);
bool qdict_is_equal(const QObject *x, const QObject *y);
void qdict_iter(const QDict *qdict,
void (*iter)(const char *key, QObject *obj, void *opaque),
void *opaque);

View file

@ -15,6 +15,7 @@
#include "qapi/qmp/qobject.h"
#include "qapi/qmp/qnum.h"
#include "qapi/qmp/qnull.h"
#include "qemu/queue.h"
typedef struct QListEntry {
@ -37,6 +38,8 @@ typedef struct QList {
qlist_append(qlist, qbool_from_bool(value))
#define qlist_append_str(qlist, value) \
qlist_append(qlist, qstring_from_str(value))
#define qlist_append_null(qlist) \
qlist_append(qlist, qnull())
#define QLIST_FOREACH_ENTRY(qlist, var) \
for ((var) = ((qlist)->head.tqh_first); \
@ -58,6 +61,7 @@ QObject *qlist_peek(QList *qlist);
int qlist_empty(const QList *qlist);
size_t qlist_size(const QList *qlist);
QList *qobject_to_qlist(const QObject *obj);
bool qlist_is_equal(const QObject *x, const QObject *y);
void qlist_destroy_obj(QObject *obj);
static inline const QListEntry *qlist_first(const QList *qlist)

32
include/qapi/qmp/qnull.h Normal file
View file

@ -0,0 +1,32 @@
/*
* QNull
*
* Copyright (C) 2015 Red Hat, Inc.
*
* Authors:
* Markus Armbruster <armbru@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.
*/
#ifndef QNULL_H
#define QNULL_H
#include "qapi/qmp/qobject.h"
struct QNull {
QObject base;
};
extern QNull qnull_;
static inline QNull *qnull(void)
{
QINCREF(&qnull_);
return &qnull_;
}
bool qnull_is_equal(const QObject *x, const QObject *y);
#endif /* QNULL_H */

View file

@ -69,6 +69,7 @@ double qnum_get_double(QNum *qn);
char *qnum_to_string(QNum *qn);
QNum *qobject_to_qnum(const QObject *obj);
bool qnum_is_equal(const QObject *x, const QObject *y);
void qnum_destroy_obj(QObject *obj);
#endif /* QNUM_H */

View file

@ -67,6 +67,15 @@ static inline void qobject_incref(QObject *obj)
obj->refcnt++;
}
/**
* qobject_is_equal(): Return whether the two objects are equal.
*
* Any of the pointers may be NULL; return true if both are. Always
* return false if only one is (therefore a QNull object is not
* considered equal to a NULL pointer).
*/
bool qobject_is_equal(const QObject *x, const QObject *y);
/**
* qobject_destroy(): Free resources used by the object
*/
@ -93,16 +102,4 @@ static inline QType qobject_type(const QObject *obj)
return obj->type;
}
struct QNull {
QObject base;
};
extern QNull qnull_;
static inline QNull *qnull(void)
{
QINCREF(&qnull_);
return &qnull_;
}
#endif /* QOBJECT_H */

View file

@ -31,6 +31,7 @@ void qstring_append_int(QString *qstring, int64_t value);
void qstring_append(QString *qstring, const char *str);
void qstring_append_chr(QString *qstring, int c);
QString *qobject_to_qstring(const QObject *obj);
bool qstring_is_equal(const QObject *x, const QObject *y);
void qstring_destroy_obj(QObject *obj);
#endif /* QSTRING_H */

View file

@ -19,5 +19,6 @@
#include "qapi/qmp/qstring.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qlist.h"
#include "qapi/qmp/qnull.h"
#endif /* QAPI_QMP_TYPES_H */

View file

@ -415,6 +415,7 @@ static int init_blk_migration(QEMUFile *f)
sectors = bdrv_nb_sectors(bs);
if (sectors <= 0) {
ret = sectors;
bdrv_next_cleanup(&it);
goto out;
}

View file

@ -12,6 +12,7 @@
#include "qapi/clone-visitor.h"
#include "qapi/visitor-impl.h"
#include "qapi/error.h"
#include "qapi/qmp/qnull.h"
struct QapiCloneVisitor {
Visitor visitor;

View file

@ -16,6 +16,7 @@
#include "qapi/string-input-visitor.h"
#include "qapi/visitor-impl.h"
#include "qapi/qmp/qerror.h"
#include "qapi/qmp/qnull.h"
#include "qemu/option.h"
#include "qemu/queue.h"
#include "qemu/range.h"

View file

@ -51,6 +51,14 @@ QBool *qobject_to_qbool(const QObject *obj)
return container_of(obj, QBool, base);
}
/**
* qbool_is_equal(): Test whether the two QBools are equal
*/
bool qbool_is_equal(const QObject *x, const QObject *y)
{
return qobject_to_qbool(x)->value == qobject_to_qbool(y)->value;
}
/**
* qbool_destroy_obj(): Free all memory allocated by a
* QBool object

View file

@ -402,6 +402,35 @@ void qdict_del(QDict *qdict, const char *key)
}
}
/**
* qdict_is_equal(): Test whether the two QDicts are equal
*
* Here, equality means whether they contain the same keys and whether
* the respective values are in turn equal (i.e. invoking
* qobject_is_equal() on them yields true).
*/
bool qdict_is_equal(const QObject *x, const QObject *y)
{
const QDict *dict_x = qobject_to_qdict(x);
const QDict *dict_y = qobject_to_qdict(y);
const QDictEntry *e;
if (qdict_size(dict_x) != qdict_size(dict_y)) {
return false;
}
for (e = qdict_first(dict_x); e; e = qdict_next(dict_x, e)) {
const QObject *obj_x = qdict_entry_value(e);
const QObject *obj_y = qdict_get(dict_y, qdict_entry_key(e));
if (!qobject_is_equal(obj_x, obj_y)) {
return false;
}
}
return true;
}
/**
* qdict_destroy_obj(): Free all the memory allocated by a QDict
*/

View file

@ -139,6 +139,38 @@ QList *qobject_to_qlist(const QObject *obj)
return container_of(obj, QList, base);
}
/**
* qlist_is_equal(): Test whether the two QLists are equal
*
* In order to be considered equal, the respective two objects at each
* index of the two lists have to compare equal (regarding
* qobject_is_equal()), and both lists have to have the same number of
* elements.
* That means both lists have to contain equal objects in equal order.
*/
bool qlist_is_equal(const QObject *x, const QObject *y)
{
const QList *list_x = qobject_to_qlist(x);
const QList *list_y = qobject_to_qlist(y);
const QListEntry *entry_x, *entry_y;
entry_x = qlist_first(list_x);
entry_y = qlist_first(list_y);
while (entry_x && entry_y) {
if (!qobject_is_equal(qlist_entry_obj(entry_x),
qlist_entry_obj(entry_y)))
{
return false;
}
entry_x = qlist_next(entry_x);
entry_y = qlist_next(entry_y);
}
return !entry_x && !entry_y;
}
/**
* qlist_destroy_obj(): Free all the memory allocated by a QList
*/

View file

@ -12,7 +12,7 @@
#include "qemu/osdep.h"
#include "qemu-common.h"
#include "qapi/qmp/qobject.h"
#include "qapi/qmp/qnull.h"
QNull qnull_ = {
.base = {
@ -20,3 +20,12 @@ QNull qnull_ = {
.refcnt = 1,
},
};
/**
* qnull_is_equal(): Always return true because any two QNull objects
* are equal.
*/
bool qnull_is_equal(const QObject *x, const QObject *y)
{
return true;
}

View file

@ -212,6 +212,60 @@ QNum *qobject_to_qnum(const QObject *obj)
return container_of(obj, QNum, base);
}
/**
* qnum_is_equal(): Test whether the two QNums are equal
*
* Negative integers are never considered equal to unsigned integers,
* but positive integers in the range [0, INT64_MAX] are considered
* equal independently of whether the QNum's kind is i64 or u64.
*
* Doubles are never considered equal to integers.
*/
bool qnum_is_equal(const QObject *x, const QObject *y)
{
QNum *num_x = qobject_to_qnum(x);
QNum *num_y = qobject_to_qnum(y);
switch (num_x->kind) {
case QNUM_I64:
switch (num_y->kind) {
case QNUM_I64:
/* Comparison in native int64_t type */
return num_x->u.i64 == num_y->u.i64;
case QNUM_U64:
/* Implicit conversion of x to uin64_t, so we have to
* check its sign before */
return num_x->u.i64 >= 0 && num_x->u.i64 == num_y->u.u64;
case QNUM_DOUBLE:
return false;
}
abort();
case QNUM_U64:
switch (num_y->kind) {
case QNUM_I64:
return qnum_is_equal(y, x);
case QNUM_U64:
/* Comparison in native uint64_t type */
return num_x->u.u64 == num_y->u.u64;
case QNUM_DOUBLE:
return false;
}
abort();
case QNUM_DOUBLE:
switch (num_y->kind) {
case QNUM_I64:
case QNUM_U64:
return false;
case QNUM_DOUBLE:
/* Comparison in native double type */
return num_x->u.dbl == num_y->u.dbl;
}
abort();
}
abort();
}
/**
* qnum_destroy_obj(): Free all memory allocated by a
* QNum object

View file

@ -27,3 +27,32 @@ void qobject_destroy(QObject *obj)
assert(QTYPE_QNULL < obj->type && obj->type < QTYPE__MAX);
qdestroy[obj->type](obj);
}
static bool (*qis_equal[QTYPE__MAX])(const QObject *, const QObject *) = {
[QTYPE_NONE] = NULL, /* No such object exists */
[QTYPE_QNULL] = qnull_is_equal,
[QTYPE_QNUM] = qnum_is_equal,
[QTYPE_QSTRING] = qstring_is_equal,
[QTYPE_QDICT] = qdict_is_equal,
[QTYPE_QLIST] = qlist_is_equal,
[QTYPE_QBOOL] = qbool_is_equal,
};
bool qobject_is_equal(const QObject *x, const QObject *y)
{
/* We cannot test x == y because an object does not need to be
* equal to itself (e.g. NaN floats are not). */
if (!x && !y) {
return true;
}
if (!x || !y || x->type != y->type) {
return false;
}
assert(QTYPE_NONE < x->type && x->type < QTYPE__MAX);
return qis_equal[x->type](x, y);
}

View file

@ -128,6 +128,15 @@ const char *qstring_get_str(const QString *qstring)
return qstring->string;
}
/**
* qstring_is_equal(): Test whether the two QStrings are equal
*/
bool qstring_is_equal(const QObject *x, const QObject *y)
{
return !strcmp(qobject_to_qstring(x)->string,
qobject_to_qstring(y)->string);
}
/**
* qstring_destroy_obj(): Free all memory allocated by a QString
* object

View file

@ -41,4 +41,7 @@ expression Obj, E;
|
- qlist_append(Obj, qstring_from_str(E));
+ qlist_append_str(Obj, E);
|
- qlist_append(Obj, qnull());
+ qlist_append_null(Obj);
)

1
tests/.gitignore vendored
View file

@ -8,6 +8,7 @@ check-qjson
check-qlist
check-qlit
check-qnull
check-qobject
check-qstring
check-qom-interface
check-qom-proplist

View file

@ -41,6 +41,7 @@ check-unit-y += tests/check-qlist$(EXESUF)
gcov-files-check-qlist-y = qobject/qlist.c
check-unit-y += tests/check-qnull$(EXESUF)
gcov-files-check-qnull-y = qobject/qnull.c
check-unit-y += tests/check-qobject$(EXESUF)
check-unit-y += tests/check-qjson$(EXESUF)
gcov-files-check-qjson-y = qobject/qjson.c
check-unit-y += tests/check-qlit$(EXESUF)
@ -546,7 +547,7 @@ GENERATED_FILES += tests/test-qapi-types.h tests/test-qapi-visit.h \
tests/test-qmp-introspect.h
test-obj-y = tests/check-qnum.o tests/check-qstring.o tests/check-qdict.o \
tests/check-qlist.o tests/check-qnull.o \
tests/check-qlist.o tests/check-qnull.o tests/check-qobject.o \
tests/check-qjson.o tests/check-qlit.o \
tests/test-coroutine.o tests/test-string-output-visitor.o \
tests/test-string-input-visitor.o tests/test-qobject-output-visitor.o \
@ -580,6 +581,7 @@ tests/check-qstring$(EXESUF): tests/check-qstring.o $(test-util-obj-y)
tests/check-qdict$(EXESUF): tests/check-qdict.o $(test-util-obj-y)
tests/check-qlist$(EXESUF): tests/check-qlist.o $(test-util-obj-y)
tests/check-qnull$(EXESUF): tests/check-qnull.o $(test-util-obj-y)
tests/check-qobject$(EXESUF): tests/check-qobject.o $(test-util-obj-y)
tests/check-qjson$(EXESUF): tests/check-qjson.o $(test-util-obj-y)
tests/check-qlit$(EXESUF): tests/check-qlit.o $(test-util-obj-y)
tests/check-qom-interface$(EXESUF): tests/check-qom-interface.o $(test-qom-obj-y)

View file

@ -8,7 +8,7 @@
*/
#include "qemu/osdep.h"
#include "qapi/qmp/qobject.h"
#include "qapi/qmp/qnull.h"
#include "qemu-common.h"
#include "qapi/qobject-input-visitor.h"
#include "qapi/qobject-output-visitor.h"

328
tests/check-qobject.c Normal file
View file

@ -0,0 +1,328 @@
/*
* Generic QObject unit-tests.
*
* Copyright (C) 2017 Red Hat Inc.
*
* 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 "qemu/osdep.h"
#include "qapi/qmp/types.h"
#include "qemu-common.h"
#include <math.h>
/* Marks the end of the test_equality() argument list.
* We cannot use NULL there because that is a valid argument. */
static QObject test_equality_end_of_arguments;
/**
* Test whether all variadic QObject *arguments are equal (@expected
* is true) or whether they are all not equal (@expected is false).
* Every QObject is tested to be equal to itself (to test
* reflexivity), all tests are done both ways (to test symmetry), and
* transitivity is not assumed but checked (each object is compared to
* every other one).
*
* Note that qobject_is_equal() is not really an equivalence relation,
* so this function may not be used for all objects (reflexivity is
* not guaranteed, e.g. in the case of a QNum containing NaN).
*
* The @_ argument is required because a boolean may not be the last
* argument before a variadic argument list (C11 7.16.1.4 para. 4).
*/
static void do_test_equality(bool expected, int _, ...)
{
va_list ap_count, ap_extract;
QObject **args;
int arg_count = 0;
int i, j;
va_start(ap_count, _);
va_copy(ap_extract, ap_count);
while (va_arg(ap_count, QObject *) != &test_equality_end_of_arguments) {
arg_count++;
}
va_end(ap_count);
args = g_new(QObject *, arg_count);
for (i = 0; i < arg_count; i++) {
args[i] = va_arg(ap_extract, QObject *);
}
va_end(ap_extract);
for (i = 0; i < arg_count; i++) {
g_assert(qobject_is_equal(args[i], args[i]) == true);
for (j = i + 1; j < arg_count; j++) {
g_assert(qobject_is_equal(args[i], args[j]) == expected);
}
}
}
#define check_equal(...) \
do_test_equality(true, 0, __VA_ARGS__, &test_equality_end_of_arguments)
#define check_unequal(...) \
do_test_equality(false, 0, __VA_ARGS__, &test_equality_end_of_arguments)
static void do_free_all(int _, ...)
{
va_list ap;
QObject *obj;
va_start(ap, _);
while ((obj = va_arg(ap, QObject *)) != NULL) {
qobject_decref(obj);
}
va_end(ap);
}
#define free_all(...) \
do_free_all(0, __VA_ARGS__, NULL)
static void qobject_is_equal_null_test(void)
{
check_unequal(qnull(), NULL);
}
static void qobject_is_equal_num_test(void)
{
QNum *u0, *i0, *d0, *dnan, *um42, *im42, *dm42;
u0 = qnum_from_uint(0u);
i0 = qnum_from_int(0);
d0 = qnum_from_double(0.0);
dnan = qnum_from_double(NAN);
um42 = qnum_from_uint((uint64_t)-42);
im42 = qnum_from_int(-42);
dm42 = qnum_from_double(-42.0);
/* Integers representing a mathematically equal number should
* compare equal */
check_equal(u0, i0);
/* Doubles, however, are always unequal to integers */
check_unequal(u0, d0);
check_unequal(i0, d0);
/* Do not assume any object is equal to itself -- note however
* that NaN cannot occur in a JSON object anyway. */
g_assert(qobject_is_equal(QOBJECT(dnan), QOBJECT(dnan)) == false);
/* No unsigned overflow */
check_unequal(um42, im42);
check_unequal(um42, dm42);
check_unequal(im42, dm42);
free_all(u0, i0, d0, dnan, um42, im42, dm42);
}
static void qobject_is_equal_bool_test(void)
{
QBool *btrue_0, *btrue_1, *bfalse_0, *bfalse_1;
btrue_0 = qbool_from_bool(true);
btrue_1 = qbool_from_bool(true);
bfalse_0 = qbool_from_bool(false);
bfalse_1 = qbool_from_bool(false);
check_equal(btrue_0, btrue_1);
check_equal(bfalse_0, bfalse_1);
check_unequal(btrue_0, bfalse_0);
free_all(btrue_0, btrue_1, bfalse_0, bfalse_1);
}
static void qobject_is_equal_string_test(void)
{
QString *str_base, *str_whitespace_0, *str_whitespace_1, *str_whitespace_2;
QString *str_whitespace_3, *str_case, *str_built;
str_base = qstring_from_str("foo");
str_whitespace_0 = qstring_from_str(" foo");
str_whitespace_1 = qstring_from_str("foo ");
str_whitespace_2 = qstring_from_str("foo\b");
str_whitespace_3 = qstring_from_str("fooo\b");
str_case = qstring_from_str("Foo");
/* Should yield "foo" */
str_built = qstring_from_substr("form", 0, 1);
qstring_append_chr(str_built, 'o');
check_unequal(str_base, str_whitespace_0, str_whitespace_1,
str_whitespace_2, str_whitespace_3, str_case);
check_equal(str_base, str_built);
free_all(str_base, str_whitespace_0, str_whitespace_1, str_whitespace_2,
str_whitespace_3, str_case, str_built);
}
static void qobject_is_equal_list_test(void)
{
QList *list_0, *list_1, *list_cloned;
QList *list_reordered, *list_longer, *list_shorter;
list_0 = qlist_new();
list_1 = qlist_new();
list_reordered = qlist_new();
list_longer = qlist_new();
list_shorter = qlist_new();
qlist_append_int(list_0, 1);
qlist_append_int(list_0, 2);
qlist_append_int(list_0, 3);
qlist_append_int(list_1, 1);
qlist_append_int(list_1, 2);
qlist_append_int(list_1, 3);
qlist_append_int(list_reordered, 1);
qlist_append_int(list_reordered, 3);
qlist_append_int(list_reordered, 2);
qlist_append_int(list_longer, 1);
qlist_append_int(list_longer, 2);
qlist_append_int(list_longer, 3);
qlist_append_null(list_longer);
qlist_append_int(list_shorter, 1);
qlist_append_int(list_shorter, 2);
list_cloned = qlist_copy(list_0);
check_equal(list_0, list_1, list_cloned);
check_unequal(list_0, list_reordered, list_longer, list_shorter);
/* With a NaN in it, the list should no longer compare equal to
* itself */
qlist_append(list_0, qnum_from_double(NAN));
g_assert(qobject_is_equal(QOBJECT(list_0), QOBJECT(list_0)) == false);
free_all(list_0, list_1, list_cloned, list_reordered, list_longer,
list_shorter);
}
static void qobject_is_equal_dict_test(void)
{
Error *local_err = NULL;
QDict *dict_0, *dict_1, *dict_cloned;
QDict *dict_different_key, *dict_different_value, *dict_different_null_key;
QDict *dict_longer, *dict_shorter, *dict_nested;
QDict *dict_crumpled;
dict_0 = qdict_new();
dict_1 = qdict_new();
dict_different_key = qdict_new();
dict_different_value = qdict_new();
dict_different_null_key = qdict_new();
dict_longer = qdict_new();
dict_shorter = qdict_new();
dict_nested = qdict_new();
qdict_put_int(dict_0, "f.o", 1);
qdict_put_int(dict_0, "bar", 2);
qdict_put_int(dict_0, "baz", 3);
qdict_put_null(dict_0, "null");
qdict_put_int(dict_1, "f.o", 1);
qdict_put_int(dict_1, "bar", 2);
qdict_put_int(dict_1, "baz", 3);
qdict_put_null(dict_1, "null");
qdict_put_int(dict_different_key, "F.o", 1);
qdict_put_int(dict_different_key, "bar", 2);
qdict_put_int(dict_different_key, "baz", 3);
qdict_put_null(dict_different_key, "null");
qdict_put_int(dict_different_value, "f.o", 42);
qdict_put_int(dict_different_value, "bar", 2);
qdict_put_int(dict_different_value, "baz", 3);
qdict_put_null(dict_different_value, "null");
qdict_put_int(dict_different_null_key, "f.o", 1);
qdict_put_int(dict_different_null_key, "bar", 2);
qdict_put_int(dict_different_null_key, "baz", 3);
qdict_put_null(dict_different_null_key, "none");
qdict_put_int(dict_longer, "f.o", 1);
qdict_put_int(dict_longer, "bar", 2);
qdict_put_int(dict_longer, "baz", 3);
qdict_put_int(dict_longer, "xyz", 4);
qdict_put_null(dict_longer, "null");
qdict_put_int(dict_shorter, "f.o", 1);
qdict_put_int(dict_shorter, "bar", 2);
qdict_put_int(dict_shorter, "baz", 3);
qdict_put(dict_nested, "f", qdict_new());
qdict_put_int(qdict_get_qdict(dict_nested, "f"), "o", 1);
qdict_put_int(dict_nested, "bar", 2);
qdict_put_int(dict_nested, "baz", 3);
qdict_put_null(dict_nested, "null");
dict_cloned = qdict_clone_shallow(dict_0);
check_equal(dict_0, dict_1, dict_cloned);
check_unequal(dict_0, dict_different_key, dict_different_value,
dict_different_null_key, dict_longer, dict_shorter,
dict_nested);
dict_crumpled = qobject_to_qdict(qdict_crumple(dict_1, &local_err));
g_assert(!local_err);
check_equal(dict_crumpled, dict_nested);
qdict_flatten(dict_nested);
check_equal(dict_0, dict_nested);
/* Containing an NaN value will make this dict compare unequal to
* itself */
qdict_put(dict_0, "NaN", qnum_from_double(NAN));
g_assert(qobject_is_equal(QOBJECT(dict_0), QOBJECT(dict_0)) == false);
free_all(dict_0, dict_1, dict_cloned, dict_different_key,
dict_different_value, dict_different_null_key, dict_longer,
dict_shorter, dict_nested, dict_crumpled);
}
static void qobject_is_equal_conversion_test(void)
{
QNum *u0, *i0, *d0;
QString *s0, *s_empty;
QBool *bfalse;
u0 = qnum_from_uint(0u);
i0 = qnum_from_int(0);
d0 = qnum_from_double(0.0);
s0 = qstring_from_str("0");
s_empty = qstring_new();
bfalse = qbool_from_bool(false);
/* No automatic type conversion */
check_unequal(u0, s0, s_empty, bfalse, qnull(), NULL);
check_unequal(i0, s0, s_empty, bfalse, qnull(), NULL);
check_unequal(d0, s0, s_empty, bfalse, qnull(), NULL);
free_all(u0, i0, d0, s0, s_empty, bfalse);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/public/qobject_is_equal_null",
qobject_is_equal_null_test);
g_test_add_func("/public/qobject_is_equal_num", qobject_is_equal_num_test);
g_test_add_func("/public/qobject_is_equal_bool",
qobject_is_equal_bool_test);
g_test_add_func("/public/qobject_is_equal_string",
qobject_is_equal_string_test);
g_test_add_func("/public/qobject_is_equal_list",
qobject_is_equal_list_test);
g_test_add_func("/public/qobject_is_equal_dict",
qobject_is_equal_dict_test);
g_test_add_func("/public/qobject_is_equal_conversion",
qobject_is_equal_conversion_test);
return g_test_run();
}

View file

@ -110,6 +110,33 @@ for offset in $TEST_OFFSETS; do
io_zero readv $(( offset + 64 * 1024 + 65536 * 4 )) 65536 65536 1
done
_check_test_img
_cleanup
TEST_IMG=$TEST_IMG_SAVE
echo
echo 'Testing failing commit'
echo
# Create an image with a null backing file to which committing will fail (with
# ENOSPC so we can distinguish the result from some generic EIO which may be
# generated anywhere in the block layer)
_make_test_img -b "json:{'driver': 'raw',
'file': {
'driver': 'blkdebug',
'inject-error': [{
'event': 'write_aio',
'errno': 28,
'once': true
}],
'image': {
'driver': 'null-co'
}}}"
# Just write anything so committing will not be a no-op
$QEMU_IO -c 'writev 0 64k' "$TEST_IMG" | _filter_qemu_io
$QEMU_IMG commit "$TEST_IMG"
_cleanup_test_img
# success, all done
echo "*** done"

View file

@ -1075,4 +1075,21 @@ read 65536/65536 bytes at offset 4295098368
read 65536/65536 bytes at offset 4295294976
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
No errors were found on the image.
Testing failing commit
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1073741824 backing_file=json:{'driver': 'raw',,
'file': {
'driver': 'blkdebug',,
'inject-error': [{
'event': 'write_aio',,
'errno': 28,,
'once': true
}],,
'image': {
'driver': 'null-co'
}}}
wrote 65536/65536 bytes at offset 0
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
qemu-img: Block job failed: No space left on device
*** done

View file

@ -301,6 +301,131 @@ _make_test_img 64M
poke_file "$TEST_IMG" "48" "\x00\x00\x00\x00\x00\x00\x00\x00"
$QEMU_IO -c "write 0 64k" "$TEST_IMG" | _filter_qemu_io
echo
echo "=== Testing dirty corrupt image ==="
echo
_make_test_img 64M
# Let the refblock appear unaligned
poke_file "$TEST_IMG" "$rt_offset" "\x00\x00\x00\x00\xff\xff\x2a\x00"
# Mark the image dirty, thus forcing an automatic check when opening it
poke_file "$TEST_IMG" 72 "\x00\x00\x00\x00\x00\x00\x00\x01"
# Open the image (qemu should refuse to do so)
$QEMU_IO -c close "$TEST_IMG" 2>&1 | _filter_testdir | _filter_imgfmt
echo '--- Repairing ---'
# The actual repair should have happened (because of the dirty bit),
# but some cleanup may have failed (like freeing the old reftable)
# because the image was already marked corrupt by that point
_check_test_img -r all
echo
echo "=== Writing to an unaligned preallocated zero cluster ==="
echo
_make_test_img 64M
# Allocate the L2 table
$QEMU_IO -c "write 0 64k" -c "discard 0 64k" "$TEST_IMG" | _filter_qemu_io
# Pretend there is a preallocated zero cluster somewhere inside the
# image header
poke_file "$TEST_IMG" "$l2_offset" "\x80\x00\x00\x00\x00\x00\x2a\x01"
# Let's write to it!
$QEMU_IO -c "write 0 64k" "$TEST_IMG" | _filter_qemu_io
# Can't repair this yet (TODO: We can just deallocate the cluster)
echo
echo '=== Discarding with an unaligned refblock ==='
echo
_make_test_img 64M
$QEMU_IO -c "write 0 128k" "$TEST_IMG" | _filter_qemu_io
# Make our refblock unaligned
poke_file "$TEST_IMG" "$(($rt_offset))" "\x00\x00\x00\x00\x00\x00\x2a\x00"
# Now try to discard something that will be submitted as two requests
# (main part + tail)
$QEMU_IO -c "discard 0 65537" "$TEST_IMG"
echo '--- Repairing ---'
# Fails the first repair because the corruption prevents the check
# function from double-checking
# (Using -q for the first invocation, because otherwise the
# double-check error message appears above the summary for some
# reason -- so let's just hide the summary)
_check_test_img -q -r all
_check_test_img -r all
echo
echo "=== Discarding an out-of-bounds refblock ==="
echo
_make_test_img 64M
# Pretend there's a refblock really up high
poke_file "$TEST_IMG" "$(($rt_offset+8))" "\x00\xff\xff\xff\x00\x00\x00\x00"
# Let's try to shrink the qcow2 image so that the block driver tries
# to discard that refblock (and see what happens!)
$QEMU_IMG resize --shrink "$TEST_IMG" 32M
echo '--- Checking and retrying ---'
# Image should not be resized
_img_info | grep 'virtual size'
# But it should pass this check, because the "partial" resize has
# already overwritten refblocks past the end
_check_test_img -r all
# So let's try again
$QEMU_IMG resize --shrink "$TEST_IMG" 32M
_img_info | grep 'virtual size'
echo
echo "=== Discarding a non-covered in-bounds refblock ==="
echo
IMGOPTS='refcount_bits=1' _make_test_img 64M
# Pretend there's a refblock somewhere where there is no refblock to
# cover it (but the covering refblock has a valid index in the
# reftable)
# Every refblock covers 65536 * 8 * 65536 = 32 GB, so we have to point
# to 0x10_0000_0000 (64G) to point to the third refblock
poke_file "$TEST_IMG" "$(($rt_offset+8))" "\x00\x00\x00\x10\x00\x00\x00\x00"
$QEMU_IMG resize --shrink "$TEST_IMG" 32M
echo '--- Checking and retrying ---'
# Image should not be resized
_img_info | grep 'virtual size'
# But it should pass this check, because the "partial" resize has
# already overwritten refblocks past the end
_check_test_img -r all
# So let's try again
$QEMU_IMG resize --shrink "$TEST_IMG" 32M
_img_info | grep 'virtual size'
echo
echo "=== Discarding a refblock covered by an unaligned refblock ==="
echo
IMGOPTS='refcount_bits=1' _make_test_img 64M
# Same as above
poke_file "$TEST_IMG" "$(($rt_offset+8))" "\x00\x00\x00\x10\x00\x00\x00\x00"
# But now we actually "create" an unaligned third refblock
poke_file "$TEST_IMG" "$(($rt_offset+16))" "\x00\x00\x00\x00\x00\x00\x02\x00"
$QEMU_IMG resize --shrink "$TEST_IMG" 32M
echo '--- Repairing ---'
# Fails the first repair because the corruption prevents the check
# function from double-checking
# (Using -q for the first invocation, because otherwise the
# double-check error message appears above the summary for some
# reason -- so let's just hide the summary)
_check_test_img -q -r all
_check_test_img -r all
# success, all done
echo "*** done"
rm -f $seq.full

View file

@ -284,4 +284,119 @@ No errors were found on the image.
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
qcow2: Marking image as corrupt: Preventing invalid allocation of L2 table at offset 0; further corruption events will be suppressed
write failed: Input/output error
=== Testing dirty corrupt image ===
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
ERROR refcount block 0 is not cluster aligned; refcount table entry corrupted
IMGFMT: Marking image as corrupt: Refblock offset 0xffff2a00 unaligned (reftable index: 0); further corruption events will be suppressed
Can't get refcount for cluster 0: Input/output error
Can't get refcount for cluster 1: Input/output error
Can't get refcount for cluster 2: Input/output error
Can't get refcount for cluster 3: Input/output error
Rebuilding refcount structure
Repairing cluster 1 refcount=1 reference=0
can't open device TEST_DIR/t.IMGFMT: Could not repair dirty image: Input/output error
--- Repairing ---
Leaked cluster 1 refcount=1 reference=0
Repairing cluster 1 refcount=1 reference=0
The following inconsistencies were found and repaired:
1 leaked clusters
0 corruptions
Double checking the fixed image now...
No errors were found on the image.
=== Writing to an unaligned preallocated zero cluster ===
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
wrote 65536/65536 bytes at offset 0
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
discard 65536/65536 bytes at offset 0
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
qcow2: Marking image as corrupt: Preallocated zero cluster offset 0x2a00 unaligned (guest offset: 0); further corruption events will be suppressed
write failed: Input/output error
=== Discarding with an unaligned refblock ===
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
wrote 131072/131072 bytes at offset 0
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
qcow2: Marking image as corrupt: Refblock offset 0x2a00 unaligned (reftable index: 0); further corruption events will be suppressed
qcow2_free_clusters failed: Input/output error
discard failed: No medium found
--- Repairing ---
ERROR refcount block 0 is not cluster aligned; refcount table entry corrupted
qcow2: Marking image as corrupt: Refblock offset 0x2a00 unaligned (reftable index: 0); further corruption events will be suppressed
Can't get refcount for cluster 0: Input/output error
Can't get refcount for cluster 1: Input/output error
Can't get refcount for cluster 2: Input/output error
Can't get refcount for cluster 3: Input/output error
Can't get refcount for cluster 4: Input/output error
Can't get refcount for cluster 5: Input/output error
Can't get refcount for cluster 6: Input/output error
Rebuilding refcount structure
Repairing cluster 1 refcount=1 reference=0
qemu-img: Check failed: No medium found
Leaked cluster 1 refcount=1 reference=0
Repairing cluster 1 refcount=1 reference=0
The following inconsistencies were found and repaired:
1 leaked clusters
0 corruptions
Double checking the fixed image now...
No errors were found on the image.
=== Discarding an out-of-bounds refblock ===
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
qcow2: Marking image as corrupt: Refblock at 0xffffff00000000 is not covered by the refcount structures; further corruption events will be suppressed
qemu-img: Failed to discard unused refblocks: Input/output error
--- Checking and retrying ---
virtual size: 64M (67108864 bytes)
No errors were found on the image.
Image resized.
virtual size: 32M (33554432 bytes)
=== Discarding a non-covered in-bounds refblock ===
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
qcow2: Marking image as corrupt: Refblock at 0x1000000000 is not covered by the refcount structures; further corruption events will be suppressed
qemu-img: Failed to discard unused refblocks: Input/output error
--- Checking and retrying ---
virtual size: 64M (67108864 bytes)
No errors were found on the image.
Image resized.
virtual size: 32M (33554432 bytes)
=== Discarding a refblock covered by an unaligned refblock ===
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
qcow2: Marking image as corrupt: Cannot get entry from refcount block cache: Offset 0x200 is unaligned; further corruption events will be suppressed
qemu-img: Failed to discard unused refblocks: Input/output error
--- Repairing ---
Repairing refcount block 1 is outside image
ERROR refcount block 2 is not cluster aligned; refcount table entry corrupted
qcow2: Marking image as corrupt: Refblock offset 0x200 unaligned (reftable index: 0x2); further corruption events will be suppressed
Can't get refcount for cluster 1048576: Input/output error
Rebuilding refcount structure
Repairing cluster 1 refcount=1 reference=0
Repairing cluster 2 refcount=1 reference=0
Repairing cluster 1048576 refcount=1 reference=0
qemu-img: Check failed: No medium found
Leaked cluster 1 refcount=1 reference=0
Leaked cluster 2 refcount=1 reference=0
Leaked cluster 1048576 refcount=1 reference=0
Repairing cluster 1 refcount=1 reference=0
Repairing cluster 2 refcount=1 reference=0
Repairing cluster 1048576 refcount=1 reference=0
The following inconsistencies were found and repaired:
3 leaked clusters
0 corruptions
Double checking the fixed image now...
No errors were found on the image.
*** done

View file

@ -102,7 +102,14 @@ echo
echo === aio=native without O_DIRECT ===
echo
run_qemu <<EOF
# Skip this test if AIO is not enabled in this build
function run_qemu_filter_aio()
{
run_qemu "$@" | \
sed -e 's/is not supported in this build/it requires cache.direct=on, which was not specified/'
}
run_qemu_filter_aio <<EOF
{ "execute": "qmp_capabilities" }
{ "execute": "blockdev-add",
"arguments": {

View file

@ -83,6 +83,15 @@ $QEMU_IO -c 'reopen -o driver=qcow2' $TEST_IMG
$QEMU_IO -c 'reopen -o file.driver=file' $TEST_IMG
$QEMU_IO -c 'reopen -o backing.driver=qcow2' $TEST_IMG
echo
echo "=== Check that reopening works with non-string options ==="
echo
# Using the json: pseudo-protocol we can create non-string options
# (Invoke 'info' just so we get some output afterwards)
IMGOPTSSYNTAX=false $QEMU_IO -f null-co -c 'reopen' -c 'info' \
"json:{'driver': 'null-co', 'size': 65536}"
# success, all done
echo "*** done"
rm -f $seq.full

View file

@ -19,4 +19,9 @@ Cannot change the option 'driver'
=== Check that unchanged driver is okay ===
=== Check that reopening works with non-string options ===
format name: null-co
format name: null-co
*** done