Block patches for softfreeze:

- iotest patches
 - Improve performance of the mirror block job in write-blocking mode
 - Limit memory usage for the backup block job
 - Add discard and write-zeroes support to the NVMe host block driver
 - Fix a bug in the mirror job
 - Prevent the qcow2 driver from creating technically non-compliant qcow2
   v3 images (where there is not enough extra data for snapshot table
   entries)
 - Allow callers of bdrv_truncate() (etc.) to determine whether the file
   must be resized to the exact given size or whether it is OK for block
   devices not to shrink
 -----BEGIN PGP SIGNATURE-----
 
 iQFGBAABCAAwFiEEkb62CjDbPohX0Rgp9AfbAGHVz0AFAl2224ESHG1yZWl0ekBy
 ZWRoYXQuY29tAAoJEPQH2wBh1c9AeXMH/RXKEX4BZYMRKCe41P18tJC9Bl2x0T20
 YeOsZVvpARlr7o/36BF2kGFF4MnL0OQ+9ELuyROX865rk/VL2rWqnHDE5oQM889a
 dFwMs+0zvNbig3iLNcw0H5OkE2mrdM+a1EUdn/lBe/39Z8dPqPxRGqIYHq38Ugdu
 emwSy1nWen7o0f71HRJfyVtI3KcrzXx71FrA/FY2yL/eHz+zRYGZj2SpAdFPkXP/
 lgaz+m0tWhnSW1QzEOXB0Gh69ULt/DczCinYmv5qUY1noW5TPPtiDNCQTts5O4ba
 oJsR3AJv5/l9m65JTmiyQSqnQfPcstrQ5FqOcSnP637cfqUFyWsvdks=
 =L7v1
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/maxreitz/tags/pull-block-2019-10-28' into staging

Block patches for softfreeze:
- iotest patches
- Improve performance of the mirror block job in write-blocking mode
- Limit memory usage for the backup block job
- Add discard and write-zeroes support to the NVMe host block driver
- Fix a bug in the mirror job
- Prevent the qcow2 driver from creating technically non-compliant qcow2
  v3 images (where there is not enough extra data for snapshot table
  entries)
- Allow callers of bdrv_truncate() (etc.) to determine whether the file
  must be resized to the exact given size or whether it is OK for block
  devices not to shrink

# gpg: Signature made Mon 28 Oct 2019 12:13:53 GMT
# gpg:                using RSA key 91BEB60A30DB3E8857D11829F407DB0061D5CF40
# gpg:                issuer "mreitz@redhat.com"
# gpg: Good signature from "Max Reitz <mreitz@redhat.com>" [full]
# Primary key fingerprint: 91BE B60A 30DB 3E88 57D1  1829 F407 DB00 61D5 CF40

* remotes/maxreitz/tags/pull-block-2019-10-28: (69 commits)
  qemu-iotests: restrict 264 to qcow2 only
  Revert "qemu-img: Check post-truncation size"
  block: Pass truncate exact=true where reasonable
  block: Let format drivers pass @exact
  block: Evaluate @exact in protocol drivers
  block: Add @exact parameter to bdrv_co_truncate()
  block: Do not truncate file node when formatting
  block/cor: Drop cor_co_truncate()
  block: Handle filter truncation like native impl.
  iotests: Test qcow2's snapshot table handling
  iotests: Add peek_file* functions
  qcow2: Fix v3 snapshot table entry compliancy
  qcow2: Repair snapshot table with too many entries
  qcow2: Fix overly long snapshot tables
  qcow2: Keep track of the snapshot table length
  qcow2: Fix broken snapshot table entries
  qcow2: Add qcow2_check_fix_snapshot_table()
  qcow2: Separate qcow2_check_read_snapshot_table()
  qcow2: Write v3-compliant snapshot list on upgrade
  qcow2: Put qcow2_upgrade() into its own function
  ...

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2019-10-28 14:40:00 +00:00
commit aaffb85335
84 changed files with 2232 additions and 486 deletions

View File

@ -1178,8 +1178,9 @@ int coroutine_fn blk_co_preadv(BlockBackend *blk, int64_t offset,
return ret; return ret;
} }
int coroutine_fn blk_co_pwritev(BlockBackend *blk, int64_t offset, int coroutine_fn blk_co_pwritev_part(BlockBackend *blk, int64_t offset,
unsigned int bytes, QEMUIOVector *qiov, unsigned int bytes,
QEMUIOVector *qiov, size_t qiov_offset,
BdrvRequestFlags flags) BdrvRequestFlags flags)
{ {
int ret; int ret;
@ -1207,11 +1208,19 @@ int coroutine_fn blk_co_pwritev(BlockBackend *blk, int64_t offset,
flags |= BDRV_REQ_FUA; flags |= BDRV_REQ_FUA;
} }
ret = bdrv_co_pwritev(blk->root, offset, bytes, qiov, flags); ret = bdrv_co_pwritev_part(blk->root, offset, bytes, qiov, qiov_offset,
flags);
bdrv_dec_in_flight(bs); bdrv_dec_in_flight(bs);
return ret; return ret;
} }
int coroutine_fn blk_co_pwritev(BlockBackend *blk, int64_t offset,
unsigned int bytes, QEMUIOVector *qiov,
BdrvRequestFlags flags)
{
return blk_co_pwritev_part(blk, offset, bytes, qiov, 0, flags);
}
typedef struct BlkRwCo { typedef struct BlkRwCo {
BlockBackend *blk; BlockBackend *blk;
int64_t offset; int64_t offset;
@ -2063,15 +2072,15 @@ int blk_pwrite_compressed(BlockBackend *blk, int64_t offset, const void *buf,
BDRV_REQ_WRITE_COMPRESSED); BDRV_REQ_WRITE_COMPRESSED);
} }
int blk_truncate(BlockBackend *blk, int64_t offset, PreallocMode prealloc, int blk_truncate(BlockBackend *blk, int64_t offset, bool exact,
Error **errp) PreallocMode prealloc, Error **errp)
{ {
if (!blk_is_available(blk)) { if (!blk_is_available(blk)) {
error_setg(errp, "No medium inserted"); error_setg(errp, "No medium inserted");
return -ENOMEDIUM; return -ENOMEDIUM;
} }
return bdrv_truncate(blk->root, offset, prealloc, errp); return bdrv_truncate(blk->root, offset, exact, prealloc, errp);
} }
static void blk_pdiscard_entry(void *opaque) static void blk_pdiscard_entry(void *opaque)

View File

@ -18,6 +18,11 @@
#include "qapi/error.h" #include "qapi/error.h"
#include "block/block-copy.h" #include "block/block-copy.h"
#include "sysemu/block-backend.h" #include "sysemu/block-backend.h"
#include "qemu/units.h"
#define BLOCK_COPY_MAX_COPY_RANGE (16 * MiB)
#define BLOCK_COPY_MAX_BUFFER (1 * MiB)
#define BLOCK_COPY_MAX_MEM (128 * MiB)
static void coroutine_fn block_copy_wait_inflight_reqs(BlockCopyState *s, static void coroutine_fn block_copy_wait_inflight_reqs(BlockCopyState *s,
int64_t start, int64_t start,
@ -61,6 +66,7 @@ void block_copy_state_free(BlockCopyState *s)
} }
bdrv_release_dirty_bitmap(s->copy_bitmap); bdrv_release_dirty_bitmap(s->copy_bitmap);
shres_destroy(s->mem);
g_free(s); g_free(s);
} }
@ -71,7 +77,8 @@ BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target,
BlockCopyState *s; BlockCopyState *s;
BdrvDirtyBitmap *copy_bitmap; BdrvDirtyBitmap *copy_bitmap;
uint32_t max_transfer = uint32_t max_transfer =
MIN_NON_ZERO(INT_MAX, MIN_NON_ZERO(source->bs->bl.max_transfer, MIN_NON_ZERO(INT_MAX,
MIN_NON_ZERO(source->bs->bl.max_transfer,
target->bs->bl.max_transfer)); target->bs->bl.max_transfer));
copy_bitmap = bdrv_create_dirty_bitmap(source->bs, cluster_size, NULL, copy_bitmap = bdrv_create_dirty_bitmap(source->bs, cluster_size, NULL,
@ -89,19 +96,31 @@ BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target,
.cluster_size = cluster_size, .cluster_size = cluster_size,
.len = bdrv_dirty_bitmap_size(copy_bitmap), .len = bdrv_dirty_bitmap_size(copy_bitmap),
.write_flags = write_flags, .write_flags = write_flags,
.mem = shres_create(BLOCK_COPY_MAX_MEM),
}; };
s->copy_range_size = QEMU_ALIGN_DOWN(max_transfer, cluster_size), if (max_transfer < cluster_size) {
/* /*
* Set use_copy_range, consider the following: * copy_range does not respect max_transfer. We don't want to bother
* 1. Compression is not supported for copy_range. * with requests smaller than block-copy cluster size, so fallback to
* 2. copy_range does not respect max_transfer (it's a TODO), so we factor * buffered copying (read and write respect max_transfer on their
* that in here. If max_transfer is smaller than the job->cluster_size, * behalf).
* we do not use copy_range (in that case it's zero after aligning down
* above).
*/ */
s->use_copy_range = s->use_copy_range = false;
!(write_flags & BDRV_REQ_WRITE_COMPRESSED) && s->copy_range_size > 0; s->copy_size = cluster_size;
} else if (write_flags & BDRV_REQ_WRITE_COMPRESSED) {
/* Compression is not supported for copy_range */
s->use_copy_range = false;
s->copy_size = MAX(cluster_size, BLOCK_COPY_MAX_BUFFER);
} else {
/*
* copy_range does not respect max_transfer (it's a TODO), so we factor
* that in here.
*/
s->use_copy_range = true;
s->copy_size = MIN(MAX(cluster_size, BLOCK_COPY_MAX_COPY_RANGE),
QEMU_ALIGN_DOWN(max_transfer, cluster_size));
}
QLIST_INIT(&s->inflight_reqs); QLIST_INIT(&s->inflight_reqs);
@ -120,79 +139,71 @@ void block_copy_set_callbacks(
} }
/* /*
* Copy range to target with a bounce buffer and return the bytes copied. If * block_copy_do_copy
* error occurred, return a negative error number *
* Do copy of cluser-aligned chunk. @end is allowed to exceed s->len only to
* cover last cluster when s->len is not aligned to clusters.
*
* No sync here: nor bitmap neighter intersecting requests handling, only copy.
*
* Returns 0 on success.
*/ */
static int coroutine_fn block_copy_with_bounce_buffer(BlockCopyState *s, static int coroutine_fn block_copy_do_copy(BlockCopyState *s,
int64_t start, int64_t start, int64_t end,
int64_t end, bool *error_is_read)
bool *error_is_read,
void **bounce_buffer)
{ {
int ret; int ret;
int nbytes; int nbytes = MIN(end, s->len) - start;
void *bounce_buffer = NULL;
assert(QEMU_IS_ALIGNED(start, s->cluster_size)); assert(QEMU_IS_ALIGNED(start, s->cluster_size));
bdrv_reset_dirty_bitmap(s->copy_bitmap, start, s->cluster_size); assert(QEMU_IS_ALIGNED(end, s->cluster_size));
nbytes = MIN(s->cluster_size, s->len - start); assert(end < s->len || end == QEMU_ALIGN_UP(s->len, s->cluster_size));
if (!*bounce_buffer) {
*bounce_buffer = qemu_blockalign(s->source->bs, s->cluster_size);
}
ret = bdrv_co_pread(s->source, start, nbytes, *bounce_buffer, 0); if (s->use_copy_range) {
if (ret < 0) {
trace_block_copy_with_bounce_buffer_read_fail(s, start, ret);
if (error_is_read) {
*error_is_read = true;
}
goto fail;
}
ret = bdrv_co_pwrite(s->target, start, nbytes, *bounce_buffer,
s->write_flags);
if (ret < 0) {
trace_block_copy_with_bounce_buffer_write_fail(s, start, ret);
if (error_is_read) {
*error_is_read = false;
}
goto fail;
}
return nbytes;
fail:
bdrv_set_dirty_bitmap(s->copy_bitmap, start, s->cluster_size);
return ret;
}
/*
* Copy range to target and return the bytes copied. If error occurred, return a
* negative error number.
*/
static int coroutine_fn block_copy_with_offload(BlockCopyState *s,
int64_t start,
int64_t end)
{
int ret;
int nr_clusters;
int nbytes;
assert(QEMU_IS_ALIGNED(s->copy_range_size, s->cluster_size));
assert(QEMU_IS_ALIGNED(start, s->cluster_size));
nbytes = MIN(s->copy_range_size, MIN(end, s->len) - start);
nr_clusters = DIV_ROUND_UP(nbytes, s->cluster_size);
bdrv_reset_dirty_bitmap(s->copy_bitmap, start,
s->cluster_size * nr_clusters);
ret = bdrv_co_copy_range(s->source, start, s->target, start, nbytes, ret = bdrv_co_copy_range(s->source, start, s->target, start, nbytes,
0, s->write_flags); 0, s->write_flags);
if (ret < 0) { if (ret < 0) {
trace_block_copy_with_offload_fail(s, start, ret); trace_block_copy_copy_range_fail(s, start, ret);
bdrv_set_dirty_bitmap(s->copy_bitmap, start, s->use_copy_range = false;
s->cluster_size * nr_clusters); s->copy_size = MAX(s->cluster_size, BLOCK_COPY_MAX_BUFFER);
return ret; /* Fallback to read+write with allocated buffer */
} else {
goto out;
}
} }
return nbytes; /*
* In case of failed copy_range request above, we may proceed with buffered
* request larger than BLOCK_COPY_MAX_BUFFER. Still, further requests will
* be properly limited, so don't care too much.
*/
bounce_buffer = qemu_blockalign(s->source->bs, nbytes);
ret = bdrv_co_pread(s->source, start, nbytes, bounce_buffer, 0);
if (ret < 0) {
trace_block_copy_read_fail(s, start, ret);
if (error_is_read) {
*error_is_read = true;
}
goto out;
}
ret = bdrv_co_pwrite(s->target, start, nbytes, bounce_buffer,
s->write_flags);
if (ret < 0) {
trace_block_copy_write_fail(s, start, ret);
if (error_is_read) {
*error_is_read = false;
}
goto out;
}
out:
qemu_vfree(bounce_buffer);
return ret;
} }
/* /*
@ -271,7 +282,6 @@ int coroutine_fn block_copy(BlockCopyState *s,
{ {
int ret = 0; int ret = 0;
int64_t end = bytes + start; /* bytes */ int64_t end = bytes + start; /* bytes */
void *bounce_buffer = NULL;
int64_t status_bytes; int64_t status_bytes;
BlockCopyInFlightReq req; BlockCopyInFlightReq req;
@ -289,7 +299,7 @@ int coroutine_fn block_copy(BlockCopyState *s,
block_copy_inflight_req_begin(s, &req, start, end); block_copy_inflight_req_begin(s, &req, start, end);
while (start < end) { while (start < end) {
int64_t dirty_end; int64_t next_zero, chunk_end;
if (!bdrv_dirty_bitmap_get(s->copy_bitmap, start)) { if (!bdrv_dirty_bitmap_get(s->copy_bitmap, start)) {
trace_block_copy_skip(s, start); trace_block_copy_skip(s, start);
@ -297,10 +307,14 @@ int coroutine_fn block_copy(BlockCopyState *s,
continue; /* already copied */ continue; /* already copied */
} }
dirty_end = bdrv_dirty_bitmap_next_zero(s->copy_bitmap, start, chunk_end = MIN(end, start + s->copy_size);
(end - start));
if (dirty_end < 0) { next_zero = bdrv_dirty_bitmap_next_zero(s->copy_bitmap, start,
dirty_end = end; chunk_end - start);
if (next_zero >= 0) {
assert(next_zero > start); /* start is dirty */
assert(next_zero < chunk_end); /* no need to do MIN() */
chunk_end = next_zero;
} }
if (s->skip_unallocated) { if (s->skip_unallocated) {
@ -311,34 +325,26 @@ int coroutine_fn block_copy(BlockCopyState *s,
continue; continue;
} }
/* Clamp to known allocated region */ /* Clamp to known allocated region */
dirty_end = MIN(dirty_end, start + status_bytes); chunk_end = MIN(chunk_end, start + status_bytes);
} }
trace_block_copy_process(s, start); trace_block_copy_process(s, start);
if (s->use_copy_range) { bdrv_reset_dirty_bitmap(s->copy_bitmap, start, chunk_end - start);
ret = block_copy_with_offload(s, start, dirty_end);
if (ret < 0) { co_get_from_shres(s->mem, chunk_end - start);
s->use_copy_range = false; ret = block_copy_do_copy(s, start, chunk_end, error_is_read);
} co_put_to_shres(s->mem, chunk_end - start);
}
if (!s->use_copy_range) {
ret = block_copy_with_bounce_buffer(s, start, dirty_end,
error_is_read, &bounce_buffer);
}
if (ret < 0) { if (ret < 0) {
bdrv_set_dirty_bitmap(s->copy_bitmap, start, chunk_end - start);
break; break;
} }
start += ret; s->progress_bytes_callback(chunk_end - start, s->progress_opaque);
s->progress_bytes_callback(ret, s->progress_opaque); start = chunk_end;
ret = 0; ret = 0;
} }
if (bounce_buffer) {
qemu_vfree(bounce_buffer);
}
block_copy_inflight_req_end(&req); block_copy_inflight_req_end(&req);
return ret; return ret;

View File

@ -155,7 +155,7 @@ static int coroutine_fn commit_run(Job *job, Error **errp)
} }
if (base_len < len) { if (base_len < len) {
ret = blk_truncate(s->base, len, PREALLOC_MODE_OFF, NULL); ret = blk_truncate(s->base, len, false, PREALLOC_MODE_OFF, NULL);
if (ret) { if (ret) {
goto out; goto out;
} }
@ -471,7 +471,8 @@ int bdrv_commit(BlockDriverState *bs)
* grow the backing file image if possible. If not possible, * grow the backing file image if possible. If not possible,
* we must return an error */ * we must return an error */
if (length > backing_length) { if (length > backing_length) {
ret = blk_truncate(backing, length, PREALLOC_MODE_OFF, &local_err); ret = blk_truncate(backing, length, false, PREALLOC_MODE_OFF,
&local_err);
if (ret < 0) { if (ret < 0) {
error_report_err(local_err); error_report_err(local_err);
goto ro_cleanup; goto ro_cleanup;

View File

@ -73,13 +73,6 @@ static int64_t cor_getlength(BlockDriverState *bs)
} }
static int coroutine_fn cor_co_truncate(BlockDriverState *bs, int64_t offset,
PreallocMode prealloc, Error **errp)
{
return bdrv_co_truncate(bs->file, offset, prealloc, errp);
}
static int coroutine_fn cor_co_preadv(BlockDriverState *bs, static int coroutine_fn cor_co_preadv(BlockDriverState *bs,
uint64_t offset, uint64_t bytes, uint64_t offset, uint64_t bytes,
QEMUIOVector *qiov, int flags) QEMUIOVector *qiov, int flags)
@ -139,7 +132,6 @@ static BlockDriver bdrv_copy_on_read = {
.bdrv_child_perm = cor_child_perm, .bdrv_child_perm = cor_child_perm,
.bdrv_getlength = cor_getlength, .bdrv_getlength = cor_getlength,
.bdrv_co_truncate = cor_co_truncate,
.bdrv_co_preadv = cor_co_preadv, .bdrv_co_preadv = cor_co_preadv,
.bdrv_co_pwritev = cor_co_pwritev, .bdrv_co_pwritev = cor_co_pwritev,

View File

@ -113,8 +113,8 @@ static ssize_t block_crypto_init_func(QCryptoBlock *block,
* available to the guest, so we must take account of that * available to the guest, so we must take account of that
* which will be used by the crypto header * which will be used by the crypto header
*/ */
return blk_truncate(data->blk, data->size + headerlen, data->prealloc, return blk_truncate(data->blk, data->size + headerlen, false,
errp); data->prealloc, errp);
} }
@ -297,7 +297,7 @@ static int block_crypto_co_create_generic(BlockDriverState *bs,
} }
static int coroutine_fn static int coroutine_fn
block_crypto_co_truncate(BlockDriverState *bs, int64_t offset, block_crypto_co_truncate(BlockDriverState *bs, int64_t offset, bool exact,
PreallocMode prealloc, Error **errp) PreallocMode prealloc, Error **errp)
{ {
BlockCrypto *crypto = bs->opaque; BlockCrypto *crypto = bs->opaque;
@ -311,7 +311,7 @@ block_crypto_co_truncate(BlockDriverState *bs, int64_t offset,
offset += payload_offset; offset += payload_offset;
return bdrv_co_truncate(bs->file, offset, prealloc, errp); return bdrv_co_truncate(bs->file, offset, exact, prealloc, errp);
} }
static void block_crypto_close(BlockDriverState *bs) static void block_crypto_close(BlockDriverState *bs)

View File

@ -2020,7 +2020,8 @@ raw_regular_truncate(BlockDriverState *bs, int fd, int64_t offset,
} }
static int coroutine_fn raw_co_truncate(BlockDriverState *bs, int64_t offset, static int coroutine_fn raw_co_truncate(BlockDriverState *bs, int64_t offset,
PreallocMode prealloc, Error **errp) bool exact, PreallocMode prealloc,
Error **errp)
{ {
BDRVRawState *s = bs->opaque; BDRVRawState *s = bs->opaque;
struct stat st; struct stat st;
@ -2033,6 +2034,7 @@ static int coroutine_fn raw_co_truncate(BlockDriverState *bs, int64_t offset,
} }
if (S_ISREG(st.st_mode)) { if (S_ISREG(st.st_mode)) {
/* Always resizes to the exact @offset */
return raw_regular_truncate(bs, s->fd, offset, prealloc, errp); return raw_regular_truncate(bs, s->fd, offset, prealloc, errp);
} }
@ -2043,7 +2045,12 @@ static int coroutine_fn raw_co_truncate(BlockDriverState *bs, int64_t offset,
} }
if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) { if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) {
if (offset > raw_getlength(bs)) { int64_t cur_length = raw_getlength(bs);
if (offset != cur_length && exact) {
error_setg(errp, "Cannot resize device files");
return -ENOTSUP;
} else if (offset > cur_length) {
error_setg(errp, "Cannot grow device files"); error_setg(errp, "Cannot grow device files");
return -EINVAL; return -EINVAL;
} }

View File

@ -468,7 +468,8 @@ static void raw_close(BlockDriverState *bs)
} }
static int coroutine_fn raw_co_truncate(BlockDriverState *bs, int64_t offset, static int coroutine_fn raw_co_truncate(BlockDriverState *bs, int64_t offset,
PreallocMode prealloc, Error **errp) bool exact, PreallocMode prealloc,
Error **errp)
{ {
BDRVRawState *s = bs->opaque; BDRVRawState *s = bs->opaque;
LONG low, high; LONG low, high;

View File

@ -1225,6 +1225,7 @@ static coroutine_fn int qemu_gluster_co_rw(BlockDriverState *bs,
static coroutine_fn int qemu_gluster_co_truncate(BlockDriverState *bs, static coroutine_fn int qemu_gluster_co_truncate(BlockDriverState *bs,
int64_t offset, int64_t offset,
bool exact,
PreallocMode prealloc, PreallocMode prealloc,
Error **errp) Error **errp)
{ {

View File

@ -3291,8 +3291,12 @@ static void bdrv_parent_cb_resize(BlockDriverState *bs)
/** /**
* Truncate file to 'offset' bytes (needed only for file protocols) * Truncate file to 'offset' bytes (needed only for file protocols)
*
* If 'exact' is true, the file must be resized to exactly the given
* 'offset'. Otherwise, it is sufficient for the node to be at least
* 'offset' bytes in length.
*/ */
int coroutine_fn bdrv_co_truncate(BdrvChild *child, int64_t offset, int coroutine_fn bdrv_co_truncate(BdrvChild *child, int64_t offset, bool exact,
PreallocMode prealloc, Error **errp) PreallocMode prealloc, Error **errp)
{ {
BlockDriverState *bs = child->bs; BlockDriverState *bs = child->bs;
@ -3347,20 +3351,19 @@ int coroutine_fn bdrv_co_truncate(BdrvChild *child, int64_t offset,
goto out; goto out;
} }
if (!drv->bdrv_co_truncate) { if (drv->bdrv_co_truncate) {
if (bs->file && drv->is_filter) { ret = drv->bdrv_co_truncate(bs, offset, exact, prealloc, errp);
ret = bdrv_co_truncate(bs->file, offset, prealloc, errp); } else if (bs->file && drv->is_filter) {
goto out; ret = bdrv_co_truncate(bs->file, offset, exact, prealloc, errp);
} } else {
error_setg(errp, "Image format driver does not support resize"); error_setg(errp, "Image format driver does not support resize");
ret = -ENOTSUP; ret = -ENOTSUP;
goto out; goto out;
} }
ret = drv->bdrv_co_truncate(bs, offset, prealloc, errp);
if (ret < 0) { if (ret < 0) {
goto out; goto out;
} }
ret = refresh_total_sectors(bs, offset >> BDRV_SECTOR_BITS); ret = refresh_total_sectors(bs, offset >> BDRV_SECTOR_BITS);
if (ret < 0) { if (ret < 0) {
error_setg_errno(errp, -ret, "Could not refresh total sector count"); error_setg_errno(errp, -ret, "Could not refresh total sector count");
@ -3382,6 +3385,7 @@ out:
typedef struct TruncateCo { typedef struct TruncateCo {
BdrvChild *child; BdrvChild *child;
int64_t offset; int64_t offset;
bool exact;
PreallocMode prealloc; PreallocMode prealloc;
Error **errp; Error **errp;
int ret; int ret;
@ -3390,18 +3394,19 @@ typedef struct TruncateCo {
static void coroutine_fn bdrv_truncate_co_entry(void *opaque) static void coroutine_fn bdrv_truncate_co_entry(void *opaque)
{ {
TruncateCo *tco = opaque; TruncateCo *tco = opaque;
tco->ret = bdrv_co_truncate(tco->child, tco->offset, tco->prealloc, tco->ret = bdrv_co_truncate(tco->child, tco->offset, tco->exact,
tco->errp); tco->prealloc, tco->errp);
aio_wait_kick(); aio_wait_kick();
} }
int bdrv_truncate(BdrvChild *child, int64_t offset, PreallocMode prealloc, int bdrv_truncate(BdrvChild *child, int64_t offset, bool exact,
Error **errp) PreallocMode prealloc, Error **errp)
{ {
Coroutine *co; Coroutine *co;
TruncateCo tco = { TruncateCo tco = {
.child = child, .child = child,
.offset = offset, .offset = offset,
.exact = exact,
.prealloc = prealloc, .prealloc = prealloc,
.errp = errp, .errp = errp,
.ret = NOT_DONE, .ret = NOT_DONE,

View File

@ -2123,9 +2123,11 @@ static void iscsi_reopen_commit(BDRVReopenState *reopen_state)
} }
static int coroutine_fn iscsi_co_truncate(BlockDriverState *bs, int64_t offset, static int coroutine_fn iscsi_co_truncate(BlockDriverState *bs, int64_t offset,
PreallocMode prealloc, Error **errp) bool exact, PreallocMode prealloc,
Error **errp)
{ {
IscsiLun *iscsilun = bs->opaque; IscsiLun *iscsilun = bs->opaque;
int64_t cur_length;
Error *local_err = NULL; Error *local_err = NULL;
if (prealloc != PREALLOC_MODE_OFF) { if (prealloc != PREALLOC_MODE_OFF) {
@ -2145,7 +2147,11 @@ static int coroutine_fn iscsi_co_truncate(BlockDriverState *bs, int64_t offset,
return -EIO; return -EIO;
} }
if (offset > iscsi_getlength(bs)) { cur_length = iscsi_getlength(bs);
if (offset != cur_length && exact) {
error_setg(errp, "Cannot resize iSCSI devices");
return -ENOTSUP;
} else if (offset > cur_length) {
error_setg(errp, "Cannot grow iSCSI devices"); error_setg(errp, "Cannot grow iSCSI devices");
return -EINVAL; return -EINVAL;
} }

View File

@ -620,11 +620,11 @@ static int mirror_exit_common(Job *job)
{ {
MirrorBlockJob *s = container_of(job, MirrorBlockJob, common.job); MirrorBlockJob *s = container_of(job, MirrorBlockJob, common.job);
BlockJob *bjob = &s->common; BlockJob *bjob = &s->common;
MirrorBDSOpaque *bs_opaque = s->mirror_top_bs->opaque; MirrorBDSOpaque *bs_opaque;
AioContext *replace_aio_context = NULL; AioContext *replace_aio_context = NULL;
BlockDriverState *src = s->mirror_top_bs->backing->bs; BlockDriverState *src;
BlockDriverState *target_bs = blk_bs(s->target); BlockDriverState *target_bs;
BlockDriverState *mirror_top_bs = s->mirror_top_bs; BlockDriverState *mirror_top_bs;
Error *local_err = NULL; Error *local_err = NULL;
bool abort = job->ret < 0; bool abort = job->ret < 0;
int ret = 0; int ret = 0;
@ -634,6 +634,11 @@ static int mirror_exit_common(Job *job)
} }
s->prepared = true; s->prepared = true;
mirror_top_bs = s->mirror_top_bs;
bs_opaque = mirror_top_bs->opaque;
src = mirror_top_bs->backing->bs;
target_bs = blk_bs(s->target);
if (bdrv_chain_contains(src, target_bs)) { if (bdrv_chain_contains(src, target_bs)) {
bdrv_unfreeze_backing_chain(mirror_top_bs, target_bs); bdrv_unfreeze_backing_chain(mirror_top_bs, target_bs);
} }
@ -873,8 +878,8 @@ static int coroutine_fn mirror_run(Job *job, Error **errp)
} }
if (s->bdev_length > base_length) { if (s->bdev_length > base_length) {
ret = blk_truncate(s->target, s->bdev_length, PREALLOC_MODE_OFF, ret = blk_truncate(s->target, s->bdev_length, false,
NULL); PREALLOC_MODE_OFF, NULL);
if (ret < 0) { if (ret < 0) {
goto immediate_exit; goto immediate_exit;
} }
@ -1181,56 +1186,78 @@ do_sync_target_write(MirrorBlockJob *job, MirrorMethod method,
uint64_t offset, uint64_t bytes, uint64_t offset, uint64_t bytes,
QEMUIOVector *qiov, int flags) QEMUIOVector *qiov, int flags)
{ {
QEMUIOVector target_qiov;
uint64_t dirty_offset = offset;
uint64_t dirty_bytes;
if (qiov) {
qemu_iovec_init(&target_qiov, qiov->niov);
}
while (true) {
bool valid_area;
int ret; int ret;
size_t qiov_offset = 0;
int64_t bitmap_offset, bitmap_end;
bdrv_dirty_bitmap_lock(job->dirty_bitmap); if (!QEMU_IS_ALIGNED(offset, job->granularity) &&
dirty_bytes = MIN(offset + bytes - dirty_offset, INT_MAX); bdrv_dirty_bitmap_get(job->dirty_bitmap, offset))
valid_area = bdrv_dirty_bitmap_next_dirty_area(job->dirty_bitmap, {
&dirty_offset, /*
&dirty_bytes); * Dirty unaligned padding: ignore it.
if (!valid_area) { *
bdrv_dirty_bitmap_unlock(job->dirty_bitmap); * Reasoning:
break; * 1. If we copy it, we can't reset corresponding bit in
* dirty_bitmap as there may be some "dirty" bytes still not
* copied.
* 2. It's already dirty, so skipping it we don't diverge mirror
* progress.
*
* Note, that because of this, guest write may have no contribution
* into mirror converge, but that's not bad, as we have background
* process of mirroring. If under some bad circumstances (high guest
* IO load) background process starve, we will not converge anyway,
* even if each write will contribute, as guest is not guaranteed to
* rewrite the whole disk.
*/
qiov_offset = QEMU_ALIGN_UP(offset, job->granularity) - offset;
if (bytes <= qiov_offset) {
/* nothing to do after shrink */
return;
}
offset += qiov_offset;
bytes -= qiov_offset;
} }
bdrv_reset_dirty_bitmap_locked(job->dirty_bitmap, if (!QEMU_IS_ALIGNED(offset + bytes, job->granularity) &&
dirty_offset, dirty_bytes); bdrv_dirty_bitmap_get(job->dirty_bitmap, offset + bytes - 1))
bdrv_dirty_bitmap_unlock(job->dirty_bitmap); {
uint64_t tail = (offset + bytes) % job->granularity;
job_progress_increase_remaining(&job->common.job, dirty_bytes); if (bytes <= tail) {
/* nothing to do after shrink */
assert(dirty_offset - offset <= SIZE_MAX); return;
if (qiov) {
qemu_iovec_reset(&target_qiov);
qemu_iovec_concat(&target_qiov, qiov,
dirty_offset - offset, dirty_bytes);
} }
bytes -= tail;
}
/*
* Tails are either clean or shrunk, so for bitmap resetting
* we safely align the range down.
*/
bitmap_offset = QEMU_ALIGN_UP(offset, job->granularity);
bitmap_end = QEMU_ALIGN_DOWN(offset + bytes, job->granularity);
if (bitmap_offset < bitmap_end) {
bdrv_reset_dirty_bitmap(job->dirty_bitmap, bitmap_offset,
bitmap_end - bitmap_offset);
}
job_progress_increase_remaining(&job->common.job, bytes);
switch (method) { switch (method) {
case MIRROR_METHOD_COPY: case MIRROR_METHOD_COPY:
ret = blk_co_pwritev(job->target, dirty_offset, dirty_bytes, ret = blk_co_pwritev_part(job->target, offset, bytes,
qiov ? &target_qiov : NULL, flags); qiov, qiov_offset, flags);
break; break;
case MIRROR_METHOD_ZERO: case MIRROR_METHOD_ZERO:
assert(!qiov); assert(!qiov);
ret = blk_co_pwrite_zeroes(job->target, dirty_offset, dirty_bytes, ret = blk_co_pwrite_zeroes(job->target, offset, bytes, flags);
flags);
break; break;
case MIRROR_METHOD_DISCARD: case MIRROR_METHOD_DISCARD:
assert(!qiov); assert(!qiov);
ret = blk_co_pdiscard(job->target, dirty_offset, dirty_bytes); ret = blk_co_pdiscard(job->target, offset, bytes);
break; break;
default: default:
@ -1238,11 +1265,20 @@ do_sync_target_write(MirrorBlockJob *job, MirrorMethod method,
} }
if (ret >= 0) { if (ret >= 0) {
job_progress_update(&job->common.job, dirty_bytes); job_progress_update(&job->common.job, bytes);
} else { } else {
BlockErrorAction action; BlockErrorAction action;
bdrv_set_dirty_bitmap(job->dirty_bitmap, dirty_offset, dirty_bytes); /*
* We failed, so we should mark dirty the whole area, aligned up.
* Note that we don't care about shrunk tails if any: they were dirty
* at function start, and they must be still dirty, as we've locked
* the region for in-flight op.
*/
bitmap_offset = QEMU_ALIGN_DOWN(offset, job->granularity);
bitmap_end = QEMU_ALIGN_UP(offset + bytes, job->granularity);
bdrv_set_dirty_bitmap(job->dirty_bitmap, bitmap_offset,
bitmap_end - bitmap_offset);
job->actively_synced = false; job->actively_synced = false;
action = mirror_error_action(job, false, -ret); action = mirror_error_action(job, false, -ret);
@ -1250,16 +1286,8 @@ do_sync_target_write(MirrorBlockJob *job, MirrorMethod method,
if (!job->ret) { if (!job->ret) {
job->ret = ret; job->ret = ret;
} }
break;
} }
} }
dirty_offset += dirty_bytes;
}
if (qiov) {
qemu_iovec_destroy(&target_qiov);
}
} }
static MirrorOp *coroutine_fn active_write_prepare(MirrorBlockJob *s, static MirrorOp *coroutine_fn active_write_prepare(MirrorBlockJob *s,
@ -1465,15 +1493,6 @@ static void bdrv_mirror_top_child_perm(BlockDriverState *bs, BdrvChild *c,
*nshared = BLK_PERM_ALL; *nshared = BLK_PERM_ALL;
} }
static void bdrv_mirror_top_refresh_limits(BlockDriverState *bs, Error **errp)
{
MirrorBDSOpaque *s = bs->opaque;
if (s && s->job && s->job->copy_mode == MIRROR_COPY_MODE_WRITE_BLOCKING) {
bs->bl.request_alignment = s->job->granularity;
}
}
/* Dummy node that provides consistent read to its users without requiring it /* Dummy node that provides consistent read to its users without requiring it
* from its backing file and that allows writes on the backing file chain. */ * from its backing file and that allows writes on the backing file chain. */
static BlockDriver bdrv_mirror_top = { static BlockDriver bdrv_mirror_top = {
@ -1486,7 +1505,6 @@ static BlockDriver bdrv_mirror_top = {
.bdrv_co_block_status = bdrv_co_block_status_from_backing, .bdrv_co_block_status = bdrv_co_block_status_from_backing,
.bdrv_refresh_filename = bdrv_mirror_top_refresh_filename, .bdrv_refresh_filename = bdrv_mirror_top_refresh_filename,
.bdrv_child_perm = bdrv_mirror_top_child_perm, .bdrv_child_perm = bdrv_mirror_top_child_perm,
.bdrv_refresh_limits = bdrv_mirror_top_refresh_limits,
}; };
static BlockJob *mirror_start_job( static BlockJob *mirror_start_job(
@ -1634,29 +1652,13 @@ static BlockJob *mirror_start_job(
s->should_complete = true; s->should_complete = true;
} }
/*
* Must be called before we start tracking writes, but after
*
* ((MirrorBlockJob *)
* ((MirrorBDSOpaque *)
* mirror_top_bs->opaque
* )->job
* )->copy_mode
*
* has the correct value.
* (We start tracking writes as of the following
* bdrv_create_dirty_bitmap() call.)
*/
bdrv_refresh_limits(mirror_top_bs, &local_err);
if (local_err) {
error_propagate(errp, local_err);
goto fail;
}
s->dirty_bitmap = bdrv_create_dirty_bitmap(bs, granularity, NULL, errp); s->dirty_bitmap = bdrv_create_dirty_bitmap(bs, granularity, NULL, errp);
if (!s->dirty_bitmap) { if (!s->dirty_bitmap) {
goto fail; goto fail;
} }
if (s->copy_mode == MIRROR_COPY_MODE_WRITE_BLOCKING) {
bdrv_disable_dirty_bitmap(s->dirty_bitmap);
}
ret = block_job_add_bdrv(&s->common, "source", bs, 0, ret = block_job_add_bdrv(&s->common, "source", bs, 0,
BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE | BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE |

View File

@ -752,7 +752,7 @@ static int64_t nfs_get_allocated_file_size(BlockDriverState *bs)
} }
static int coroutine_fn static int coroutine_fn
nfs_file_co_truncate(BlockDriverState *bs, int64_t offset, nfs_file_co_truncate(BlockDriverState *bs, int64_t offset, bool exact,
PreallocMode prealloc, Error **errp) PreallocMode prealloc, Error **errp)
{ {
NFSClient *client = bs->opaque; NFSClient *client = bs->opaque;

View File

@ -112,6 +112,9 @@ typedef struct {
uint64_t max_transfer; uint64_t max_transfer;
bool plugged; bool plugged;
bool supports_write_zeroes;
bool supports_discard;
CoMutex dma_map_lock; CoMutex dma_map_lock;
CoQueue dma_flush_queue; CoQueue dma_flush_queue;
@ -423,6 +426,7 @@ static void nvme_identify(BlockDriverState *bs, int namespace, Error **errp)
NvmeIdNs *idns; NvmeIdNs *idns;
NvmeLBAF *lbaf; NvmeLBAF *lbaf;
uint8_t *resp; uint8_t *resp;
uint16_t oncs;
int r; int r;
uint64_t iova; uint64_t iova;
NvmeCmd cmd = { NvmeCmd cmd = {
@ -460,6 +464,10 @@ static void nvme_identify(BlockDriverState *bs, int namespace, Error **errp)
s->max_transfer = MIN_NON_ZERO(s->max_transfer, s->max_transfer = MIN_NON_ZERO(s->max_transfer,
s->page_size / sizeof(uint64_t) * s->page_size); s->page_size / sizeof(uint64_t) * s->page_size);
oncs = le16_to_cpu(idctrl->oncs);
s->supports_write_zeroes = !!(oncs & NVME_ONCS_WRITE_ZEROS);
s->supports_discard = !!(oncs & NVME_ONCS_DSM);
memset(resp, 0, 4096); memset(resp, 0, 4096);
cmd.cdw10 = 0; cmd.cdw10 = 0;
@ -472,6 +480,12 @@ static void nvme_identify(BlockDriverState *bs, int namespace, Error **errp)
s->nsze = le64_to_cpu(idns->nsze); s->nsze = le64_to_cpu(idns->nsze);
lbaf = &idns->lbaf[NVME_ID_NS_FLBAS_INDEX(idns->flbas)]; lbaf = &idns->lbaf[NVME_ID_NS_FLBAS_INDEX(idns->flbas)];
if (NVME_ID_NS_DLFEAT_WRITE_ZEROES(idns->dlfeat) &&
NVME_ID_NS_DLFEAT_READ_BEHAVIOR(idns->dlfeat) ==
NVME_ID_NS_DLFEAT_READ_BEHAVIOR_ZEROES) {
bs->supported_write_flags |= BDRV_REQ_MAY_UNMAP;
}
if (lbaf->ms) { if (lbaf->ms) {
error_setg(errp, "Namespaces with metadata are not yet supported"); error_setg(errp, "Namespaces with metadata are not yet supported");
goto out; goto out;
@ -766,6 +780,8 @@ static int nvme_file_open(BlockDriverState *bs, QDict *options, int flags,
int ret; int ret;
BDRVNVMeState *s = bs->opaque; BDRVNVMeState *s = bs->opaque;
bs->supported_write_flags = BDRV_REQ_FUA;
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort); opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
qemu_opts_absorb_qdict(opts, options, &error_abort); qemu_opts_absorb_qdict(opts, options, &error_abort);
device = qemu_opt_get(opts, NVME_BLOCK_OPT_DEVICE); device = qemu_opt_get(opts, NVME_BLOCK_OPT_DEVICE);
@ -794,7 +810,6 @@ static int nvme_file_open(BlockDriverState *bs, QDict *options, int flags,
goto fail; goto fail;
} }
} }
bs->supported_write_flags = BDRV_REQ_FUA;
return 0; return 0;
fail: fail:
nvme_close(bs); nvme_close(bs);
@ -1088,6 +1103,140 @@ static coroutine_fn int nvme_co_flush(BlockDriverState *bs)
} }
static coroutine_fn int nvme_co_pwrite_zeroes(BlockDriverState *bs,
int64_t offset,
int bytes,
BdrvRequestFlags flags)
{
BDRVNVMeState *s = bs->opaque;
NVMeQueuePair *ioq = s->queues[1];
NVMeRequest *req;
uint32_t cdw12 = ((bytes >> s->blkshift) - 1) & 0xFFFF;
if (!s->supports_write_zeroes) {
return -ENOTSUP;
}
NvmeCmd cmd = {
.opcode = NVME_CMD_WRITE_ZEROS,
.nsid = cpu_to_le32(s->nsid),
.cdw10 = cpu_to_le32((offset >> s->blkshift) & 0xFFFFFFFF),
.cdw11 = cpu_to_le32(((offset >> s->blkshift) >> 32) & 0xFFFFFFFF),
};
NVMeCoData data = {
.ctx = bdrv_get_aio_context(bs),
.ret = -EINPROGRESS,
};
if (flags & BDRV_REQ_MAY_UNMAP) {
cdw12 |= (1 << 25);
}
if (flags & BDRV_REQ_FUA) {
cdw12 |= (1 << 30);
}
cmd.cdw12 = cpu_to_le32(cdw12);
trace_nvme_write_zeroes(s, offset, bytes, flags);
assert(s->nr_queues > 1);
req = nvme_get_free_req(ioq);
assert(req);
nvme_submit_command(s, ioq, req, &cmd, nvme_rw_cb, &data);
data.co = qemu_coroutine_self();
while (data.ret == -EINPROGRESS) {
qemu_coroutine_yield();
}
trace_nvme_rw_done(s, true, offset, bytes, data.ret);
return data.ret;
}
static int coroutine_fn nvme_co_pdiscard(BlockDriverState *bs,
int64_t offset,
int bytes)
{
BDRVNVMeState *s = bs->opaque;
NVMeQueuePair *ioq = s->queues[1];
NVMeRequest *req;
NvmeDsmRange *buf;
QEMUIOVector local_qiov;
int ret;
NvmeCmd cmd = {
.opcode = NVME_CMD_DSM,
.nsid = cpu_to_le32(s->nsid),
.cdw10 = cpu_to_le32(0), /*number of ranges - 0 based*/
.cdw11 = cpu_to_le32(1 << 2), /*deallocate bit*/
};
NVMeCoData data = {
.ctx = bdrv_get_aio_context(bs),
.ret = -EINPROGRESS,
};
if (!s->supports_discard) {
return -ENOTSUP;
}
assert(s->nr_queues > 1);
buf = qemu_try_blockalign0(bs, s->page_size);
if (!buf) {
return -ENOMEM;
}
buf->nlb = cpu_to_le32(bytes >> s->blkshift);
buf->slba = cpu_to_le64(offset >> s->blkshift);
buf->cattr = 0;
qemu_iovec_init(&local_qiov, 1);
qemu_iovec_add(&local_qiov, buf, 4096);
req = nvme_get_free_req(ioq);
assert(req);
qemu_co_mutex_lock(&s->dma_map_lock);
ret = nvme_cmd_map_qiov(bs, &cmd, req, &local_qiov);
qemu_co_mutex_unlock(&s->dma_map_lock);
if (ret) {
req->busy = false;
goto out;
}
trace_nvme_dsm(s, offset, bytes);
nvme_submit_command(s, ioq, req, &cmd, nvme_rw_cb, &data);
data.co = qemu_coroutine_self();
while (data.ret == -EINPROGRESS) {
qemu_coroutine_yield();
}
qemu_co_mutex_lock(&s->dma_map_lock);
ret = nvme_cmd_unmap_qiov(bs, &local_qiov);
qemu_co_mutex_unlock(&s->dma_map_lock);
if (ret) {
goto out;
}
ret = data.ret;
trace_nvme_dsm_done(s, offset, bytes, ret);
out:
qemu_iovec_destroy(&local_qiov);
qemu_vfree(buf);
return ret;
}
static int nvme_reopen_prepare(BDRVReopenState *reopen_state, static int nvme_reopen_prepare(BDRVReopenState *reopen_state,
BlockReopenQueue *queue, Error **errp) BlockReopenQueue *queue, Error **errp)
{ {
@ -1192,6 +1341,10 @@ static BlockDriver bdrv_nvme = {
.bdrv_co_preadv = nvme_co_preadv, .bdrv_co_preadv = nvme_co_preadv,
.bdrv_co_pwritev = nvme_co_pwritev, .bdrv_co_pwritev = nvme_co_pwritev,
.bdrv_co_pwrite_zeroes = nvme_co_pwrite_zeroes,
.bdrv_co_pdiscard = nvme_co_pdiscard,
.bdrv_co_flush_to_disk = nvme_co_flush, .bdrv_co_flush_to_disk = nvme_co_flush,
.bdrv_reopen_prepare = nvme_reopen_prepare, .bdrv_reopen_prepare = nvme_reopen_prepare,

View File

@ -203,7 +203,7 @@ static int64_t allocate_clusters(BlockDriverState *bs, int64_t sector_num,
} else { } else {
ret = bdrv_truncate(bs->file, ret = bdrv_truncate(bs->file,
(s->data_end + space) << BDRV_SECTOR_BITS, (s->data_end + space) << BDRV_SECTOR_BITS,
PREALLOC_MODE_OFF, NULL); false, PREALLOC_MODE_OFF, NULL);
} }
if (ret < 0) { if (ret < 0) {
return ret; return ret;
@ -487,7 +487,12 @@ static int coroutine_fn parallels_co_check(BlockDriverState *bs,
res->leaks += count; res->leaks += count;
if (fix & BDRV_FIX_LEAKS) { if (fix & BDRV_FIX_LEAKS) {
Error *local_err = NULL; Error *local_err = NULL;
ret = bdrv_truncate(bs->file, res->image_end_offset,
/*
* In order to really repair the image, we must shrink it.
* That means we have to pass exact=true.
*/
ret = bdrv_truncate(bs->file, res->image_end_offset, true,
PREALLOC_MODE_OFF, &local_err); PREALLOC_MODE_OFF, &local_err);
if (ret < 0) { if (ret < 0) {
error_report_err(local_err); error_report_err(local_err);
@ -563,11 +568,6 @@ static int coroutine_fn parallels_co_create(BlockdevCreateOptions* opts,
blk_set_allow_write_beyond_eof(blk, true); blk_set_allow_write_beyond_eof(blk, true);
/* Create image format */ /* Create image format */
ret = blk_truncate(blk, 0, PREALLOC_MODE_OFF, errp);
if (ret < 0) {
goto out;
}
bat_entries = DIV_ROUND_UP(total_size, cl_size); bat_entries = DIV_ROUND_UP(total_size, cl_size);
bat_sectors = DIV_ROUND_UP(bat_entry_off(bat_entries), cl_size); bat_sectors = DIV_ROUND_UP(bat_entry_off(bat_entries), cl_size);
bat_sectors = (bat_sectors * cl_size) >> BDRV_SECTOR_BITS; bat_sectors = (bat_sectors * cl_size) >> BDRV_SECTOR_BITS;
@ -885,7 +885,9 @@ static void parallels_close(BlockDriverState *bs)
if ((bs->open_flags & BDRV_O_RDWR) && !(bs->open_flags & BDRV_O_INACTIVE)) { if ((bs->open_flags & BDRV_O_RDWR) && !(bs->open_flags & BDRV_O_INACTIVE)) {
s->header->inuse = 0; s->header->inuse = 0;
parallels_update_header(bs); parallels_update_header(bs);
bdrv_truncate(bs->file, s->data_end << BDRV_SECTOR_BITS,
/* errors are ignored, so we might as well pass exact=true */
bdrv_truncate(bs->file, s->data_end << BDRV_SECTOR_BITS, true,
PREALLOC_MODE_OFF, NULL); PREALLOC_MODE_OFF, NULL);
} }

View File

@ -480,7 +480,7 @@ static int get_cluster_offset(BlockDriverState *bs,
return -E2BIG; return -E2BIG;
} }
ret = bdrv_truncate(bs->file, cluster_offset + s->cluster_size, ret = bdrv_truncate(bs->file, cluster_offset + s->cluster_size,
PREALLOC_MODE_OFF, NULL); false, PREALLOC_MODE_OFF, NULL);
if (ret < 0) { if (ret < 0) {
return ret; return ret;
} }
@ -858,11 +858,6 @@ static int coroutine_fn qcow_co_create(BlockdevCreateOptions *opts,
blk_set_allow_write_beyond_eof(qcow_blk, true); blk_set_allow_write_beyond_eof(qcow_blk, true);
/* Create image format */ /* Create image format */
ret = blk_truncate(qcow_blk, 0, PREALLOC_MODE_OFF, errp);
if (ret < 0) {
goto exit;
}
memset(&header, 0, sizeof(header)); memset(&header, 0, sizeof(header));
header.magic = cpu_to_be32(QCOW_MAGIC); header.magic = cpu_to_be32(QCOW_MAGIC);
header.version = cpu_to_be32(QCOW_VERSION); header.version = cpu_to_be32(QCOW_VERSION);
@ -1038,7 +1033,7 @@ static int qcow_make_empty(BlockDriverState *bs)
if (bdrv_pwrite_sync(bs->file, s->l1_table_offset, s->l1_table, if (bdrv_pwrite_sync(bs->file, s->l1_table_offset, s->l1_table,
l1_length) < 0) l1_length) < 0)
return -1; return -1;
ret = bdrv_truncate(bs->file, s->l1_table_offset + l1_length, ret = bdrv_truncate(bs->file, s->l1_table_offset + l1_length, false,
PREALLOC_MODE_OFF, NULL); PREALLOC_MODE_OFF, NULL);
if (ret < 0) if (ret < 0)
return ret; return ret;

View File

@ -2016,7 +2016,7 @@ static int check_refblocks(BlockDriverState *bs, BdrvCheckResult *res,
goto resize_fail; goto resize_fail;
} }
ret = bdrv_truncate(bs->file, offset + s->cluster_size, ret = bdrv_truncate(bs->file, offset + s->cluster_size, false,
PREALLOC_MODE_OFF, &local_err); PREALLOC_MODE_OFF, &local_err);
if (ret < 0) { if (ret < 0) {
error_report_err(local_err); error_report_err(local_err);

View File

@ -29,29 +29,64 @@
#include "qemu/error-report.h" #include "qemu/error-report.h"
#include "qemu/cutils.h" #include "qemu/cutils.h"
static void qcow2_free_single_snapshot(BlockDriverState *bs, int i)
{
BDRVQcow2State *s = bs->opaque;
assert(i >= 0 && i < s->nb_snapshots);
g_free(s->snapshots[i].name);
g_free(s->snapshots[i].id_str);
g_free(s->snapshots[i].unknown_extra_data);
memset(&s->snapshots[i], 0, sizeof(s->snapshots[i]));
}
void qcow2_free_snapshots(BlockDriverState *bs) void qcow2_free_snapshots(BlockDriverState *bs)
{ {
BDRVQcow2State *s = bs->opaque; BDRVQcow2State *s = bs->opaque;
int i; int i;
for(i = 0; i < s->nb_snapshots; i++) { for(i = 0; i < s->nb_snapshots; i++) {
g_free(s->snapshots[i].name); qcow2_free_single_snapshot(bs, i);
g_free(s->snapshots[i].id_str);
} }
g_free(s->snapshots); g_free(s->snapshots);
s->snapshots = NULL; s->snapshots = NULL;
s->nb_snapshots = 0; s->nb_snapshots = 0;
} }
int qcow2_read_snapshots(BlockDriverState *bs) /*
* If @repair is true, try to repair a broken snapshot table instead
* of just returning an error:
*
* - If the snapshot table was too long, set *nb_clusters_reduced to
* the number of snapshots removed off the end.
* The caller will update the on-disk nb_snapshots accordingly;
* this leaks clusters, but is safe.
* (The on-disk information must be updated before
* qcow2_check_refcounts(), because that function relies on
* s->nb_snapshots to reflect the on-disk value.)
*
* - If there were snapshots with too much extra metadata, increment
* *extra_data_dropped for each.
* This requires the caller to eventually rewrite the whole snapshot
* table, which requires cluster allocation. Therefore, this should
* be done only after qcow2_check_refcounts() made sure the refcount
* structures are valid.
* (In the meantime, the image is still valid because
* qcow2_check_refcounts() does not do anything with snapshots'
* extra data.)
*/
static int qcow2_do_read_snapshots(BlockDriverState *bs, bool repair,
int *nb_clusters_reduced,
int *extra_data_dropped,
Error **errp)
{ {
BDRVQcow2State *s = bs->opaque; BDRVQcow2State *s = bs->opaque;
QCowSnapshotHeader h; QCowSnapshotHeader h;
QCowSnapshotExtraData extra; QCowSnapshotExtraData extra;
QCowSnapshot *sn; QCowSnapshot *sn;
int i, id_str_size, name_size; int i, id_str_size, name_size;
int64_t offset; int64_t offset, pre_sn_offset;
uint32_t extra_data_size; uint64_t table_length = 0;
int ret; int ret;
if (!s->nb_snapshots) { if (!s->nb_snapshots) {
@ -64,10 +99,16 @@ int qcow2_read_snapshots(BlockDriverState *bs)
s->snapshots = g_new0(QCowSnapshot, s->nb_snapshots); s->snapshots = g_new0(QCowSnapshot, s->nb_snapshots);
for(i = 0; i < s->nb_snapshots; i++) { for(i = 0; i < s->nb_snapshots; i++) {
bool truncate_unknown_extra_data = false;
pre_sn_offset = offset;
table_length = ROUND_UP(table_length, 8);
/* Read statically sized part of the snapshot header */ /* Read statically sized part of the snapshot header */
offset = ROUND_UP(offset, 8); offset = ROUND_UP(offset, 8);
ret = bdrv_pread(bs->file, offset, &h, sizeof(h)); ret = bdrv_pread(bs->file, offset, &h, sizeof(h));
if (ret < 0) { if (ret < 0) {
error_setg_errno(errp, -ret, "Failed to read snapshot table");
goto fail; goto fail;
} }
@ -79,33 +120,77 @@ int qcow2_read_snapshots(BlockDriverState *bs)
sn->date_sec = be32_to_cpu(h.date_sec); sn->date_sec = be32_to_cpu(h.date_sec);
sn->date_nsec = be32_to_cpu(h.date_nsec); sn->date_nsec = be32_to_cpu(h.date_nsec);
sn->vm_clock_nsec = be64_to_cpu(h.vm_clock_nsec); sn->vm_clock_nsec = be64_to_cpu(h.vm_clock_nsec);
extra_data_size = be32_to_cpu(h.extra_data_size); sn->extra_data_size = be32_to_cpu(h.extra_data_size);
id_str_size = be16_to_cpu(h.id_str_size); id_str_size = be16_to_cpu(h.id_str_size);
name_size = be16_to_cpu(h.name_size); name_size = be16_to_cpu(h.name_size);
/* Read extra data */ if (sn->extra_data_size > QCOW_MAX_SNAPSHOT_EXTRA_DATA) {
ret = bdrv_pread(bs->file, offset, &extra, if (!repair) {
MIN(sizeof(extra), extra_data_size)); ret = -EFBIG;
if (ret < 0) { error_setg(errp, "Too much extra metadata in snapshot table "
"entry %i", i);
error_append_hint(errp, "You can force-remove this extra "
"metadata with qemu-img check -r all\n");
goto fail; goto fail;
} }
offset += extra_data_size;
if (extra_data_size >= 8) { fprintf(stderr, "Discarding too much extra metadata in snapshot "
"table entry %i (%" PRIu32 " > %u)\n",
i, sn->extra_data_size, QCOW_MAX_SNAPSHOT_EXTRA_DATA);
(*extra_data_dropped)++;
truncate_unknown_extra_data = true;
}
/* Read known extra data */
ret = bdrv_pread(bs->file, offset, &extra,
MIN(sizeof(extra), sn->extra_data_size));
if (ret < 0) {
error_setg_errno(errp, -ret, "Failed to read snapshot table");
goto fail;
}
offset += MIN(sizeof(extra), sn->extra_data_size);
if (sn->extra_data_size >= endof(QCowSnapshotExtraData,
vm_state_size_large)) {
sn->vm_state_size = be64_to_cpu(extra.vm_state_size_large); sn->vm_state_size = be64_to_cpu(extra.vm_state_size_large);
} }
if (extra_data_size >= 16) { if (sn->extra_data_size >= endof(QCowSnapshotExtraData, disk_size)) {
sn->disk_size = be64_to_cpu(extra.disk_size); sn->disk_size = be64_to_cpu(extra.disk_size);
} else { } else {
sn->disk_size = bs->total_sectors * BDRV_SECTOR_SIZE; sn->disk_size = bs->total_sectors * BDRV_SECTOR_SIZE;
} }
if (sn->extra_data_size > sizeof(extra)) {
uint64_t extra_data_end;
size_t unknown_extra_data_size;
extra_data_end = offset + sn->extra_data_size - sizeof(extra);
if (truncate_unknown_extra_data) {
sn->extra_data_size = QCOW_MAX_SNAPSHOT_EXTRA_DATA;
}
/* Store unknown extra data */
unknown_extra_data_size = sn->extra_data_size - sizeof(extra);
sn->unknown_extra_data = g_malloc(unknown_extra_data_size);
ret = bdrv_pread(bs->file, offset, sn->unknown_extra_data,
unknown_extra_data_size);
if (ret < 0) {
error_setg_errno(errp, -ret,
"Failed to read snapshot table");
goto fail;
}
offset = extra_data_end;
}
/* Read snapshot ID */ /* Read snapshot ID */
sn->id_str = g_malloc(id_str_size + 1); sn->id_str = g_malloc(id_str_size + 1);
ret = bdrv_pread(bs->file, offset, sn->id_str, id_str_size); ret = bdrv_pread(bs->file, offset, sn->id_str, id_str_size);
if (ret < 0) { if (ret < 0) {
error_setg_errno(errp, -ret, "Failed to read snapshot table");
goto fail; goto fail;
} }
offset += id_str_size; offset += id_str_size;
@ -115,15 +200,48 @@ int qcow2_read_snapshots(BlockDriverState *bs)
sn->name = g_malloc(name_size + 1); sn->name = g_malloc(name_size + 1);
ret = bdrv_pread(bs->file, offset, sn->name, name_size); ret = bdrv_pread(bs->file, offset, sn->name, name_size);
if (ret < 0) { if (ret < 0) {
error_setg_errno(errp, -ret, "Failed to read snapshot table");
goto fail; goto fail;
} }
offset += name_size; offset += name_size;
sn->name[name_size] = '\0'; sn->name[name_size] = '\0';
if (offset - s->snapshots_offset > QCOW_MAX_SNAPSHOTS_SIZE) { /* Note that the extra data may have been truncated */
table_length += sizeof(h) + sn->extra_data_size + id_str_size +
name_size;
if (!repair) {
assert(table_length == offset - s->snapshots_offset);
}
if (table_length > QCOW_MAX_SNAPSHOTS_SIZE ||
offset - s->snapshots_offset > INT_MAX)
{
if (!repair) {
ret = -EFBIG; ret = -EFBIG;
error_setg(errp, "Snapshot table is too big");
error_append_hint(errp, "You can force-remove all %u "
"overhanging snapshots with qemu-img check "
"-r all\n", s->nb_snapshots - i);
goto fail; goto fail;
} }
fprintf(stderr, "Discarding %u overhanging snapshots (snapshot "
"table is too big)\n", s->nb_snapshots - i);
*nb_clusters_reduced += (s->nb_snapshots - i);
/* Discard current snapshot also */
qcow2_free_single_snapshot(bs, i);
/*
* This leaks all the rest of the snapshot table and the
* snapshots' clusters, but we run in check -r all mode,
* so qcow2_check_refcounts() will take care of it.
*/
s->nb_snapshots = i;
offset = pre_sn_offset;
break;
}
} }
assert(offset - s->snapshots_offset <= INT_MAX); assert(offset - s->snapshots_offset <= INT_MAX);
@ -135,8 +253,13 @@ fail:
return ret; return ret;
} }
int qcow2_read_snapshots(BlockDriverState *bs, Error **errp)
{
return qcow2_do_read_snapshots(bs, false, NULL, NULL, errp);
}
/* add at the end of the file a new list of snapshots */ /* add at the end of the file a new list of snapshots */
static int qcow2_write_snapshots(BlockDriverState *bs) int qcow2_write_snapshots(BlockDriverState *bs)
{ {
BDRVQcow2State *s = bs->opaque; BDRVQcow2State *s = bs->opaque;
QCowSnapshot *sn; QCowSnapshot *sn;
@ -156,7 +279,7 @@ static int qcow2_write_snapshots(BlockDriverState *bs)
sn = s->snapshots + i; sn = s->snapshots + i;
offset = ROUND_UP(offset, 8); offset = ROUND_UP(offset, 8);
offset += sizeof(h); offset += sizeof(h);
offset += sizeof(extra); offset += MAX(sizeof(extra), sn->extra_data_size);
offset += strlen(sn->id_str); offset += strlen(sn->id_str);
offset += strlen(sn->name); offset += strlen(sn->name);
@ -203,7 +326,8 @@ static int qcow2_write_snapshots(BlockDriverState *bs)
h.date_sec = cpu_to_be32(sn->date_sec); h.date_sec = cpu_to_be32(sn->date_sec);
h.date_nsec = cpu_to_be32(sn->date_nsec); h.date_nsec = cpu_to_be32(sn->date_nsec);
h.vm_clock_nsec = cpu_to_be64(sn->vm_clock_nsec); h.vm_clock_nsec = cpu_to_be64(sn->vm_clock_nsec);
h.extra_data_size = cpu_to_be32(sizeof(extra)); h.extra_data_size = cpu_to_be32(MAX(sizeof(extra),
sn->extra_data_size));
memset(&extra, 0, sizeof(extra)); memset(&extra, 0, sizeof(extra));
extra.vm_state_size_large = cpu_to_be64(sn->vm_state_size); extra.vm_state_size_large = cpu_to_be64(sn->vm_state_size);
@ -228,6 +352,22 @@ static int qcow2_write_snapshots(BlockDriverState *bs)
} }
offset += sizeof(extra); offset += sizeof(extra);
if (sn->extra_data_size > sizeof(extra)) {
size_t unknown_extra_data_size =
sn->extra_data_size - sizeof(extra);
/* qcow2_read_snapshots() ensures no unbounded allocation */
assert(unknown_extra_data_size <= BDRV_REQUEST_MAX_BYTES);
assert(sn->unknown_extra_data);
ret = bdrv_pwrite(bs->file, offset, sn->unknown_extra_data,
unknown_extra_data_size);
if (ret < 0) {
goto fail;
}
offset += unknown_extra_data_size;
}
ret = bdrv_pwrite(bs->file, offset, sn->id_str, id_str_size); ret = bdrv_pwrite(bs->file, offset, sn->id_str, id_str_size);
if (ret < 0) { if (ret < 0) {
goto fail; goto fail;
@ -251,7 +391,7 @@ static int qcow2_write_snapshots(BlockDriverState *bs)
} }
QEMU_BUILD_BUG_ON(offsetof(QCowHeader, snapshots_offset) != QEMU_BUILD_BUG_ON(offsetof(QCowHeader, snapshots_offset) !=
offsetof(QCowHeader, nb_snapshots) + sizeof(header_data.nb_snapshots)); endof(QCowHeader, nb_snapshots));
header_data.nb_snapshots = cpu_to_be32(s->nb_snapshots); header_data.nb_snapshots = cpu_to_be32(s->nb_snapshots);
header_data.snapshots_offset = cpu_to_be64(snapshots_offset); header_data.snapshots_offset = cpu_to_be64(snapshots_offset);
@ -277,6 +417,151 @@ fail:
return ret; return ret;
} }
int coroutine_fn qcow2_check_read_snapshot_table(BlockDriverState *bs,
BdrvCheckResult *result,
BdrvCheckMode fix)
{
BDRVQcow2State *s = bs->opaque;
Error *local_err = NULL;
int nb_clusters_reduced = 0;
int extra_data_dropped = 0;
int ret;
struct {
uint32_t nb_snapshots;
uint64_t snapshots_offset;
} QEMU_PACKED snapshot_table_pointer;
/* qcow2_do_open() discards this information in check mode */
ret = bdrv_pread(bs->file, offsetof(QCowHeader, nb_snapshots),
&snapshot_table_pointer, sizeof(snapshot_table_pointer));
if (ret < 0) {
result->check_errors++;
fprintf(stderr, "ERROR failed to read the snapshot table pointer from "
"the image header: %s\n", strerror(-ret));
return ret;
}
s->snapshots_offset = be64_to_cpu(snapshot_table_pointer.snapshots_offset);
s->nb_snapshots = be32_to_cpu(snapshot_table_pointer.nb_snapshots);
if (s->nb_snapshots > QCOW_MAX_SNAPSHOTS && (fix & BDRV_FIX_ERRORS)) {
fprintf(stderr, "Discarding %u overhanging snapshots\n",
s->nb_snapshots - QCOW_MAX_SNAPSHOTS);
nb_clusters_reduced += s->nb_snapshots - QCOW_MAX_SNAPSHOTS;
s->nb_snapshots = QCOW_MAX_SNAPSHOTS;
}
ret = qcow2_validate_table(bs, s->snapshots_offset, s->nb_snapshots,
sizeof(QCowSnapshotHeader),
sizeof(QCowSnapshotHeader) * QCOW_MAX_SNAPSHOTS,
"snapshot table", &local_err);
if (ret < 0) {
result->check_errors++;
error_reportf_err(local_err, "ERROR ");
if (s->nb_snapshots > QCOW_MAX_SNAPSHOTS) {
fprintf(stderr, "You can force-remove all %u overhanging snapshots "
"with qemu-img check -r all\n",
s->nb_snapshots - QCOW_MAX_SNAPSHOTS);
}
/* We did not read the snapshot table, so invalidate this information */
s->snapshots_offset = 0;
s->nb_snapshots = 0;
return ret;
}
qemu_co_mutex_unlock(&s->lock);
ret = qcow2_do_read_snapshots(bs, fix & BDRV_FIX_ERRORS,
&nb_clusters_reduced, &extra_data_dropped,
&local_err);
qemu_co_mutex_lock(&s->lock);
if (ret < 0) {
result->check_errors++;
error_reportf_err(local_err,
"ERROR failed to read the snapshot table: ");
/* We did not read the snapshot table, so invalidate this information */
s->snapshots_offset = 0;
s->nb_snapshots = 0;
return ret;
}
result->corruptions += nb_clusters_reduced + extra_data_dropped;
if (nb_clusters_reduced) {
/*
* Update image header now, because:
* (1) qcow2_check_refcounts() relies on s->nb_snapshots to be
* the same as what the image header says,
* (2) this leaks clusters, but qcow2_check_refcounts() will
* fix that.
*/
assert(fix & BDRV_FIX_ERRORS);
snapshot_table_pointer.nb_snapshots = cpu_to_be32(s->nb_snapshots);
ret = bdrv_pwrite_sync(bs->file, offsetof(QCowHeader, nb_snapshots),
&snapshot_table_pointer.nb_snapshots,
sizeof(snapshot_table_pointer.nb_snapshots));
if (ret < 0) {
result->check_errors++;
fprintf(stderr, "ERROR failed to update the snapshot count in the "
"image header: %s\n", strerror(-ret));
return ret;
}
result->corruptions_fixed += nb_clusters_reduced;
result->corruptions -= nb_clusters_reduced;
}
/*
* All of v3 images' snapshot table entries need to have at least
* 16 bytes of extra data.
*/
if (s->qcow_version >= 3) {
int i;
for (i = 0; i < s->nb_snapshots; i++) {
if (s->snapshots[i].extra_data_size <
sizeof_field(QCowSnapshotExtraData, vm_state_size_large) +
sizeof_field(QCowSnapshotExtraData, disk_size))
{
result->corruptions++;
fprintf(stderr, "%s snapshot table entry %i is incomplete\n",
fix & BDRV_FIX_ERRORS ? "Repairing" : "ERROR", i);
}
}
}
return 0;
}
int coroutine_fn qcow2_check_fix_snapshot_table(BlockDriverState *bs,
BdrvCheckResult *result,
BdrvCheckMode fix)
{
BDRVQcow2State *s = bs->opaque;
int ret;
if (result->corruptions && (fix & BDRV_FIX_ERRORS)) {
qemu_co_mutex_unlock(&s->lock);
ret = qcow2_write_snapshots(bs);
qemu_co_mutex_lock(&s->lock);
if (ret < 0) {
result->check_errors++;
fprintf(stderr, "ERROR failed to update snapshot table: %s\n",
strerror(-ret));
return ret;
}
result->corruptions_fixed += result->corruptions;
result->corruptions = 0;
}
return 0;
}
static void find_new_snapshot_id(BlockDriverState *bs, static void find_new_snapshot_id(BlockDriverState *bs,
char *id_str, int id_str_size) char *id_str, int id_str_size)
{ {
@ -370,6 +655,7 @@ int qcow2_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info)
sn->date_sec = sn_info->date_sec; sn->date_sec = sn_info->date_sec;
sn->date_nsec = sn_info->date_nsec; sn->date_nsec = sn_info->date_nsec;
sn->vm_clock_nsec = sn_info->vm_clock_nsec; sn->vm_clock_nsec = sn_info->vm_clock_nsec;
sn->extra_data_size = sizeof(QCowSnapshotExtraData);
/* Allocate the L1 table of the snapshot and copy the current one there. */ /* Allocate the L1 table of the snapshot and copy the current one there. */
l1_table_offset = qcow2_alloc_clusters(bs, s->l1_size * sizeof(uint64_t)); l1_table_offset = qcow2_alloc_clusters(bs, s->l1_size * sizeof(uint64_t));
@ -641,6 +927,7 @@ int qcow2_snapshot_delete(BlockDriverState *bs,
* The snapshot is now unused, clean up. If we fail after this point, we * The snapshot is now unused, clean up. If we fail after this point, we
* won't recover but just leak clusters. * won't recover but just leak clusters.
*/ */
g_free(sn.unknown_extra_data);
g_free(sn.id_str); g_free(sn.id_str);
g_free(sn.name); g_free(sn.name);

View File

@ -570,11 +570,47 @@ int qcow2_mark_consistent(BlockDriverState *bs)
return 0; return 0;
} }
static void qcow2_add_check_result(BdrvCheckResult *out,
const BdrvCheckResult *src,
bool set_allocation_info)
{
out->corruptions += src->corruptions;
out->leaks += src->leaks;
out->check_errors += src->check_errors;
out->corruptions_fixed += src->corruptions_fixed;
out->leaks_fixed += src->leaks_fixed;
if (set_allocation_info) {
out->image_end_offset = src->image_end_offset;
out->bfi = src->bfi;
}
}
static int coroutine_fn qcow2_co_check_locked(BlockDriverState *bs, static int coroutine_fn qcow2_co_check_locked(BlockDriverState *bs,
BdrvCheckResult *result, BdrvCheckResult *result,
BdrvCheckMode fix) BdrvCheckMode fix)
{ {
int ret = qcow2_check_refcounts(bs, result, fix); BdrvCheckResult snapshot_res = {};
BdrvCheckResult refcount_res = {};
int ret;
memset(result, 0, sizeof(*result));
ret = qcow2_check_read_snapshot_table(bs, &snapshot_res, fix);
if (ret < 0) {
qcow2_add_check_result(result, &snapshot_res, false);
return ret;
}
ret = qcow2_check_refcounts(bs, &refcount_res, fix);
qcow2_add_check_result(result, &refcount_res, true);
if (ret < 0) {
qcow2_add_check_result(result, &snapshot_res, false);
return ret;
}
ret = qcow2_check_fix_snapshot_table(bs, &snapshot_res, fix);
qcow2_add_check_result(result, &snapshot_res, false);
if (ret < 0) { if (ret < 0) {
return ret; return ret;
} }
@ -1410,18 +1446,23 @@ static int coroutine_fn qcow2_do_open(BlockDriverState *bs, QDict *options,
goto fail; goto fail;
} }
/* The total size in bytes of the snapshot table is checked in if (!(flags & BDRV_O_CHECK)) {
/*
* The total size in bytes of the snapshot table is checked in
* qcow2_read_snapshots() because the size of each snapshot is * qcow2_read_snapshots() because the size of each snapshot is
* variable and we don't know it yet. * variable and we don't know it yet.
* Here we only check the offset and number of snapshots. */ * Here we only check the offset and number of snapshots.
*/
ret = qcow2_validate_table(bs, header.snapshots_offset, ret = qcow2_validate_table(bs, header.snapshots_offset,
header.nb_snapshots, header.nb_snapshots,
sizeof(QCowSnapshotHeader), sizeof(QCowSnapshotHeader),
sizeof(QCowSnapshotHeader) * QCOW_MAX_SNAPSHOTS, sizeof(QCowSnapshotHeader) *
QCOW_MAX_SNAPSHOTS,
"Snapshot table", errp); "Snapshot table", errp);
if (ret < 0) { if (ret < 0) {
goto fail; goto fail;
} }
}
/* read the level 1 table */ /* read the level 1 table */
ret = qcow2_validate_table(bs, header.l1_table_offset, ret = qcow2_validate_table(bs, header.l1_table_offset,
@ -1580,15 +1621,20 @@ static int coroutine_fn qcow2_do_open(BlockDriverState *bs, QDict *options,
s->image_backing_file = g_strdup(bs->auto_backing_file); s->image_backing_file = g_strdup(bs->auto_backing_file);
} }
/* Internal snapshots */ /*
* Internal snapshots; skip reading them in check mode, because
* we do not need them then, and we do not want to abort because
* of a broken table.
*/
if (!(flags & BDRV_O_CHECK)) {
s->snapshots_offset = header.snapshots_offset; s->snapshots_offset = header.snapshots_offset;
s->nb_snapshots = header.nb_snapshots; s->nb_snapshots = header.nb_snapshots;
ret = qcow2_read_snapshots(bs); ret = qcow2_read_snapshots(bs, errp);
if (ret < 0) { if (ret < 0) {
error_setg_errno(errp, -ret, "Could not read snapshots");
goto fail; goto fail;
} }
}
/* Clear unknown autoclear feature bits */ /* Clear unknown autoclear feature bits */
update_header |= s->autoclear_features & ~QCOW2_AUTOCLEAR_MASK; update_header |= s->autoclear_features & ~QCOW2_AUTOCLEAR_MASK;
@ -3028,8 +3074,8 @@ static int coroutine_fn preallocate_co(BlockDriverState *bs, uint64_t offset,
if (mode == PREALLOC_MODE_METADATA) { if (mode == PREALLOC_MODE_METADATA) {
mode = PREALLOC_MODE_OFF; mode = PREALLOC_MODE_OFF;
} }
ret = bdrv_co_truncate(s->data_file, host_offset + cur_bytes, mode, ret = bdrv_co_truncate(s->data_file, host_offset + cur_bytes, false,
errp); mode, errp);
if (ret < 0) { if (ret < 0) {
return ret; return ret;
} }
@ -3345,12 +3391,6 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
} }
blk_set_allow_write_beyond_eof(blk, true); blk_set_allow_write_beyond_eof(blk, true);
/* Clear the protocol layer and preallocate it if necessary */
ret = blk_truncate(blk, 0, PREALLOC_MODE_OFF, errp);
if (ret < 0) {
goto out;
}
/* Write the header */ /* Write the header */
QEMU_BUILD_BUG_ON((1 << MIN_CLUSTER_BITS) < sizeof(*header)); QEMU_BUILD_BUG_ON((1 << MIN_CLUSTER_BITS) < sizeof(*header));
header = g_malloc0(cluster_size); header = g_malloc0(cluster_size);
@ -3449,7 +3489,8 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
} }
/* Okay, now that we have a valid image, let's give it the right size */ /* Okay, now that we have a valid image, let's give it the right size */
ret = blk_truncate(blk, qcow2_opts->size, qcow2_opts->preallocation, errp); ret = blk_truncate(blk, qcow2_opts->size, false, qcow2_opts->preallocation,
errp);
if (ret < 0) { if (ret < 0) {
error_prepend(errp, "Could not resize image: "); error_prepend(errp, "Could not resize image: ");
goto out; goto out;
@ -3897,7 +3938,8 @@ fail:
} }
static int coroutine_fn qcow2_co_truncate(BlockDriverState *bs, int64_t offset, static int coroutine_fn qcow2_co_truncate(BlockDriverState *bs, int64_t offset,
PreallocMode prealloc, Error **errp) bool exact, PreallocMode prealloc,
Error **errp)
{ {
BDRVQcow2State *s = bs->opaque; BDRVQcow2State *s = bs->opaque;
uint64_t old_length; uint64_t old_length;
@ -3985,8 +4027,15 @@ static int coroutine_fn qcow2_co_truncate(BlockDriverState *bs, int64_t offset,
if ((last_cluster + 1) * s->cluster_size < old_file_size) { if ((last_cluster + 1) * s->cluster_size < old_file_size) {
Error *local_err = NULL; Error *local_err = NULL;
/*
* Do not pass @exact here: It will not help the user if
* we get an error here just because they wanted to shrink
* their qcow2 image (on a block device) with qemu-img.
* (And on the qcow2 layer, the @exact requirement is
* always fulfilled, so there is no need to pass it on.)
*/
bdrv_co_truncate(bs->file, (last_cluster + 1) * s->cluster_size, bdrv_co_truncate(bs->file, (last_cluster + 1) * s->cluster_size,
PREALLOC_MODE_OFF, &local_err); false, PREALLOC_MODE_OFF, &local_err);
if (local_err) { if (local_err) {
warn_reportf_err(local_err, warn_reportf_err(local_err,
"Failed to truncate the tail of the image: "); "Failed to truncate the tail of the image: ");
@ -4003,7 +4052,12 @@ static int coroutine_fn qcow2_co_truncate(BlockDriverState *bs, int64_t offset,
switch (prealloc) { switch (prealloc) {
case PREALLOC_MODE_OFF: case PREALLOC_MODE_OFF:
if (has_data_file(bs)) { if (has_data_file(bs)) {
ret = bdrv_co_truncate(s->data_file, offset, prealloc, errp); /*
* If the caller wants an exact resize, the external data
* file should be resized to the exact target size, too,
* so we pass @exact here.
*/
ret = bdrv_co_truncate(s->data_file, offset, exact, prealloc, errp);
if (ret < 0) { if (ret < 0) {
goto fail; goto fail;
} }
@ -4088,7 +4142,8 @@ static int coroutine_fn qcow2_co_truncate(BlockDriverState *bs, int64_t offset,
/* Allocate the data area */ /* Allocate the data area */
new_file_size = allocation_start + new_file_size = allocation_start +
nb_new_data_clusters * s->cluster_size; nb_new_data_clusters * s->cluster_size;
ret = bdrv_co_truncate(bs->file, new_file_size, prealloc, errp); /* Image file grows, so @exact does not matter */
ret = bdrv_co_truncate(bs->file, new_file_size, false, prealloc, errp);
if (ret < 0) { if (ret < 0) {
error_prepend(errp, "Failed to resize underlying file: "); error_prepend(errp, "Failed to resize underlying file: ");
qcow2_free_clusters(bs, allocation_start, qcow2_free_clusters(bs, allocation_start,
@ -4191,7 +4246,7 @@ qcow2_co_pwritev_compressed_part(BlockDriverState *bs,
if (len < 0) { if (len < 0) {
return len; return len;
} }
return bdrv_co_truncate(bs->file, len, PREALLOC_MODE_OFF, NULL); return bdrv_co_truncate(bs->file, len, false, PREALLOC_MODE_OFF, NULL);
} }
if (offset_into_cluster(s, offset)) { if (offset_into_cluster(s, offset)) {
@ -4428,7 +4483,7 @@ static int make_completely_empty(BlockDriverState *bs)
goto fail; goto fail;
} }
ret = bdrv_truncate(bs->file, (3 + l1_clusters) * s->cluster_size, ret = bdrv_truncate(bs->file, (3 + l1_clusters) * s->cluster_size, false,
PREALLOC_MODE_OFF, &local_err); PREALLOC_MODE_OFF, &local_err);
if (ret < 0) { if (ret < 0) {
error_report_err(local_err); error_report_err(local_err);
@ -4913,12 +4968,74 @@ static int qcow2_downgrade(BlockDriverState *bs, int target_version,
return 0; return 0;
} }
/*
* Upgrades an image's version. While newer versions encompass all
* features of older versions, some things may have to be presented
* differently.
*/
static int qcow2_upgrade(BlockDriverState *bs, int target_version,
BlockDriverAmendStatusCB *status_cb, void *cb_opaque,
Error **errp)
{
BDRVQcow2State *s = bs->opaque;
bool need_snapshot_update;
int current_version = s->qcow_version;
int i;
int ret;
/* This is qcow2_upgrade(), not qcow2_downgrade() */
assert(target_version > current_version);
/* There are no other versions (yet) that you can upgrade to */
assert(target_version == 3);
status_cb(bs, 0, 2, cb_opaque);
/*
* In v2, snapshots do not need to have extra data. v3 requires
* the 64-bit VM state size and the virtual disk size to be
* present.
* qcow2_write_snapshots() will always write the list in the
* v3-compliant format.
*/
need_snapshot_update = false;
for (i = 0; i < s->nb_snapshots; i++) {
if (s->snapshots[i].extra_data_size <
sizeof_field(QCowSnapshotExtraData, vm_state_size_large) +
sizeof_field(QCowSnapshotExtraData, disk_size))
{
need_snapshot_update = true;
break;
}
}
if (need_snapshot_update) {
ret = qcow2_write_snapshots(bs);
if (ret < 0) {
error_setg_errno(errp, -ret, "Failed to update the snapshot table");
return ret;
}
}
status_cb(bs, 1, 2, cb_opaque);
s->qcow_version = target_version;
ret = qcow2_update_header(bs);
if (ret < 0) {
s->qcow_version = current_version;
error_setg_errno(errp, -ret, "Failed to update the image header");
return ret;
}
status_cb(bs, 2, 2, cb_opaque);
return 0;
}
typedef enum Qcow2AmendOperation { typedef enum Qcow2AmendOperation {
/* This is the value Qcow2AmendHelperCBInfo::last_operation will be /* This is the value Qcow2AmendHelperCBInfo::last_operation will be
* statically initialized to so that the helper CB can discern the first * statically initialized to so that the helper CB can discern the first
* invocation from an operation change */ * invocation from an operation change */
QCOW2_NO_OPERATION = 0, QCOW2_NO_OPERATION = 0,
QCOW2_UPGRADING,
QCOW2_CHANGING_REFCOUNT_ORDER, QCOW2_CHANGING_REFCOUNT_ORDER,
QCOW2_DOWNGRADING, QCOW2_DOWNGRADING,
} Qcow2AmendOperation; } Qcow2AmendOperation;
@ -5101,17 +5218,16 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
helper_cb_info = (Qcow2AmendHelperCBInfo){ helper_cb_info = (Qcow2AmendHelperCBInfo){
.original_status_cb = status_cb, .original_status_cb = status_cb,
.original_cb_opaque = cb_opaque, .original_cb_opaque = cb_opaque,
.total_operations = (new_version < old_version) .total_operations = (new_version != old_version)
+ (s->refcount_bits != refcount_bits) + (s->refcount_bits != refcount_bits)
}; };
/* Upgrade first (some features may require compat=1.1) */ /* Upgrade first (some features may require compat=1.1) */
if (new_version > old_version) { if (new_version > old_version) {
s->qcow_version = new_version; helper_cb_info.current_operation = QCOW2_UPGRADING;
ret = qcow2_update_header(bs); ret = qcow2_upgrade(bs, new_version, &qcow2_amend_helper_cb,
&helper_cb_info, errp);
if (ret < 0) { if (ret < 0) {
s->qcow_version = old_version;
error_setg_errno(errp, -ret, "Failed to update the image header");
return ret; return ret;
} }
} }
@ -5207,7 +5323,11 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
return ret; return ret;
} }
ret = blk_truncate(blk, new_size, PREALLOC_MODE_OFF, errp); /*
* Amending image options should ensure that the image has
* exactly the given new values, so pass exact=true here.
*/
ret = blk_truncate(blk, new_size, true, PREALLOC_MODE_OFF, errp);
blk_unref(blk); blk_unref(blk);
if (ret < 0) { if (ret < 0) {
return ret; return ret;

View File

@ -61,6 +61,9 @@
* space for snapshot names and IDs */ * space for snapshot names and IDs */
#define QCOW_MAX_SNAPSHOTS_SIZE (1024 * QCOW_MAX_SNAPSHOTS) #define QCOW_MAX_SNAPSHOTS_SIZE (1024 * QCOW_MAX_SNAPSHOTS)
/* Maximum amount of extra data per snapshot table entry to accept */
#define QCOW_MAX_SNAPSHOT_EXTRA_DATA 1024
/* Bitmap header extension constraints */ /* Bitmap header extension constraints */
#define QCOW2_MAX_BITMAPS 65535 #define QCOW2_MAX_BITMAPS 65535
#define QCOW2_MAX_BITMAP_DIRECTORY_SIZE (1024 * QCOW2_MAX_BITMAPS) #define QCOW2_MAX_BITMAP_DIRECTORY_SIZE (1024 * QCOW2_MAX_BITMAPS)
@ -181,6 +184,10 @@ typedef struct QCowSnapshot {
uint32_t date_sec; uint32_t date_sec;
uint32_t date_nsec; uint32_t date_nsec;
uint64_t vm_clock_nsec; uint64_t vm_clock_nsec;
/* Size of all extra data, including QCowSnapshotExtraData if available */
uint32_t extra_data_size;
/* Data beyond QCowSnapshotExtraData, if any */
void *unknown_extra_data;
} QCowSnapshot; } QCowSnapshot;
struct Qcow2Cache; struct Qcow2Cache;
@ -708,7 +715,15 @@ int qcow2_snapshot_load_tmp(BlockDriverState *bs,
Error **errp); Error **errp);
void qcow2_free_snapshots(BlockDriverState *bs); void qcow2_free_snapshots(BlockDriverState *bs);
int qcow2_read_snapshots(BlockDriverState *bs); int qcow2_read_snapshots(BlockDriverState *bs, Error **errp);
int qcow2_write_snapshots(BlockDriverState *bs);
int coroutine_fn qcow2_check_read_snapshot_table(BlockDriverState *bs,
BdrvCheckResult *result,
BdrvCheckMode fix);
int coroutine_fn qcow2_check_fix_snapshot_table(BlockDriverState *bs,
BdrvCheckResult *result,
BdrvCheckMode fix);
/* qcow2-cache.c functions */ /* qcow2-cache.c functions */
Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables, Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables,

View File

@ -673,8 +673,11 @@ static int coroutine_fn bdrv_qed_co_create(BlockdevCreateOptions *opts,
l1_size = header.cluster_size * header.table_size; l1_size = header.cluster_size * header.table_size;
/* File must start empty and grow, check truncate is supported */ /*
ret = blk_truncate(blk, 0, PREALLOC_MODE_OFF, errp); * The QED format associates file length with allocation status,
* so a new file (which is empty) must have a length of 0.
*/
ret = blk_truncate(blk, 0, true, PREALLOC_MODE_OFF, errp);
if (ret < 0) { if (ret < 0) {
goto out; goto out;
} }
@ -1461,6 +1464,7 @@ static int coroutine_fn bdrv_qed_co_pwrite_zeroes(BlockDriverState *bs,
static int coroutine_fn bdrv_qed_co_truncate(BlockDriverState *bs, static int coroutine_fn bdrv_qed_co_truncate(BlockDriverState *bs,
int64_t offset, int64_t offset,
bool exact,
PreallocMode prealloc, PreallocMode prealloc,
Error **errp) Error **errp)
{ {

View File

@ -370,7 +370,8 @@ static void raw_refresh_limits(BlockDriverState *bs, Error **errp)
} }
static int coroutine_fn raw_co_truncate(BlockDriverState *bs, int64_t offset, static int coroutine_fn raw_co_truncate(BlockDriverState *bs, int64_t offset,
PreallocMode prealloc, Error **errp) bool exact, PreallocMode prealloc,
Error **errp)
{ {
BDRVRawState *s = bs->opaque; BDRVRawState *s = bs->opaque;
@ -386,7 +387,7 @@ static int coroutine_fn raw_co_truncate(BlockDriverState *bs, int64_t offset,
s->size = offset; s->size = offset;
offset += s->offset; offset += s->offset;
return bdrv_co_truncate(bs->file, offset, prealloc, errp); return bdrv_co_truncate(bs->file, offset, exact, prealloc, errp);
} }
static void raw_eject(BlockDriverState *bs, bool eject_flag) static void raw_eject(BlockDriverState *bs, bool eject_flag)

View File

@ -1087,6 +1087,7 @@ static int64_t qemu_rbd_getlength(BlockDriverState *bs)
static int coroutine_fn qemu_rbd_co_truncate(BlockDriverState *bs, static int coroutine_fn qemu_rbd_co_truncate(BlockDriverState *bs,
int64_t offset, int64_t offset,
bool exact,
PreallocMode prealloc, PreallocMode prealloc,
Error **errp) Error **errp)
{ {

View File

@ -2285,7 +2285,8 @@ static int64_t sd_getlength(BlockDriverState *bs)
} }
static int coroutine_fn sd_co_truncate(BlockDriverState *bs, int64_t offset, static int coroutine_fn sd_co_truncate(BlockDriverState *bs, int64_t offset,
PreallocMode prealloc, Error **errp) bool exact, PreallocMode prealloc,
Error **errp)
{ {
BDRVSheepdogState *s = bs->opaque; BDRVSheepdogState *s = bs->opaque;
int ret, fd; int ret, fd;
@ -2601,7 +2602,7 @@ static coroutine_fn int sd_co_writev(BlockDriverState *bs, int64_t sector_num,
assert(!flags); assert(!flags);
if (offset > s->inode.vdi_size) { if (offset > s->inode.vdi_size) {
ret = sd_co_truncate(bs, offset, PREALLOC_MODE_OFF, NULL); ret = sd_co_truncate(bs, offset, false, PREALLOC_MODE_OFF, NULL);
if (ret < 0) { if (ret < 0) {
return ret; return ret;
} }

View File

@ -1295,7 +1295,8 @@ static int64_t ssh_getlength(BlockDriverState *bs)
} }
static int coroutine_fn ssh_co_truncate(BlockDriverState *bs, int64_t offset, static int coroutine_fn ssh_co_truncate(BlockDriverState *bs, int64_t offset,
PreallocMode prealloc, Error **errp) bool exact, PreallocMode prealloc,
Error **errp)
{ {
BDRVSSHState *s = bs->opaque; BDRVSSHState *s = bs->opaque;

View File

@ -45,9 +45,9 @@ backup_do_cow_return(void *job, int64_t offset, uint64_t bytes, int ret) "job %p
block_copy_skip(void *bcs, int64_t start) "bcs %p start %"PRId64 block_copy_skip(void *bcs, int64_t start) "bcs %p start %"PRId64
block_copy_skip_range(void *bcs, int64_t start, uint64_t bytes) "bcs %p start %"PRId64" bytes %"PRId64 block_copy_skip_range(void *bcs, int64_t start, uint64_t bytes) "bcs %p start %"PRId64" bytes %"PRId64
block_copy_process(void *bcs, int64_t start) "bcs %p start %"PRId64 block_copy_process(void *bcs, int64_t start) "bcs %p start %"PRId64
block_copy_with_bounce_buffer_read_fail(void *bcs, int64_t start, int ret) "bcs %p start %"PRId64" ret %d" block_copy_copy_range_fail(void *bcs, int64_t start, int ret) "bcs %p start %"PRId64" ret %d"
block_copy_with_bounce_buffer_write_fail(void *bcs, int64_t start, int ret) "bcs %p start %"PRId64" ret %d" block_copy_read_fail(void *bcs, int64_t start, int ret) "bcs %p start %"PRId64" ret %d"
block_copy_with_offload_fail(void *bcs, int64_t start, int ret) "bcs %p start %"PRId64" ret %d" block_copy_write_fail(void *bcs, int64_t start, int ret) "bcs %p start %"PRId64" ret %d"
# ../blockdev.c # ../blockdev.c
qmp_block_job_cancel(void *job) "job %p" qmp_block_job_cancel(void *job) "job %p"
@ -152,9 +152,12 @@ nvme_submit_command_raw(int c0, int c1, int c2, int c3, int c4, int c5, int c6,
nvme_handle_event(void *s) "s %p" nvme_handle_event(void *s) "s %p"
nvme_poll_cb(void *s) "s %p" nvme_poll_cb(void *s) "s %p"
nvme_prw_aligned(void *s, int is_write, uint64_t offset, uint64_t bytes, int flags, int niov) "s %p is_write %d offset %"PRId64" bytes %"PRId64" flags %d niov %d" nvme_prw_aligned(void *s, int is_write, uint64_t offset, uint64_t bytes, int flags, int niov) "s %p is_write %d offset %"PRId64" bytes %"PRId64" flags %d niov %d"
nvme_write_zeroes(void *s, uint64_t offset, uint64_t bytes, int flags) "s %p offset %"PRId64" bytes %"PRId64" flags %d"
nvme_qiov_unaligned(const void *qiov, int n, void *base, size_t size, int align) "qiov %p n %d base %p size 0x%zx align 0x%x" nvme_qiov_unaligned(const void *qiov, int n, void *base, size_t size, int align) "qiov %p n %d base %p size 0x%zx align 0x%x"
nvme_prw_buffered(void *s, uint64_t offset, uint64_t bytes, int niov, int is_write) "s %p offset %"PRId64" bytes %"PRId64" niov %d is_write %d" nvme_prw_buffered(void *s, uint64_t offset, uint64_t bytes, int niov, int is_write) "s %p offset %"PRId64" bytes %"PRId64" niov %d is_write %d"
nvme_rw_done(void *s, int is_write, uint64_t offset, uint64_t bytes, int ret) "s %p is_write %d offset %"PRId64" bytes %"PRId64" ret %d" nvme_rw_done(void *s, int is_write, uint64_t offset, uint64_t bytes, int ret) "s %p is_write %d offset %"PRId64" bytes %"PRId64" ret %d"
nvme_dsm(void *s, uint64_t offset, uint64_t bytes) "s %p offset %"PRId64" bytes %"PRId64""
nvme_dsm_done(void *s, uint64_t offset, uint64_t bytes, int ret) "s %p offset %"PRId64" bytes %"PRId64" ret %d"
nvme_dma_map_flush(void *s) "s %p" nvme_dma_map_flush(void *s) "s %p"
nvme_free_req_queue_wait(void *q) "q %p" nvme_free_req_queue_wait(void *q) "q %p"
nvme_cmd_map_qiov(void *s, void *cmd, void *req, void *qiov, int entries) "s %p cmd %p req %p qiov %p entries %d" nvme_cmd_map_qiov(void *s, void *cmd, void *req, void *qiov, int entries) "s %p cmd %p req %p qiov %p entries %d"

View File

@ -874,7 +874,7 @@ static int coroutine_fn vdi_co_do_create(BlockdevCreateOptions *create_options,
} }
if (image_type == VDI_TYPE_STATIC) { if (image_type == VDI_TYPE_STATIC) {
ret = blk_truncate(blk, offset + blocks * block_size, ret = blk_truncate(blk, offset + blocks * block_size, false,
PREALLOC_MODE_OFF, errp); PREALLOC_MODE_OFF, errp);
if (ret < 0) { if (ret < 0) {
error_prepend(errp, "Failed to statically allocate file"); error_prepend(errp, "Failed to statically allocate file");

View File

@ -557,8 +557,8 @@ static int vhdx_log_flush(BlockDriverState *bs, BDRVVHDXState *s,
ret = -EINVAL; ret = -EINVAL;
goto exit; goto exit;
} }
ret = bdrv_truncate(bs->file, new_file_size, PREALLOC_MODE_OFF, ret = bdrv_truncate(bs->file, new_file_size, false,
NULL); PREALLOC_MODE_OFF, NULL);
if (ret < 0) { if (ret < 0) {
goto exit; goto exit;
} }

View File

@ -1263,7 +1263,7 @@ static int vhdx_allocate_block(BlockDriverState *bs, BDRVVHDXState *s,
return -EINVAL; return -EINVAL;
} }
return bdrv_truncate(bs->file, *new_offset + s->block_size, return bdrv_truncate(bs->file, *new_offset + s->block_size, false,
PREALLOC_MODE_OFF, NULL); PREALLOC_MODE_OFF, NULL);
} }
@ -1702,12 +1702,13 @@ static int vhdx_create_bat(BlockBackend *blk, BDRVVHDXState *s,
if (type == VHDX_TYPE_DYNAMIC) { if (type == VHDX_TYPE_DYNAMIC) {
/* All zeroes, so we can just extend the file - the end of the BAT /* All zeroes, so we can just extend the file - the end of the BAT
* is the furthest thing we have written yet */ * is the furthest thing we have written yet */
ret = blk_truncate(blk, data_file_offset, PREALLOC_MODE_OFF, errp); ret = blk_truncate(blk, data_file_offset, false, PREALLOC_MODE_OFF,
errp);
if (ret < 0) { if (ret < 0) {
goto exit; goto exit;
} }
} else if (type == VHDX_TYPE_FIXED) { } else if (type == VHDX_TYPE_FIXED) {
ret = blk_truncate(blk, data_file_offset + image_size, ret = blk_truncate(blk, data_file_offset + image_size, false,
PREALLOC_MODE_OFF, errp); PREALLOC_MODE_OFF, errp);
if (ret < 0) { if (ret < 0) {
goto exit; goto exit;

View File

@ -2076,7 +2076,7 @@ vmdk_co_pwritev_compressed(BlockDriverState *bs, uint64_t offset,
return length; return length;
} }
length = QEMU_ALIGN_UP(length, BDRV_SECTOR_SIZE); length = QEMU_ALIGN_UP(length, BDRV_SECTOR_SIZE);
ret = bdrv_truncate(s->extents[i].file, length, ret = bdrv_truncate(s->extents[i].file, length, false,
PREALLOC_MODE_OFF, NULL); PREALLOC_MODE_OFF, NULL);
if (ret < 0) { if (ret < 0) {
return ret; return ret;
@ -2118,7 +2118,7 @@ static int vmdk_init_extent(BlockBackend *blk,
int gd_buf_size; int gd_buf_size;
if (flat) { if (flat) {
ret = blk_truncate(blk, filesize, PREALLOC_MODE_OFF, errp); ret = blk_truncate(blk, filesize, false, PREALLOC_MODE_OFF, errp);
goto exit; goto exit;
} }
magic = cpu_to_be32(VMDK4_MAGIC); magic = cpu_to_be32(VMDK4_MAGIC);
@ -2181,7 +2181,7 @@ static int vmdk_init_extent(BlockBackend *blk,
goto exit; goto exit;
} }
ret = blk_truncate(blk, le64_to_cpu(header.grain_offset) << 9, ret = blk_truncate(blk, le64_to_cpu(header.grain_offset) << 9, false,
PREALLOC_MODE_OFF, errp); PREALLOC_MODE_OFF, errp);
if (ret < 0) { if (ret < 0) {
goto exit; goto exit;
@ -2523,7 +2523,7 @@ static int coroutine_fn vmdk_co_do_create(int64_t size,
/* bdrv_pwrite write padding zeros to align to sector, we don't need that /* bdrv_pwrite write padding zeros to align to sector, we don't need that
* for description file */ * for description file */
if (desc_offset == 0) { if (desc_offset == 0) {
ret = blk_truncate(blk, desc_len, PREALLOC_MODE_OFF, errp); ret = blk_truncate(blk, desc_len, false, PREALLOC_MODE_OFF, errp);
if (ret < 0) { if (ret < 0) {
goto exit; goto exit;
} }

View File

@ -898,7 +898,7 @@ static int create_fixed_disk(BlockBackend *blk, uint8_t *buf,
/* Add footer to total size */ /* Add footer to total size */
total_size += HEADER_SIZE; total_size += HEADER_SIZE;
ret = blk_truncate(blk, total_size, PREALLOC_MODE_OFF, errp); ret = blk_truncate(blk, total_size, false, PREALLOC_MODE_OFF, errp);
if (ret < 0) { if (ret < 0) {
return ret; return ret;
} }

View File

@ -3204,7 +3204,7 @@ void qmp_block_resize(bool has_device, const char *device,
} }
bdrv_drained_begin(bs); bdrv_drained_begin(bs);
ret = blk_truncate(blk, size, PREALLOC_MODE_OFF, errp); ret = blk_truncate(blk, size, false, PREALLOC_MODE_OFF, errp);
bdrv_drained_end(bs); bdrv_drained_end(bs);
out: out:

View File

@ -42,9 +42,9 @@
*/ */
static VirtIOFeature feature_sizes[] = { static VirtIOFeature feature_sizes[] = {
{.flags = 1ULL << VIRTIO_BLK_F_DISCARD, {.flags = 1ULL << VIRTIO_BLK_F_DISCARD,
.end = virtio_endof(struct virtio_blk_config, discard_sector_alignment)}, .end = endof(struct virtio_blk_config, discard_sector_alignment)},
{.flags = 1ULL << VIRTIO_BLK_F_WRITE_ZEROES, {.flags = 1ULL << VIRTIO_BLK_F_WRITE_ZEROES,
.end = virtio_endof(struct virtio_blk_config, write_zeroes_may_unmap)}, .end = endof(struct virtio_blk_config, write_zeroes_may_unmap)},
{} {}
}; };

View File

@ -90,15 +90,15 @@ static inline __virtio16 *virtio_net_rsc_ext_num_dupacks(
static VirtIOFeature feature_sizes[] = { static VirtIOFeature feature_sizes[] = {
{.flags = 1ULL << VIRTIO_NET_F_MAC, {.flags = 1ULL << VIRTIO_NET_F_MAC,
.end = virtio_endof(struct virtio_net_config, mac)}, .end = endof(struct virtio_net_config, mac)},
{.flags = 1ULL << VIRTIO_NET_F_STATUS, {.flags = 1ULL << VIRTIO_NET_F_STATUS,
.end = virtio_endof(struct virtio_net_config, status)}, .end = endof(struct virtio_net_config, status)},
{.flags = 1ULL << VIRTIO_NET_F_MQ, {.flags = 1ULL << VIRTIO_NET_F_MQ,
.end = virtio_endof(struct virtio_net_config, max_virtqueue_pairs)}, .end = endof(struct virtio_net_config, max_virtqueue_pairs)},
{.flags = 1ULL << VIRTIO_NET_F_MTU, {.flags = 1ULL << VIRTIO_NET_F_MTU,
.end = virtio_endof(struct virtio_net_config, mtu)}, .end = endof(struct virtio_net_config, mtu)},
{.flags = 1ULL << VIRTIO_NET_F_SPEED_DUPLEX, {.flags = 1ULL << VIRTIO_NET_F_SPEED_DUPLEX,
.end = virtio_endof(struct virtio_net_config, duplex)}, .end = endof(struct virtio_net_config, duplex)},
{} {}
}; };

View File

@ -16,6 +16,7 @@
#define BLOCK_COPY_H #define BLOCK_COPY_H
#include "block/block.h" #include "block/block.h"
#include "qemu/co-shared-resource.h"
typedef struct BlockCopyInFlightReq { typedef struct BlockCopyInFlightReq {
int64_t start_byte; int64_t start_byte;
@ -37,7 +38,7 @@ typedef struct BlockCopyState {
BdrvDirtyBitmap *copy_bitmap; BdrvDirtyBitmap *copy_bitmap;
int64_t cluster_size; int64_t cluster_size;
bool use_copy_range; bool use_copy_range;
int64_t copy_range_size; int64_t copy_size;
uint64_t len; uint64_t len;
QLIST_HEAD(, BlockCopyInFlightReq) inflight_reqs; QLIST_HEAD(, BlockCopyInFlightReq) inflight_reqs;
@ -69,6 +70,8 @@ typedef struct BlockCopyState {
*/ */
ProgressResetCallbackFunc progress_reset_callback; ProgressResetCallbackFunc progress_reset_callback;
void *progress_opaque; void *progress_opaque;
SharedResource *mem;
} BlockCopyState; } BlockCopyState;
BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target, BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target,

View File

@ -346,10 +346,10 @@ BlockDriverState *bdrv_find_backing_image(BlockDriverState *bs,
const char *backing_file); const char *backing_file);
void bdrv_refresh_filename(BlockDriverState *bs); void bdrv_refresh_filename(BlockDriverState *bs);
int coroutine_fn bdrv_co_truncate(BdrvChild *child, int64_t offset, int coroutine_fn bdrv_co_truncate(BdrvChild *child, int64_t offset, bool exact,
PreallocMode prealloc, Error **errp);
int bdrv_truncate(BdrvChild *child, int64_t offset, bool exact,
PreallocMode prealloc, Error **errp); PreallocMode prealloc, Error **errp);
int bdrv_truncate(BdrvChild *child, int64_t offset, PreallocMode prealloc,
Error **errp);
int64_t bdrv_nb_sectors(BlockDriverState *bs); int64_t bdrv_nb_sectors(BlockDriverState *bs);
int64_t bdrv_getlength(BlockDriverState *bs); int64_t bdrv_getlength(BlockDriverState *bs);

View File

@ -334,8 +334,23 @@ struct BlockDriver {
* bdrv_parse_filename. * bdrv_parse_filename.
*/ */
const char *protocol_name; const char *protocol_name;
/*
* Truncate @bs to @offset bytes using the given @prealloc mode
* when growing. Modes other than PREALLOC_MODE_OFF should be
* rejected when shrinking @bs.
*
* If @exact is true, @bs must be resized to exactly @offset.
* Otherwise, it is sufficient for @bs (if it is a host block
* device and thus there is no way to resize it) to be at least
* @offset bytes in length.
*
* If @exact is true and this function fails but would succeed
* with @exact = false, it should return -ENOTSUP.
*/
int coroutine_fn (*bdrv_co_truncate)(BlockDriverState *bs, int64_t offset, int coroutine_fn (*bdrv_co_truncate)(BlockDriverState *bs, int64_t offset,
PreallocMode prealloc, Error **errp); bool exact, PreallocMode prealloc,
Error **errp);
int64_t (*bdrv_getlength)(BlockDriverState *bs); int64_t (*bdrv_getlength)(BlockDriverState *bs);
bool has_variable_length; bool has_variable_length;

View File

@ -653,12 +653,29 @@ typedef struct NvmeIdNs {
uint8_t mc; uint8_t mc;
uint8_t dpc; uint8_t dpc;
uint8_t dps; uint8_t dps;
uint8_t res30[98];
uint8_t nmic;
uint8_t rescap;
uint8_t fpi;
uint8_t dlfeat;
uint8_t res34[94];
NvmeLBAF lbaf[16]; NvmeLBAF lbaf[16];
uint8_t res192[192]; uint8_t res192[192];
uint8_t vs[3712]; uint8_t vs[3712];
} NvmeIdNs; } NvmeIdNs;
/*Deallocate Logical Block Features*/
#define NVME_ID_NS_DLFEAT_GUARD_CRC(dlfeat) ((dlfeat) & 0x10)
#define NVME_ID_NS_DLFEAT_WRITE_ZEROES(dlfeat) ((dlfeat) & 0x08)
#define NVME_ID_NS_DLFEAT_READ_BEHAVIOR(dlfeat) ((dlfeat) & 0x7)
#define NVME_ID_NS_DLFEAT_READ_BEHAVIOR_UNDEFINED 0
#define NVME_ID_NS_DLFEAT_READ_BEHAVIOR_ZEROES 1
#define NVME_ID_NS_DLFEAT_READ_BEHAVIOR_ONES 2
#define NVME_ID_NS_NSFEAT_THIN(nsfeat) ((nsfeat & 0x1)) #define NVME_ID_NS_NSFEAT_THIN(nsfeat) ((nsfeat & 0x1))
#define NVME_ID_NS_FLBAS_EXTENDED(flbas) ((flbas >> 4) & 0x1) #define NVME_ID_NS_FLBAS_EXTENDED(flbas) ((flbas >> 4) & 0x1)
#define NVME_ID_NS_FLBAS_INDEX(flbas) ((flbas & 0xf)) #define NVME_ID_NS_FLBAS_INDEX(flbas) ((flbas & 0xf))

View File

@ -37,13 +37,6 @@ static inline hwaddr vring_align(hwaddr addr,
return QEMU_ALIGN_UP(addr, align); return QEMU_ALIGN_UP(addr, align);
} }
/*
* Calculate the number of bytes up to and including the given 'field' of
* 'container'.
*/
#define virtio_endof(container, field) \
(offsetof(container, field) + sizeof_field(container, field))
typedef struct VirtIOFeature { typedef struct VirtIOFeature {
uint64_t flags; uint64_t flags;
size_t end; size_t end;

View File

@ -0,0 +1,71 @@
/*
* Helper functionality for distributing a fixed total amount of
* an abstract resource among multiple coroutines.
*
* Copyright (c) 2019 Virtuozzo International GmbH
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef QEMU_CO_SHARED_RESOURCE_H
#define QEMU_CO_SHARED_RESOURCE_H
typedef struct SharedResource SharedResource;
/*
* Create SharedResource structure
*
* @total: total amount of some resource to be shared between clients
*
* Note: this API is not thread-safe.
*/
SharedResource *shres_create(uint64_t total);
/*
* Release SharedResource structure
*
* This function may only be called once everything allocated by all
* clients has been deallocated.
*/
void shres_destroy(SharedResource *s);
/*
* Try to allocate an amount of @n. Return true on success, and false
* if there is too little left of the collective resource to fulfill
* the request.
*/
bool co_try_get_from_shres(SharedResource *s, uint64_t n);
/*
* Allocate an amount of @n, and, if necessary, yield until
* that becomes possible.
*/
void coroutine_fn co_get_from_shres(SharedResource *s, uint64_t n);
/*
* Deallocate an amount of @n. The total amount allocated by a caller
* does not need to be deallocated/released with a single call, but may
* be split over several calls. For example, get(4), get(3), and then
* put(5), put(2).
*/
void coroutine_fn co_put_to_shres(SharedResource *s, uint64_t n);
#endif /* QEMU_CO_SHARED_RESOURCE_H */

View File

@ -60,6 +60,13 @@
#define sizeof_field(type, field) sizeof(((type *)0)->field) #define sizeof_field(type, field) sizeof(((type *)0)->field)
/*
* Calculate the number of bytes up to and including the given 'field' of
* 'container'.
*/
#define endof(container, field) \
(offsetof(container, field) + sizeof_field(container, field))
/* Convert from a base type to a parent type, with compile time checking. */ /* Convert from a base type to a parent type, with compile time checking. */
#ifdef __GNUC__ #ifdef __GNUC__
#define DO_UPCAST(type, field, dev) ( __extension__ ( { \ #define DO_UPCAST(type, field, dev) ( __extension__ ( { \

View File

@ -121,6 +121,10 @@ void blk_set_dev_ops(BlockBackend *blk, const BlockDevOps *ops, void *opaque);
int coroutine_fn blk_co_preadv(BlockBackend *blk, int64_t offset, int coroutine_fn blk_co_preadv(BlockBackend *blk, int64_t offset,
unsigned int bytes, QEMUIOVector *qiov, unsigned int bytes, QEMUIOVector *qiov,
BdrvRequestFlags flags); BdrvRequestFlags flags);
int coroutine_fn blk_co_pwritev_part(BlockBackend *blk, int64_t offset,
unsigned int bytes,
QEMUIOVector *qiov, size_t qiov_offset,
BdrvRequestFlags flags);
int coroutine_fn blk_co_pwritev(BlockBackend *blk, int64_t offset, int coroutine_fn blk_co_pwritev(BlockBackend *blk, int64_t offset,
unsigned int bytes, QEMUIOVector *qiov, unsigned int bytes, QEMUIOVector *qiov,
BdrvRequestFlags flags); BdrvRequestFlags flags);
@ -233,8 +237,8 @@ int coroutine_fn blk_co_pwrite_zeroes(BlockBackend *blk, int64_t offset,
int bytes, BdrvRequestFlags flags); int bytes, BdrvRequestFlags flags);
int blk_pwrite_compressed(BlockBackend *blk, int64_t offset, const void *buf, int blk_pwrite_compressed(BlockBackend *blk, int64_t offset, const void *buf,
int bytes); int bytes);
int blk_truncate(BlockBackend *blk, int64_t offset, PreallocMode prealloc, int blk_truncate(BlockBackend *blk, int64_t offset, bool exact,
Error **errp); PreallocMode prealloc, Error **errp);
int blk_pdiscard(BlockBackend *blk, int64_t offset, int bytes); int blk_pdiscard(BlockBackend *blk, int64_t offset, int bytes);
int blk_save_vmstate(BlockBackend *blk, const uint8_t *buf, int blk_save_vmstate(BlockBackend *blk, const uint8_t *buf,
int64_t pos, int size); int64_t pos, int size);

View File

@ -71,7 +71,7 @@ class QEMUMachine(object):
def __init__(self, binary, args=None, wrapper=None, name=None, def __init__(self, binary, args=None, wrapper=None, name=None,
test_dir="/var/tmp", monitor_address=None, test_dir="/var/tmp", monitor_address=None,
socket_scm_helper=None): socket_scm_helper=None, sock_dir=None):
''' '''
Initialize a QEMUMachine Initialize a QEMUMachine
@ -90,6 +90,8 @@ class QEMUMachine(object):
wrapper = [] wrapper = []
if name is None: if name is None:
name = "qemu-%d" % os.getpid() name = "qemu-%d" % os.getpid()
if sock_dir is None:
sock_dir = test_dir
self._name = name self._name = name
self._monitor_address = monitor_address self._monitor_address = monitor_address
self._vm_monitor = None self._vm_monitor = None
@ -106,12 +108,14 @@ class QEMUMachine(object):
self._qemu_full_args = None self._qemu_full_args = None
self._test_dir = test_dir self._test_dir = test_dir
self._temp_dir = None self._temp_dir = None
self._sock_dir = sock_dir
self._launched = False self._launched = False
self._machine = None self._machine = None
self._console_set = False self._console_set = False
self._console_device_type = None self._console_device_type = None
self._console_address = None self._console_address = None
self._console_socket = None self._console_socket = None
self._remove_files = []
# just in case logging wasn't configured by the main script: # just in case logging wasn't configured by the main script:
logging.basicConfig() logging.basicConfig()
@ -236,8 +240,9 @@ class QEMUMachine(object):
if self._machine is not None: if self._machine is not None:
args.extend(['-machine', self._machine]) args.extend(['-machine', self._machine])
if self._console_set: if self._console_set:
self._console_address = os.path.join(self._temp_dir, self._console_address = os.path.join(self._sock_dir,
self._name + "-console.sock") self._name + "-console.sock")
self._remove_files.append(self._console_address)
chardev = ('socket,id=console,path=%s,server,nowait' % chardev = ('socket,id=console,path=%s,server,nowait' %
self._console_address) self._console_address)
args.extend(['-chardev', chardev]) args.extend(['-chardev', chardev])
@ -253,8 +258,9 @@ class QEMUMachine(object):
if self._monitor_address is not None: if self._monitor_address is not None:
self._vm_monitor = self._monitor_address self._vm_monitor = self._monitor_address
else: else:
self._vm_monitor = os.path.join(self._temp_dir, self._vm_monitor = os.path.join(self._sock_dir,
self._name + "-monitor.sock") self._name + "-monitor.sock")
self._remove_files.append(self._vm_monitor)
self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log") self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
self._qemu_log_file = open(self._qemu_log_path, 'wb') self._qemu_log_file = open(self._qemu_log_path, 'wb')
@ -279,6 +285,9 @@ class QEMUMachine(object):
shutil.rmtree(self._temp_dir) shutil.rmtree(self._temp_dir)
self._temp_dir = None self._temp_dir = None
while len(self._remove_files) > 0:
self._remove_if_exists(self._remove_files.pop())
def launch(self): def launch(self):
""" """
Launch the VM and make sure we cleanup and expose the Launch the VM and make sure we cleanup and expose the

View File

@ -84,14 +84,17 @@ class QEMUQtestMachine(QEMUMachine):
'''A QEMU VM''' '''A QEMU VM'''
def __init__(self, binary, args=None, name=None, test_dir="/var/tmp", def __init__(self, binary, args=None, name=None, test_dir="/var/tmp",
socket_scm_helper=None): socket_scm_helper=None, sock_dir=None):
if name is None: if name is None:
name = "qemu-%d" % os.getpid() name = "qemu-%d" % os.getpid()
if sock_dir is None:
sock_dir = test_dir
super(QEMUQtestMachine, super(QEMUQtestMachine,
self).__init__(binary, args, name=name, test_dir=test_dir, self).__init__(binary, args, name=name, test_dir=test_dir,
socket_scm_helper=socket_scm_helper) socket_scm_helper=socket_scm_helper,
sock_dir=sock_dir)
self._qtest = None self._qtest = None
self._qtest_path = os.path.join(test_dir, name + "-qtest.sock") self._qtest_path = os.path.join(sock_dir, name + "-qtest.sock")
def _base_args(self): def _base_args(self):
args = super(QEMUQtestMachine, self)._base_args() args = super(QEMUQtestMachine, self)._base_args()

View File

@ -3656,7 +3656,7 @@ static int img_resize(int argc, char **argv)
Error *err = NULL; Error *err = NULL;
int c, ret, relative; int c, ret, relative;
const char *filename, *fmt, *size; const char *filename, *fmt, *size;
int64_t n, total_size, current_size, new_size; int64_t n, total_size, current_size;
bool quiet = false; bool quiet = false;
BlockBackend *blk = NULL; BlockBackend *blk = NULL;
PreallocMode prealloc = PREALLOC_MODE_OFF; PreallocMode prealloc = PREALLOC_MODE_OFF;
@ -3831,43 +3831,17 @@ static int img_resize(int argc, char **argv)
} }
} }
ret = blk_truncate(blk, total_size, prealloc, &err); /*
if (ret < 0) { * The user expects the image to have the desired size after
error_report_err(err); * resizing, so pass @exact=true. It is of no use to report
goto out; * success when the image has not actually been resized.
} */
ret = blk_truncate(blk, total_size, true, prealloc, &err);
new_size = blk_getlength(blk); if (!ret) {
if (new_size < 0) {
error_report("Failed to verify truncated image length: %s",
strerror(-new_size));
ret = -1;
goto out;
}
/* Some block drivers implement a truncation method, but only so
* the user can cause qemu to refresh the image's size from disk.
* The idea is that the user resizes the image outside of qemu and
* then invokes block_resize to inform qemu about it.
* (This includes iscsi and file-posix for device files.)
* Of course, that is not the behavior someone invoking
* qemu-img resize would find useful, so we catch that behavior
* here and tell the user. */
if (new_size != total_size && new_size == current_size) {
error_report("Image was not resized; resizing may not be supported "
"for this image");
ret = -1;
goto out;
}
if (new_size != total_size) {
warn_report("Image should have been resized to %" PRIi64
" bytes, but was resized to %" PRIi64 " bytes",
total_size, new_size);
}
qprintf(quiet, "Image resized.\n"); qprintf(quiet, "Image resized.\n");
} else {
error_report_err(err);
}
out: out:
blk_unref(blk); blk_unref(blk);
if (ret) { if (ret) {

View File

@ -1710,7 +1710,12 @@ static int truncate_f(BlockBackend *blk, int argc, char **argv)
return offset; return offset;
} }
ret = blk_truncate(blk, offset, PREALLOC_MODE_OFF, &local_err); /*
* qemu-io is a debugging tool, so let us be strict here and pass
* exact=true. It is better to err on the "emit more errors" side
* than to be overly permissive.
*/
ret = blk_truncate(blk, offset, true, PREALLOC_MODE_OFF, &local_err);
if (ret < 0) { if (ret < 0) {
error_report_err(local_err); error_report_err(local_err);
return ret; return ret;

View File

@ -28,7 +28,7 @@ status=1 # failure is the default!
_cleanup() _cleanup()
{ {
rm -f nbd.sock rm -f "$SOCK_DIR/nbd.sock"
rm -f nbd-fault-injector.out rm -f nbd-fault-injector.out
rm -f nbd-fault-injector.conf rm -f nbd-fault-injector.conf
} }
@ -80,10 +80,10 @@ EOF
if [ "$proto" = "tcp" ]; then if [ "$proto" = "tcp" ]; then
nbd_addr="127.0.0.1:0" nbd_addr="127.0.0.1:0"
else else
nbd_addr="$TEST_DIR/nbd.sock" nbd_addr="$SOCK_DIR/nbd.sock"
fi fi
rm -f "$TEST_DIR/nbd.sock" rm -f "$SOCK_DIR/nbd.sock"
echo > "$TEST_DIR/nbd-fault-injector.out" echo > "$TEST_DIR/nbd-fault-injector.out"
$PYTHON nbd-fault-injector.py $extra_args "$nbd_addr" "$TEST_DIR/nbd-fault-injector.conf" >"$TEST_DIR/nbd-fault-injector.out" 2>&1 & $PYTHON nbd-fault-injector.py $extra_args "$nbd_addr" "$TEST_DIR/nbd-fault-injector.conf" >"$TEST_DIR/nbd-fault-injector.out" 2>&1 &

View File

@ -110,43 +110,43 @@ read failed: Input/output error
=== Check disconnect before neg1 === === Check disconnect before neg1 ===
qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect after neg1 === === Check disconnect after neg1 ===
qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect 8 neg1 === === Check disconnect 8 neg1 ===
qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect 16 neg1 === === Check disconnect 16 neg1 ===
qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect before export === === Check disconnect before export ===
qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect after export === === Check disconnect after export ===
qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect 4 export === === Check disconnect 4 export ===
qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect 12 export === === Check disconnect 12 export ===
qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect 16 export === === Check disconnect 16 export ===
qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect before neg2 === === Check disconnect before neg2 ===
qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect after neg2 === === Check disconnect after neg2 ===
@ -154,11 +154,11 @@ read failed: Input/output error
=== Check disconnect 8 neg2 === === Check disconnect 8 neg2 ===
qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect 10 neg2 === === Check disconnect 10 neg2 ===
qemu-io: can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock qemu-io: can't open device nbd+unix:///foo?socket=SOCK_DIR/nbd.sock
=== Check disconnect before request === === Check disconnect before request ===
@ -195,23 +195,23 @@ read 512/512 bytes at offset 0
=== Check disconnect before neg-classic === === Check disconnect before neg-classic ===
qemu-io: can't open device nbd+unix:///?socket=TEST_DIR/nbd.sock qemu-io: can't open device nbd+unix:///?socket=SOCK_DIR/nbd.sock
=== Check disconnect 8 neg-classic === === Check disconnect 8 neg-classic ===
qemu-io: can't open device nbd+unix:///?socket=TEST_DIR/nbd.sock qemu-io: can't open device nbd+unix:///?socket=SOCK_DIR/nbd.sock
=== Check disconnect 16 neg-classic === === Check disconnect 16 neg-classic ===
qemu-io: can't open device nbd+unix:///?socket=TEST_DIR/nbd.sock qemu-io: can't open device nbd+unix:///?socket=SOCK_DIR/nbd.sock
=== Check disconnect 24 neg-classic === === Check disconnect 24 neg-classic ===
qemu-io: can't open device nbd+unix:///?socket=TEST_DIR/nbd.sock qemu-io: can't open device nbd+unix:///?socket=SOCK_DIR/nbd.sock
=== Check disconnect 28 neg-classic === === Check disconnect 28 neg-classic ===
qemu-io: can't open device nbd+unix:///?socket=TEST_DIR/nbd.sock qemu-io: can't open device nbd+unix:///?socket=SOCK_DIR/nbd.sock
=== Check disconnect after neg-classic === === Check disconnect after neg-classic ===

View File

@ -24,7 +24,7 @@ import iotests
nsec_per_sec = 1000000000 nsec_per_sec = 1000000000
class ThrottleTestCase(iotests.QMPTestCase): class ThrottleTestCase(iotests.QMPTestCase):
test_img = "null-aio://" test_driver = "null-aio"
max_drives = 3 max_drives = 3
def blockstats(self, device): def blockstats(self, device):
@ -35,10 +35,14 @@ class ThrottleTestCase(iotests.QMPTestCase):
return stat['rd_bytes'], stat['rd_operations'], stat['wr_bytes'], stat['wr_operations'] return stat['rd_bytes'], stat['rd_operations'], stat['wr_bytes'], stat['wr_operations']
raise Exception("Device not found for blockstats: %s" % device) raise Exception("Device not found for blockstats: %s" % device)
def required_drivers(self):
return [self.test_driver]
@iotests.skip_if_unsupported(required_drivers)
def setUp(self): def setUp(self):
self.vm = iotests.VM() self.vm = iotests.VM()
for i in range(0, self.max_drives): for i in range(0, self.max_drives):
self.vm.add_drive(self.test_img, "file.read-zeroes=on") self.vm.add_drive(self.test_driver + "://", "file.read-zeroes=on")
self.vm.launch() self.vm.launch()
def tearDown(self): def tearDown(self):
@ -264,16 +268,15 @@ class ThrottleTestCase(iotests.QMPTestCase):
self.assertEqual(self.blockstats('drive1')[0], 4096) self.assertEqual(self.blockstats('drive1')[0], 4096)
class ThrottleTestCoroutine(ThrottleTestCase): class ThrottleTestCoroutine(ThrottleTestCase):
test_img = "null-co://" test_driver = "null-co"
class ThrottleTestGroupNames(iotests.QMPTestCase): class ThrottleTestGroupNames(iotests.QMPTestCase):
test_img = "null-aio://"
max_drives = 3 max_drives = 3
def setUp(self): def setUp(self):
self.vm = iotests.VM() self.vm = iotests.VM()
for i in range(0, self.max_drives): for i in range(0, self.max_drives):
self.vm.add_drive(self.test_img, self.vm.add_drive("null-co://",
"throttling.iops-total=100,file.read-zeroes=on") "throttling.iops-total=100,file.read-zeroes=on")
self.vm.launch() self.vm.launch()
@ -376,10 +379,10 @@ class ThrottleTestRemovableMedia(iotests.QMPTestCase):
def test_removable_media(self): def test_removable_media(self):
# Add a couple of dummy nodes named cd0 and cd1 # Add a couple of dummy nodes named cd0 and cd1
result = self.vm.qmp("blockdev-add", driver="null-aio", result = self.vm.qmp("blockdev-add", driver="null-co",
read_zeroes=True, node_name="cd0") read_zeroes=True, node_name="cd0")
self.assert_qmp(result, 'return', {}) self.assert_qmp(result, 'return', {})
result = self.vm.qmp("blockdev-add", driver="null-aio", result = self.vm.qmp("blockdev-add", driver="null-co",
read_zeroes=True, node_name="cd1") read_zeroes=True, node_name="cd1")
self.assert_qmp(result, 'return', {}) self.assert_qmp(result, 'return', {})
@ -426,4 +429,6 @@ class ThrottleTestRemovableMedia(iotests.QMPTestCase):
if __name__ == '__main__': if __name__ == '__main__':
if 'null-co' not in iotests.supported_formats():
iotests.notrun('null-co driver support missing')
iotests.main(supported_fmts=["raw"]) iotests.main(supported_fmts=["raw"])

View File

@ -30,7 +30,7 @@ bad_offset = bad_sector * 512
blkdebug_file = os.path.join(iotests.test_dir, 'blkdebug.conf') blkdebug_file = os.path.join(iotests.test_dir, 'blkdebug.conf')
class BlockDeviceStatsTestCase(iotests.QMPTestCase): class BlockDeviceStatsTestCase(iotests.QMPTestCase):
test_img = "null-aio://" test_driver = "null-aio"
total_rd_bytes = 0 total_rd_bytes = 0
total_rd_ops = 0 total_rd_ops = 0
total_wr_bytes = 0 total_wr_bytes = 0
@ -67,6 +67,10 @@ sector = "%d"
''' % (bad_sector, bad_sector)) ''' % (bad_sector, bad_sector))
file.close() file.close()
def required_drivers(self):
return [self.test_driver]
@iotests.skip_if_unsupported(required_drivers)
def setUp(self): def setUp(self):
drive_args = [] drive_args = []
drive_args.append("stats-intervals.0=%d" % interval_length) drive_args.append("stats-intervals.0=%d" % interval_length)
@ -76,8 +80,8 @@ sector = "%d"
(self.account_failed and "on" or "off")) (self.account_failed and "on" or "off"))
drive_args.append("file.image.read-zeroes=on") drive_args.append("file.image.read-zeroes=on")
self.create_blkdebug_file() self.create_blkdebug_file()
self.vm = iotests.VM().add_drive('blkdebug:%s:%s' % self.vm = iotests.VM().add_drive('blkdebug:%s:%s://' %
(blkdebug_file, self.test_img), (blkdebug_file, self.test_driver),
','.join(drive_args)) ','.join(drive_args))
self.vm.launch() self.vm.launch()
# Set an initial value for the clock # Set an initial value for the clock
@ -337,7 +341,9 @@ class BlockDeviceStatsTestAccountBoth(BlockDeviceStatsTestCase):
account_failed = True account_failed = True
class BlockDeviceStatsTestCoroutine(BlockDeviceStatsTestCase): class BlockDeviceStatsTestCoroutine(BlockDeviceStatsTestCase):
test_img = "null-co://" test_driver = "null-co"
if __name__ == '__main__': if __name__ == '__main__':
if 'null-co' not in iotests.supported_formats():
iotests.notrun('null-co driver support missing')
iotests.main(supported_fmts=["raw"]) iotests.main(supported_fmts=["raw"])

View File

@ -34,7 +34,7 @@ _cleanup()
{ {
_cleanup_qemu _cleanup_qemu
_cleanup_test_img _cleanup_test_img
rm -f "$TEST_DIR/nbd" rm -f "$SOCK_DIR/nbd"
} }
trap "_cleanup; exit \$status" 0 1 2 3 15 trap "_cleanup; exit \$status" 0 1 2 3 15
@ -69,7 +69,7 @@ _send_qemu_cmd $QEMU_HANDLE \
_send_qemu_cmd $QEMU_HANDLE \ _send_qemu_cmd $QEMU_HANDLE \
"{ 'execute': 'nbd-server-start', "{ 'execute': 'nbd-server-start',
'arguments': { 'addr': { 'type': 'unix', 'arguments': { 'addr': { 'type': 'unix',
'data': { 'path': '$TEST_DIR/nbd' }}}}" \ 'data': { 'path': '$SOCK_DIR/nbd' }}}}" \
'return' 'return'
_send_qemu_cmd $QEMU_HANDLE \ _send_qemu_cmd $QEMU_HANDLE \
@ -78,7 +78,7 @@ _send_qemu_cmd $QEMU_HANDLE \
'return' 'return'
$QEMU_IO_PROG -f raw -r -c 'read -P 42 0 64k' \ $QEMU_IO_PROG -f raw -r -c 'read -P 42 0 64k' \
"nbd+unix:///drv?socket=$TEST_DIR/nbd" 2>&1 \ "nbd+unix:///drv?socket=$SOCK_DIR/nbd" 2>&1 \
| _filter_qemu_io | _filter_nbd | _filter_qemu_io | _filter_nbd
_send_qemu_cmd $QEMU_HANDLE \ _send_qemu_cmd $QEMU_HANDLE \
@ -87,7 +87,7 @@ _send_qemu_cmd $QEMU_HANDLE \
'return' 'return'
$QEMU_IO_PROG -f raw -r -c close \ $QEMU_IO_PROG -f raw -r -c close \
"nbd+unix:///drv?socket=$TEST_DIR/nbd" 2>&1 \ "nbd+unix:///drv?socket=$SOCK_DIR/nbd" 2>&1 \
| _filter_qemu_io | _filter_nbd | _filter_qemu_io | _filter_nbd
_send_qemu_cmd $QEMU_HANDLE \ _send_qemu_cmd $QEMU_HANDLE \

View File

@ -8,7 +8,7 @@ wrote 65536/65536 bytes at offset 0
read 65536/65536 bytes at offset 0 read 65536/65536 bytes at offset 0
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
{"return": {}} {"return": {}}
qemu-io: can't open device nbd+unix:///drv?socket=TEST_DIR/nbd: Requested export not available qemu-io: can't open device nbd+unix:///drv?socket=SOCK_DIR/nbd: Requested export not available
server reported: export 'drv' not present server reported: export 'drv' not present
{"return": {}} {"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}}

View File

@ -29,7 +29,7 @@ status=1 # failure is the default!
_cleanup() _cleanup()
{ {
_cleanup_qemu _cleanup_qemu
rm -f "$TEST_DIR/nbd" rm -f "$SOCK_DIR/nbd"
} }
trap "_cleanup; exit \$status" 0 1 2 3 15 trap "_cleanup; exit \$status" 0 1 2 3 15
@ -51,12 +51,12 @@ _send_qemu_cmd $QEMU_HANDLE \
_send_qemu_cmd $QEMU_HANDLE \ _send_qemu_cmd $QEMU_HANDLE \
"{ 'execute': 'nbd-server-start', "{ 'execute': 'nbd-server-start',
'arguments': { 'addr': { 'type': 'unix', 'arguments': { 'addr': { 'type': 'unix',
'data': { 'path': '$TEST_DIR/nbd' }}}}" \ 'data': { 'path': '$SOCK_DIR/nbd' }}}}" \
'return' 'return'
# This should just result in a client error, not in the server crashing # This should just result in a client error, not in the server crashing
$QEMU_IO_PROG -f raw -c quit \ $QEMU_IO_PROG -f raw -c quit \
"nbd+unix:///no_such_export?socket=$TEST_DIR/nbd" 2>&1 \ "nbd+unix:///no_such_export?socket=$SOCK_DIR/nbd" 2>&1 \
| _filter_qemu_io | _filter_nbd | _filter_qemu_io | _filter_nbd
_send_qemu_cmd $QEMU_HANDLE \ _send_qemu_cmd $QEMU_HANDLE \

View File

@ -1,7 +1,7 @@
QA output created by 143 QA output created by 143
{"return": {}} {"return": {}}
{"return": {}} {"return": {}}
qemu-io: can't open device nbd+unix:///no_such_export?socket=TEST_DIR/nbd: Requested export not available qemu-io: can't open device nbd+unix:///no_such_export?socket=SOCK_DIR/nbd: Requested export not available
server reported: export 'no_such_export' not present server reported: export 'no_such_export' not present
{"return": {}} {"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}}

View File

@ -32,7 +32,7 @@ NBD_IPV6_PORT_START = NBD_PORT_END
NBD_IPV6_PORT_END = NBD_IPV6_PORT_START + 1024 NBD_IPV6_PORT_END = NBD_IPV6_PORT_START + 1024
test_img = os.path.join(iotests.test_dir, 'test.img') test_img = os.path.join(iotests.test_dir, 'test.img')
unix_socket = os.path.join(iotests.test_dir, 'nbd.socket') unix_socket = os.path.join(iotests.sock_dir, 'nbd.socket')
def flatten_sock_addr(crumpled_address): def flatten_sock_addr(crumpled_address):

View File

@ -26,7 +26,7 @@ echo "QA output created by $seq"
status=1 # failure is the default! status=1 # failure is the default!
MIG_SOCKET="${TEST_DIR}/migrate" MIG_SOCKET="${SOCK_DIR}/migrate"
_cleanup() _cleanup()
{ {

View File

@ -31,7 +31,7 @@ _cleanup()
{ {
_cleanup_test_img _cleanup_test_img
rm -f "$TEST_IMG.overlay" rm -f "$TEST_IMG.overlay"
rm -f "$TEST_DIR/nbd.socket" rm -f "$SOCK_DIR/nbd.socket"
} }
trap "_cleanup; exit \$status" 0 1 2 3 15 trap "_cleanup; exit \$status" 0 1 2 3 15
@ -133,7 +133,7 @@ success_or_failure=y _send_qemu_cmd $QEMU_HANDLE \
'addr': { 'addr': {
'type': 'unix', 'type': 'unix',
'data': { 'data': {
'path': '$TEST_DIR/nbd.socket' 'path': '$SOCK_DIR/nbd.socket'
} } } }" \ } } } }" \
'return' \ 'return' \
'error' 'error'

View File

@ -26,7 +26,7 @@ echo "QA output created by $seq"
status=1 # failure is the default! status=1 # failure is the default!
MIG_SOCKET="${TEST_DIR}/migrate" MIG_SOCKET="${SOCK_DIR}/migrate"
_cleanup() _cleanup()
{ {

View File

@ -31,7 +31,7 @@ _cleanup()
{ {
_cleanup_qemu _cleanup_qemu
_cleanup_test_img _cleanup_test_img
rm -f "$TEST_DIR/nbd" rm -f "$SOCK_DIR/nbd"
} }
trap "_cleanup; exit \$status" 0 1 2 3 15 trap "_cleanup; exit \$status" 0 1 2 3 15
@ -66,7 +66,7 @@ else
QEMU_COMM_TIMEOUT=1 QEMU_COMM_TIMEOUT=1
fi fi
_send_qemu_cmd $h "nbd_server_start unix:$TEST_DIR/nbd" "(qemu)" _send_qemu_cmd $h "nbd_server_start unix:$SOCK_DIR/nbd" "(qemu)"
_send_qemu_cmd $h "nbd_server_add -w drive0" "(qemu)" _send_qemu_cmd $h "nbd_server_add -w drive0" "(qemu)"
_send_qemu_cmd $h "q" "(qemu)" _send_qemu_cmd $h "q" "(qemu)"

View File

@ -1,7 +1,7 @@
QA output created by 192 QA output created by 192
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
QEMU X.Y.Z monitor - type 'help' for more information QEMU X.Y.Z monitor - type 'help' for more information
(qemu) nbd_server_start unix:TEST_DIR/nbd (qemu) nbd_server_start unix:SOCK_DIR/nbd
(qemu) nbd_server_add -w drive0 (qemu) nbd_server_add -w drive0
(qemu) q (qemu) q
*** done *** done

View File

@ -26,8 +26,8 @@ iotests.verify_platform(['linux'])
with iotests.FilePath('source.img') as source_img_path, \ with iotests.FilePath('source.img') as source_img_path, \
iotests.FilePath('dest.img') as dest_img_path, \ iotests.FilePath('dest.img') as dest_img_path, \
iotests.FilePath('migration.sock') as migration_sock_path, \ iotests.FilePaths(['migration.sock', 'nbd.sock'], iotests.sock_dir) as \
iotests.FilePath('nbd.sock') as nbd_sock_path, \ [migration_sock_path, nbd_sock_path], \
iotests.VM('source') as source_vm, \ iotests.VM('source') as source_vm, \
iotests.VM('dest') as dest_vm: iotests.VM('dest') as dest_vm:

View File

@ -24,7 +24,7 @@ echo "QA output created by $seq"
status=1 # failure is the default! status=1 # failure is the default!
MIG_SOCKET="${TEST_DIR}/migrate" MIG_SOCKET="${SOCK_DIR}/migrate"
# get standard environment, filters and checks # get standard environment, filters and checks
. ./common.rc . ./common.rc

View File

@ -24,7 +24,7 @@ import iotests
import time import time
from iotests import qemu_img_create, qemu_io, filter_qemu_io, QemuIoInteractive from iotests import qemu_img_create, qemu_io, filter_qemu_io, QemuIoInteractive
nbd_sock = os.path.join(iotests.test_dir, 'nbd_sock') nbd_sock = os.path.join(iotests.sock_dir, 'nbd_sock')
nbd_uri = 'nbd+unix:///exp?socket=' + nbd_sock nbd_uri = 'nbd+unix:///exp?socket=' + nbd_sock
disk = os.path.join(iotests.test_dir, 'disk') disk = os.path.join(iotests.test_dir, 'disk')

View File

@ -26,7 +26,7 @@ iotests.verify_image_format(supported_fmts=['generic'])
with iotests.FilePath('disk.img') as disk_img_path, \ with iotests.FilePath('disk.img') as disk_img_path, \
iotests.FilePath('disk-snapshot.img') as disk_snapshot_img_path, \ iotests.FilePath('disk-snapshot.img') as disk_snapshot_img_path, \
iotests.FilePath('nbd.sock') as nbd_sock_path, \ iotests.FilePath('nbd.sock', iotests.sock_dir) as nbd_sock_path, \
iotests.VM() as vm: iotests.VM() as vm:
img_size = '10M' img_size = '10M'

View File

@ -24,7 +24,8 @@ from iotests import qemu_img_create, qemu_io, qemu_img_verbose, qemu_nbd, \
iotests.verify_image_format(supported_fmts=['qcow2']) iotests.verify_image_format(supported_fmts=['qcow2'])
disk, nbd_sock = file_path('disk', 'nbd-sock') disk = file_path('disk')
nbd_sock = file_path('nbd-sock', base_dir=iotests.sock_dir)
nbd_uri = 'nbd+unix:///exp?socket=' + nbd_sock nbd_uri = 'nbd+unix:///exp?socket=' + nbd_sock
qemu_img_create('-f', iotests.imgfmt, disk, '1M') qemu_img_create('-f', iotests.imgfmt, disk, '1M')

View File

@ -48,7 +48,7 @@ remainder = [("0xd5", "0x108000", "32k"), # Right-end of partial-left [1]
with iotests.FilePath('base.img') as base_img_path, \ with iotests.FilePath('base.img') as base_img_path, \
iotests.FilePath('fleece.img') as fleece_img_path, \ iotests.FilePath('fleece.img') as fleece_img_path, \
iotests.FilePath('nbd.sock') as nbd_sock_path, \ iotests.FilePath('nbd.sock', iotests.sock_dir) as nbd_sock_path, \
iotests.VM() as vm: iotests.VM() as vm:
log('--- Setting up images ---') log('--- Setting up images ---')

View File

@ -28,7 +28,7 @@ _cleanup()
nbd_server_stop nbd_server_stop
_cleanup_test_img _cleanup_test_img
_cleanup_qemu _cleanup_qemu
rm -f "$TEST_DIR/nbd" rm -f "$SOCK_DIR/nbd"
} }
trap "_cleanup; exit \$status" 0 1 2 3 15 trap "_cleanup; exit \$status" 0 1 2 3 15
@ -125,11 +125,11 @@ _send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
"arguments":{"device":"n"}}' "error" # Attempt add without server "arguments":{"device":"n"}}' "error" # Attempt add without server
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-start", _send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-start",
"arguments":{"addr":{"type":"unix", "arguments":{"addr":{"type":"unix",
"data":{"path":"'"$TEST_DIR/nbd"'"}}}}' "return" "data":{"path":"'"$SOCK_DIR/nbd"'"}}}}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-start", _send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-start",
"arguments":{"addr":{"type":"unix", "arguments":{"addr":{"type":"unix",
"data":{"path":"'"$TEST_DIR/nbd"1'"}}}}' "error" # Attempt second server "data":{"path":"'"$SOCK_DIR/nbd"1'"}}}}' "error" # Attempt second server
$QEMU_NBD_PROG -L -k "$TEST_DIR/nbd" $QEMU_NBD_PROG -L -k "$SOCK_DIR/nbd"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add", _send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
"arguments":{"device":"n", "bitmap":"b"}}' "return" "arguments":{"device":"n", "bitmap":"b"}}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add", _send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
@ -145,14 +145,14 @@ _send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add", _send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
"arguments":{"device":"n", "name":"n2", "writable":true, "arguments":{"device":"n", "name":"n2", "writable":true,
"bitmap":"b2"}}' "return" "bitmap":"b2"}}' "return"
$QEMU_NBD_PROG -L -k "$TEST_DIR/nbd" $QEMU_NBD_PROG -L -k "$SOCK_DIR/nbd"
echo echo
echo "=== Contrast normal status to large granularity dirty-bitmap ===" echo "=== Contrast normal status to large granularity dirty-bitmap ==="
echo echo
QEMU_IO_OPTIONS=$QEMU_IO_OPTIONS_NO_FMT QEMU_IO_OPTIONS=$QEMU_IO_OPTIONS_NO_FMT
IMG="driver=nbd,export=n,server.type=unix,server.path=$TEST_DIR/nbd" IMG="driver=nbd,export=n,server.type=unix,server.path=$SOCK_DIR/nbd"
$QEMU_IO -r -c 'r -P 0x22 512 512' -c 'r -P 0 512k 512k' -c 'r -P 0x11 1m 1m' \ $QEMU_IO -r -c 'r -P 0x22 512 512' -c 'r -P 0 512k 512k' -c 'r -P 0x11 1m 1m' \
-c 'r -P 0x33 2m 2m' --image-opts "$IMG" | _filter_qemu_io -c 'r -P 0x33 2m 2m' --image-opts "$IMG" | _filter_qemu_io
$QEMU_IMG map --output=json --image-opts \ $QEMU_IMG map --output=json --image-opts \
@ -164,7 +164,7 @@ echo
echo "=== Contrast to small granularity dirty-bitmap ===" echo "=== Contrast to small granularity dirty-bitmap ==="
echo echo
IMG="driver=nbd,export=n2,server.type=unix,server.path=$TEST_DIR/nbd" IMG="driver=nbd,export=n2,server.type=unix,server.path=$SOCK_DIR/nbd"
$QEMU_IMG map --output=json --image-opts \ $QEMU_IMG map --output=json --image-opts \
"$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b2" | _filter_qemu_img_map "$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b2" | _filter_qemu_img_map

View File

@ -29,7 +29,7 @@ status=1 # failure is the default!
_cleanup() _cleanup()
{ {
rm -f "$TEST_DIR/nbd" rm -f "$SOCK_DIR/nbd"
} }
trap "_cleanup; exit \$status" 0 1 2 3 15 trap "_cleanup; exit \$status" 0 1 2 3 15
@ -135,7 +135,7 @@ echo
run_qemu <<EOF run_qemu <<EOF
{ "execute": "qmp_capabilities" } { "execute": "qmp_capabilities" }
{ "execute": "blockdev-add", "arguments": {"driver": "null-co", "read-zeroes": true, "node-name": "hd0", "read-only": true}} { "execute": "blockdev-add", "arguments": {"driver": "null-co", "read-zeroes": true, "node-name": "hd0", "read-only": true}}
{ "execute": "nbd-server-start", "arguments": {"addr":{"type":"unix","data":{"path":"$TEST_DIR/nbd"}}}} { "execute": "nbd-server-start", "arguments": {"addr":{"type":"unix","data":{"path":"$SOCK_DIR/nbd"}}}}
{ "execute": "nbd-server-add", "arguments": {"device":"hd0"}} { "execute": "nbd-server-add", "arguments": {"device":"hd0"}}
{ "execute": "object-add", "arguments": {"qom-type": "iothread", "id": "iothread0"}} { "execute": "object-add", "arguments": {"qom-type": "iothread", "id": "iothread0"}}
{ "execute": "device_add", "arguments": {"id": "scsi0", "driver": "${virtio_scsi}", "iothread": "iothread0"}} { "execute": "device_add", "arguments": {"id": "scsi0", "driver": "${virtio_scsi}", "iothread": "iothread0"}}

View File

@ -23,8 +23,6 @@ echo "QA output created by $seq"
status=1 # failure is the default! status=1 # failure is the default!
nbd_unix_socket=$TEST_DIR/test_qemu_nbd_socket
_cleanup() _cleanup()
{ {
_cleanup_test_img _cleanup_test_img

View File

@ -598,7 +598,7 @@ class TestBlockdevReopen(iotests.QMPTestCase):
################## ##################
###### null ###### ###### null ######
################## ##################
opts = {'driver': 'null-aio', 'node-name': 'root', 'size': 1024} opts = {'driver': 'null-co', 'node-name': 'root', 'size': 1024}
result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
self.assert_qmp(result, 'return', {}) self.assert_qmp(result, 'return', {})

523
tests/qemu-iotests/261 Executable file
View File

@ -0,0 +1,523 @@
#!/usr/bin/env bash
#
# Test case for qcow2's handling of extra data in snapshot table entries
# (and more generally, how certain cases of broken snapshot tables are
# handled)
#
# Copyright (C) 2019 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=mreitz@redhat.com
seq=$(basename $0)
echo "QA output created by $seq"
status=1 # failure is the default!
_cleanup()
{
_cleanup_test_img
rm -f "$TEST_IMG".v{2,3}.orig
rm -f "$TEST_DIR"/sn{0,1,2}{,-pre,-extra,-post}
}
trap "_cleanup; exit \$status" 0 1 2 3 15
# get standard environment, filters and checks
. ./common.rc
. ./common.filter
# This tests qocw2-specific low-level functionality
_supported_fmt qcow2
_supported_proto file
_supported_os Linux
# (1) We create a v2 image that supports nothing but refcount_bits=16
# (2) We do some refcount management on our own which expects
# refcount_bits=16
_unsupported_imgopts 'refcount_bits=\([^1]\|.\([^6]\|$\)\)'
# Parameters:
# $1: image filename
# $2: snapshot table entry offset in the image
snapshot_table_entry_size()
{
id_len=$(peek_file_be "$1" $(($2 + 12)) 2)
name_len=$(peek_file_be "$1" $(($2 + 14)) 2)
extra_len=$(peek_file_be "$1" $(($2 + 36)) 4)
full_len=$((40 + extra_len + id_len + name_len))
echo $(((full_len + 7) / 8 * 8))
}
# Parameter:
# $1: image filename
print_snapshot_table()
{
nb_entries=$(peek_file_be "$1" 60 4)
offset=$(peek_file_be "$1" 64 8)
echo "Snapshots in $1:" | _filter_testdir | _filter_imgfmt
for ((i = 0; i < nb_entries; i++)); do
id_len=$(peek_file_be "$1" $((offset + 12)) 2)
name_len=$(peek_file_be "$1" $((offset + 14)) 2)
extra_len=$(peek_file_be "$1" $((offset + 36)) 4)
extra_ofs=$((offset + 40))
id_ofs=$((extra_ofs + extra_len))
name_ofs=$((id_ofs + id_len))
echo " [$i]"
echo " ID: $(peek_file_raw "$1" $id_ofs $id_len)"
echo " Name: $(peek_file_raw "$1" $name_ofs $name_len)"
echo " Extra data size: $extra_len"
if [ $extra_len -ge 8 ]; then
echo " VM state size: $(peek_file_be "$1" $extra_ofs 8)"
fi
if [ $extra_len -ge 16 ]; then
echo " Disk size: $(peek_file_be "$1" $((extra_ofs + 8)) 8)"
fi
if [ $extra_len -gt 16 ]; then
echo ' Unknown extra data:' \
"$(peek_file_raw "$1" $((extra_ofs + 16)) $((extra_len - 16)) \
| tr -d '\0')"
fi
offset=$((offset + $(snapshot_table_entry_size "$1" $offset)))
done
}
# Mark clusters as allocated; works only in refblock 0 (i.e. before
# cluster #32768).
# Parameters:
# $1: Start offset of what to allocate
# $2: End offset (exclusive)
refblock0_allocate()
{
reftable_ofs=$(peek_file_be "$TEST_IMG" 48 8)
refblock_ofs=$(peek_file_be "$TEST_IMG" $reftable_ofs 8)
cluster=$(($1 / 65536))
ecluster=$((($2 + 65535) / 65536))
while [ $cluster -lt $ecluster ]; do
if [ $cluster -ge 32768 ]; then
echo "*** Abort: Cluster $cluster exceeds refblock 0 ***"
exit 1
fi
poke_file "$TEST_IMG" $((refblock_ofs + cluster * 2)) '\x00\x01'
cluster=$((cluster + 1))
done
}
echo
echo '=== Create v2 template ==='
echo
# Create v2 image with a snapshot table with three entries:
# [0]: No extra data (valid with v2, not valid with v3)
# [1]: Has extra data unknown to qemu
# [2]: Has the 64-bit VM state size, but not the disk size (again,
# valid with v2, not valid with v3)
TEST_IMG="$TEST_IMG.v2.orig" IMGOPTS='compat=0.10' _make_test_img 64M
$QEMU_IMG snapshot -c sn0 "$TEST_IMG.v2.orig"
$QEMU_IMG snapshot -c sn1 "$TEST_IMG.v2.orig"
$QEMU_IMG snapshot -c sn2 "$TEST_IMG.v2.orig"
# Copy out all existing snapshot table entries
sn_table_ofs=$(peek_file_be "$TEST_IMG.v2.orig" 64 8)
# ofs: Snapshot table entry offset
# eds: Extra data size
# ids: Name + ID size
# len: Total entry length
sn0_ofs=$sn_table_ofs
sn0_eds=$(peek_file_be "$TEST_IMG.v2.orig" $((sn0_ofs + 36)) 4)
sn0_ids=$(($(peek_file_be "$TEST_IMG.v2.orig" $((sn0_ofs + 12)) 2) +
$(peek_file_be "$TEST_IMG.v2.orig" $((sn0_ofs + 14)) 2)))
sn0_len=$(snapshot_table_entry_size "$TEST_IMG.v2.orig" $sn0_ofs)
sn1_ofs=$((sn0_ofs + sn0_len))
sn1_eds=$(peek_file_be "$TEST_IMG.v2.orig" $((sn1_ofs + 36)) 4)
sn1_ids=$(($(peek_file_be "$TEST_IMG.v2.orig" $((sn1_ofs + 12)) 2) +
$(peek_file_be "$TEST_IMG.v2.orig" $((sn1_ofs + 14)) 2)))
sn1_len=$(snapshot_table_entry_size "$TEST_IMG.v2.orig" $sn1_ofs)
sn2_ofs=$((sn1_ofs + sn1_len))
sn2_eds=$(peek_file_be "$TEST_IMG.v2.orig" $((sn2_ofs + 36)) 4)
sn2_ids=$(($(peek_file_be "$TEST_IMG.v2.orig" $((sn2_ofs + 12)) 2) +
$(peek_file_be "$TEST_IMG.v2.orig" $((sn2_ofs + 14)) 2)))
sn2_len=$(snapshot_table_entry_size "$TEST_IMG.v2.orig" $sn2_ofs)
# Data before extra data
dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn0-pre" bs=1 skip=$sn0_ofs count=40 \
&> /dev/null
dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn1-pre" bs=1 skip=$sn1_ofs count=40 \
&> /dev/null
dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn2-pre" bs=1 skip=$sn2_ofs count=40 \
&> /dev/null
# Extra data
dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn0-extra" bs=1 \
skip=$((sn0_ofs + 40)) count=$sn0_eds &> /dev/null
dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn1-extra" bs=1 \
skip=$((sn1_ofs + 40)) count=$sn1_eds &> /dev/null
dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn2-extra" bs=1 \
skip=$((sn2_ofs + 40)) count=$sn2_eds &> /dev/null
# Data after extra data
dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn0-post" bs=1 \
skip=$((sn0_ofs + 40 + sn0_eds)) count=$sn0_ids \
&> /dev/null
dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn1-post" bs=1 \
skip=$((sn1_ofs + 40 + sn1_eds)) count=$sn1_ids \
&> /dev/null
dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn2-post" bs=1 \
skip=$((sn2_ofs + 40 + sn2_eds)) count=$sn2_ids \
&> /dev/null
# Amend them, one by one
# Set sn0's extra data size to 0
poke_file "$TEST_DIR/sn0-pre" 36 '\x00\x00\x00\x00'
truncate -s 0 "$TEST_DIR/sn0-extra"
# Grow sn0-post to pad
truncate -s $(($(snapshot_table_entry_size "$TEST_DIR/sn0-pre") - 40)) \
"$TEST_DIR/sn0-post"
# Set sn1's extra data size to 42
poke_file "$TEST_DIR/sn1-pre" 36 '\x00\x00\x00\x2a'
truncate -s 42 "$TEST_DIR/sn1-extra"
poke_file "$TEST_DIR/sn1-extra" 16 'very important data'
# Grow sn1-post to pad
truncate -s $(($(snapshot_table_entry_size "$TEST_DIR/sn1-pre") - 82)) \
"$TEST_DIR/sn1-post"
# Set sn2's extra data size to 8
poke_file "$TEST_DIR/sn2-pre" 36 '\x00\x00\x00\x08'
truncate -s 8 "$TEST_DIR/sn2-extra"
# Grow sn2-post to pad
truncate -s $(($(snapshot_table_entry_size "$TEST_DIR/sn2-pre") - 48)) \
"$TEST_DIR/sn2-post"
# Construct snapshot table
cat "$TEST_DIR"/sn0-{pre,extra,post} \
"$TEST_DIR"/sn1-{pre,extra,post} \
"$TEST_DIR"/sn2-{pre,extra,post} \
| dd of="$TEST_IMG.v2.orig" bs=1 seek=$sn_table_ofs conv=notrunc \
&> /dev/null
# Done!
TEST_IMG="$TEST_IMG.v2.orig" _check_test_img
print_snapshot_table "$TEST_IMG.v2.orig"
echo
echo '=== Upgrade to v3 ==='
echo
cp "$TEST_IMG.v2.orig" "$TEST_IMG.v3.orig"
$QEMU_IMG amend -o compat=1.1 "$TEST_IMG.v3.orig"
TEST_IMG="$TEST_IMG.v3.orig" _check_test_img
print_snapshot_table "$TEST_IMG.v3.orig"
echo
echo '=== Repair botched v3 ==='
echo
# Force the v2 file to be v3. v3 requires each snapshot table entry
# to have at least 16 bytes of extra data, so it will not comply to
# the qcow2 v3 specification; but we can fix that.
cp "$TEST_IMG.v2.orig" "$TEST_IMG"
# Set version
poke_file "$TEST_IMG" 4 '\x00\x00\x00\x03'
# Increase header length (necessary for v3)
poke_file "$TEST_IMG" 100 '\x00\x00\x00\x68'
# Set refcount order (necessary for v3)
poke_file "$TEST_IMG" 96 '\x00\x00\x00\x04'
_check_test_img -r all
print_snapshot_table "$TEST_IMG"
# From now on, just test the qcow2 version we are supposed to test.
# (v3 by default, v2 by choice through $IMGOPTS.)
# That works because we always write all known extra data when
# updating the snapshot table, independent of the version.
if echo "$IMGOPTS" | grep -q 'compat=\(0\.10\|v2\)' 2> /dev/null; then
subver=v2
else
subver=v3
fi
echo
echo '=== Add new snapshot ==='
echo
cp "$TEST_IMG.$subver.orig" "$TEST_IMG"
$QEMU_IMG snapshot -c sn3 "$TEST_IMG"
_check_test_img
print_snapshot_table "$TEST_IMG"
echo
echo '=== Remove different snapshots ==='
for sn in sn0 sn1 sn2; do
echo
echo "--- $sn ---"
cp "$TEST_IMG.$subver.orig" "$TEST_IMG"
$QEMU_IMG snapshot -d $sn "$TEST_IMG"
_check_test_img
print_snapshot_table "$TEST_IMG"
done
echo
echo '=== Reject too much unknown extra data ==='
echo
cp "$TEST_IMG.$subver.orig" "$TEST_IMG"
$QEMU_IMG snapshot -c sn3 "$TEST_IMG"
sn_table_ofs=$(peek_file_be "$TEST_IMG" 64 8)
sn0_ofs=$sn_table_ofs
sn1_ofs=$((sn0_ofs + $(snapshot_table_entry_size "$TEST_IMG" $sn0_ofs)))
sn2_ofs=$((sn1_ofs + $(snapshot_table_entry_size "$TEST_IMG" $sn1_ofs)))
sn3_ofs=$((sn2_ofs + $(snapshot_table_entry_size "$TEST_IMG" $sn2_ofs)))
# 64 kB of extra data should be rejected
# (Note that this also induces a refcount error, because it spills
# over to the next cluster. That's a good way to test that we can
# handle simultaneous snapshot table and refcount errors.)
poke_file "$TEST_IMG" $((sn3_ofs + 36)) '\x00\x01\x00\x00'
# Print error
_img_info
echo
_check_test_img
echo
# Should be repairable
_check_test_img -r all
echo
echo '=== Snapshot table too big ==='
echo
sn_table_ofs=$(peek_file_be "$TEST_IMG.v3.orig" 64 8)
# Fill a snapshot with 1 kB of extra data, a 65535-char ID, and a
# 65535-char name, and repeat it as many times as necessary to fill
# 64 MB (the maximum supported by qemu)
touch "$TEST_DIR/sn0"
# Full size (fixed + extra + ID + name + padding)
sn_size=$((40 + 1024 + 65535 + 65535 + 2))
# We only need the fixed part, though.
truncate -s 40 "$TEST_DIR/sn0"
# 65535-char ID string
poke_file "$TEST_DIR/sn0" 12 '\xff\xff'
# 65535-char name
poke_file "$TEST_DIR/sn0" 14 '\xff\xff'
# 1 kB of extra data
poke_file "$TEST_DIR/sn0" 36 '\x00\x00\x04\x00'
# Create test image
_make_test_img 64M
# Hook up snapshot table somewhere safe (at 1 MB)
poke_file "$TEST_IMG" 64 '\x00\x00\x00\x00\x00\x10\x00\x00'
offset=1048576
size_written=0
sn_count=0
while [ $size_written -le $((64 * 1048576)) ]; do
dd if="$TEST_DIR/sn0" of="$TEST_IMG" bs=1 seek=$offset conv=notrunc \
&> /dev/null
offset=$((offset + sn_size))
size_written=$((size_written + sn_size))
sn_count=$((sn_count + 1))
done
truncate -s "$offset" "$TEST_IMG"
# Give the last snapshot (the one to be removed) an L1 table so we can
# see how that is handled when repairing the image
# (Put it two clusters before 1 MB, and one L2 table one cluster
# before 1 MB)
poke_file "$TEST_IMG" $((offset - sn_size + 0)) \
'\x00\x00\x00\x00\x00\x0e\x00\x00'
poke_file "$TEST_IMG" $((offset - sn_size + 8)) \
'\x00\x00\x00\x01'
# Hook up the L2 table
poke_file "$TEST_IMG" $((1048576 - 2 * 65536)) \
'\x80\x00\x00\x00\x00\x0f\x00\x00'
# Make sure all of the clusters we just hooked up are allocated:
# - The snapshot table
# - The last snapshot's L1 and L2 table
refblock0_allocate $((1048576 - 2 * 65536)) $offset
poke_file "$TEST_IMG" 60 \
"$(printf '%08x' $sn_count | sed -e 's/\(..\)/\\x\1/g')"
# Print error
_img_info
echo
_check_test_img
echo
# Should be repairable
_check_test_img -r all
echo
echo "$((sn_count - 1)) snapshots should remain:"
echo " qemu-img info reports $(_img_info | grep -c '^ \{34\}') snapshots"
echo " Image header reports $(peek_file_be "$TEST_IMG" 60 4) snapshots"
echo
echo '=== Snapshot table too big with one entry with too much extra data ==='
echo
# For this test, we reuse the image from the previous case, which has
# a snapshot table that is right at the limit.
# Our layout looks like this:
# - (a number of snapshot table entries)
# - One snapshot with $extra_data_size extra data
# - One normal snapshot that breaks the 64 MB boundary
# - One normal snapshot beyond the 64 MB boundary
#
# $extra_data_size is calculated so that simply by virtue of it
# decreasing to 1 kB, the penultimate snapshot will fit into 64 MB
# limit again. The final snapshot will always be beyond the limit, so
# that we can see that the repair algorithm does still determine the
# limit to be somewhere, even when truncating one snapshot's extra
# data.
# The last case has removed the last snapshot, so calculate
# $old_offset to get the current image's real length
old_offset=$((offset - sn_size))
# The layout from the previous test had one snapshot beyond the 64 MB
# limit; we want the same (after the oversized extra data has been
# truncated to 1 kB), so we drop the last three snapshots and
# construct them from scratch.
offset=$((offset - 3 * sn_size))
sn_count=$((sn_count - 3))
# Assuming we had already written one of the three snapshots
# (necessary so we can calculate $extra_data_size next).
size_written=$((size_written - 2 * sn_size))
# Increase the extra data size so we go past the limit
# (The -1024 comes from the 1 kB of extra data we already have)
extra_data_size=$((64 * 1048576 + 8 - sn_size - (size_written - 1024)))
poke_file "$TEST_IMG" $((offset + 36)) \
"$(printf '%08x' $extra_data_size | sed -e 's/\(..\)/\\x\1/g')"
offset=$((offset + sn_size - 1024 + extra_data_size))
size_written=$((size_written - 1024 + extra_data_size))
sn_count=$((sn_count + 1))
# Write the two normal snapshots
for ((i = 0; i < 2; i++)); do
dd if="$TEST_DIR/sn0" of="$TEST_IMG" bs=1 seek=$offset conv=notrunc \
&> /dev/null
offset=$((offset + sn_size))
size_written=$((size_written + sn_size))
sn_count=$((sn_count + 1))
if [ $i = 0 ]; then
# Check that the penultimate snapshot is beyond the 64 MB limit
echo "Snapshot table size should equal $((64 * 1048576 + 8)):" \
$size_written
echo
fi
done
truncate -s $offset "$TEST_IMG"
refblock0_allocate $old_offset $offset
poke_file "$TEST_IMG" 60 \
"$(printf '%08x' $sn_count | sed -e 's/\(..\)/\\x\1/g')"
# Print error
_img_info
echo
_check_test_img
echo
# Just truncating the extra data should be sufficient to shorten the
# snapshot table so only one snapshot exceeds the extra size
_check_test_img -r all
echo
echo '=== Too many snapshots ==='
echo
# Create a v2 image, for speeds' sake: All-zero snapshot table entries
# are only valid in v2.
IMGOPTS='compat=0.10' _make_test_img 64M
# Hook up snapshot table somewhere safe (at 1 MB)
poke_file "$TEST_IMG" 64 '\x00\x00\x00\x00\x00\x10\x00\x00'
# "Create" more than 65536 snapshots (twice that many here)
poke_file "$TEST_IMG" 60 '\x00\x02\x00\x00'
# 40-byte all-zero snapshot table entries are valid snapshots, but
# only in v2 (v3 needs 16 bytes of extra data, so we would have to
# write 131072x '\x10').
truncate -s $((1048576 + 40 * 131072)) "$TEST_IMG"
# But let us give one of the snapshots to be removed an L1 table so
# we can see how that is handled when repairing the image.
# (Put it two clusters before 1 MB, and one L2 table one cluster
# before 1 MB)
poke_file "$TEST_IMG" $((1048576 + 40 * 65536 + 0)) \
'\x00\x00\x00\x00\x00\x0e\x00\x00'
poke_file "$TEST_IMG" $((1048576 + 40 * 65536 + 8)) \
'\x00\x00\x00\x01'
# Hook up the L2 table
poke_file "$TEST_IMG" $((1048576 - 2 * 65536)) \
'\x80\x00\x00\x00\x00\x0f\x00\x00'
# Make sure all of the clusters we just hooked up are allocated:
# - The snapshot table
# - The last snapshot's L1 and L2 table
refblock0_allocate $((1048576 - 2 * 65536)) $((1048576 + 40 * 131072))
# Print error
_img_info
echo
_check_test_img
echo
# Should be repairable
_check_test_img -r all
echo
echo '65536 snapshots should remain:'
echo " qemu-img info reports $(_img_info | grep -c '^ \{34\}') snapshots"
echo " Image header reports $(peek_file_be "$TEST_IMG" 60 4) snapshots"
# success, all done
echo "*** done"
status=0

346
tests/qemu-iotests/261.out Normal file
View File

@ -0,0 +1,346 @@
QA output created by 261
=== Create v2 template ===
Formatting 'TEST_DIR/t.IMGFMT.v2.orig', fmt=IMGFMT size=67108864
No errors were found on the image.
Snapshots in TEST_DIR/t.IMGFMT.v2.orig:
[0]
ID: 1
Name: sn0
Extra data size: 0
[1]
ID: 2
Name: sn1
Extra data size: 42
VM state size: 0
Disk size: 67108864
Unknown extra data: very important data
[2]
ID: 3
Name: sn2
Extra data size: 8
VM state size: 0
=== Upgrade to v3 ===
No errors were found on the image.
Snapshots in TEST_DIR/t.IMGFMT.v3.orig:
[0]
ID: 1
Name: sn0
Extra data size: 16
VM state size: 0
Disk size: 67108864
[1]
ID: 2
Name: sn1
Extra data size: 42
VM state size: 0
Disk size: 67108864
Unknown extra data: very important data
[2]
ID: 3
Name: sn2
Extra data size: 16
VM state size: 0
Disk size: 67108864
=== Repair botched v3 ===
Repairing snapshot table entry 0 is incomplete
Repairing snapshot table entry 2 is incomplete
The following inconsistencies were found and repaired:
0 leaked clusters
2 corruptions
Double checking the fixed image now...
No errors were found on the image.
Snapshots in TEST_DIR/t.IMGFMT:
[0]
ID: 1
Name: sn0
Extra data size: 16
VM state size: 0
Disk size: 67108864
[1]
ID: 2
Name: sn1
Extra data size: 42
VM state size: 0
Disk size: 67108864
Unknown extra data: very important data
[2]
ID: 3
Name: sn2
Extra data size: 16
VM state size: 0
Disk size: 67108864
=== Add new snapshot ===
No errors were found on the image.
Snapshots in TEST_DIR/t.IMGFMT:
[0]
ID: 1
Name: sn0
Extra data size: 16
VM state size: 0
Disk size: 67108864
[1]
ID: 2
Name: sn1
Extra data size: 42
VM state size: 0
Disk size: 67108864
Unknown extra data: very important data
[2]
ID: 3
Name: sn2
Extra data size: 16
VM state size: 0
Disk size: 67108864
[3]
ID: 4
Name: sn3
Extra data size: 16
VM state size: 0
Disk size: 67108864
=== Remove different snapshots ===
--- sn0 ---
No errors were found on the image.
Snapshots in TEST_DIR/t.IMGFMT:
[0]
ID: 2
Name: sn1
Extra data size: 42
VM state size: 0
Disk size: 67108864
Unknown extra data: very important data
[1]
ID: 3
Name: sn2
Extra data size: 16
VM state size: 0
Disk size: 67108864
--- sn1 ---
No errors were found on the image.
Snapshots in TEST_DIR/t.IMGFMT:
[0]
ID: 1
Name: sn0
Extra data size: 16
VM state size: 0
Disk size: 67108864
[1]
ID: 3
Name: sn2
Extra data size: 16
VM state size: 0
Disk size: 67108864
--- sn2 ---
No errors were found on the image.
Snapshots in TEST_DIR/t.IMGFMT:
[0]
ID: 1
Name: sn0
Extra data size: 16
VM state size: 0
Disk size: 67108864
[1]
ID: 2
Name: sn1
Extra data size: 42
VM state size: 0
Disk size: 67108864
Unknown extra data: very important data
=== Reject too much unknown extra data ===
qemu-img: Could not open 'TEST_DIR/t.IMGFMT': Too much extra metadata in snapshot table entry 3
You can force-remove this extra metadata with qemu-img check -r all
qemu-img: ERROR failed to read the snapshot table: Too much extra metadata in snapshot table entry 3
You can force-remove this extra metadata with qemu-img check -r all
qemu-img: Check failed: File too large
Discarding too much extra metadata in snapshot table entry 3 (65536 > 1024)
ERROR cluster 10 refcount=0 reference=1
Rebuilding refcount structure
Repairing cluster 1 refcount=1 reference=0
Repairing cluster 2 refcount=1 reference=0
The following inconsistencies were found and repaired:
0 leaked clusters
2 corruptions
Double checking the fixed image now...
No errors were found on the image.
=== Snapshot table too big ===
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
qemu-img: Could not open 'TEST_DIR/t.IMGFMT': Snapshot table is too big
You can force-remove all 1 overhanging snapshots with qemu-img check -r all
qemu-img: ERROR failed to read the snapshot table: Snapshot table is too big
You can force-remove all 1 overhanging snapshots with qemu-img check -r all
qemu-img: Check failed: File too large
Discarding 1 overhanging snapshots (snapshot table is too big)
Leaked cluster 14 refcount=1 reference=0
Leaked cluster 15 refcount=1 reference=0
Leaked cluster 1039 refcount=1 reference=0
Leaked cluster 1040 refcount=1 reference=0
Repairing cluster 14 refcount=1 reference=0
Repairing cluster 15 refcount=1 reference=0
Repairing cluster 1039 refcount=1 reference=0
Repairing cluster 1040 refcount=1 reference=0
The following inconsistencies were found and repaired:
4 leaked clusters
1 corruptions
Double checking the fixed image now...
No errors were found on the image.
507 snapshots should remain:
qemu-img info reports 507 snapshots
Image header reports 507 snapshots
=== Snapshot table too big with one entry with too much extra data ===
Snapshot table size should equal 67108872: 67108872
qemu-img: Could not open 'TEST_DIR/t.IMGFMT': Too much extra metadata in snapshot table entry 505
You can force-remove this extra metadata with qemu-img check -r all
qemu-img: ERROR failed to read the snapshot table: Too much extra metadata in snapshot table entry 505
You can force-remove this extra metadata with qemu-img check -r all
qemu-img: Check failed: File too large
Discarding too much extra metadata in snapshot table entry 505 (116944 > 1024)
Discarding 1 overhanging snapshots (snapshot table is too big)
Leaked cluster 1041 refcount=1 reference=0
Leaked cluster 1042 refcount=1 reference=0
Repairing cluster 1041 refcount=1 reference=0
Repairing cluster 1042 refcount=1 reference=0
The following inconsistencies were found and repaired:
2 leaked clusters
2 corruptions
Double checking the fixed image now...
No errors were found on the image.
=== Too many snapshots ===
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
qemu-img: Could not open 'TEST_DIR/t.IMGFMT': Snapshot table too large
qemu-img: ERROR snapshot table too large
You can force-remove all 65536 overhanging snapshots with qemu-img check -r all
qemu-img: Check failed: File too large
Discarding 65536 overhanging snapshots
Leaked cluster 14 refcount=1 reference=0
Leaked cluster 15 refcount=1 reference=0
Leaked cluster 56 refcount=1 reference=0
Leaked cluster 57 refcount=1 reference=0
Leaked cluster 58 refcount=1 reference=0
Leaked cluster 59 refcount=1 reference=0
Leaked cluster 60 refcount=1 reference=0
Leaked cluster 61 refcount=1 reference=0
Leaked cluster 62 refcount=1 reference=0
Leaked cluster 63 refcount=1 reference=0
Leaked cluster 64 refcount=1 reference=0
Leaked cluster 65 refcount=1 reference=0
Leaked cluster 66 refcount=1 reference=0
Leaked cluster 67 refcount=1 reference=0
Leaked cluster 68 refcount=1 reference=0
Leaked cluster 69 refcount=1 reference=0
Leaked cluster 70 refcount=1 reference=0
Leaked cluster 71 refcount=1 reference=0
Leaked cluster 72 refcount=1 reference=0
Leaked cluster 73 refcount=1 reference=0
Leaked cluster 74 refcount=1 reference=0
Leaked cluster 75 refcount=1 reference=0
Leaked cluster 76 refcount=1 reference=0
Leaked cluster 77 refcount=1 reference=0
Leaked cluster 78 refcount=1 reference=0
Leaked cluster 79 refcount=1 reference=0
Leaked cluster 80 refcount=1 reference=0
Leaked cluster 81 refcount=1 reference=0
Leaked cluster 82 refcount=1 reference=0
Leaked cluster 83 refcount=1 reference=0
Leaked cluster 84 refcount=1 reference=0
Leaked cluster 85 refcount=1 reference=0
Leaked cluster 86 refcount=1 reference=0
Leaked cluster 87 refcount=1 reference=0
Leaked cluster 88 refcount=1 reference=0
Leaked cluster 89 refcount=1 reference=0
Leaked cluster 90 refcount=1 reference=0
Leaked cluster 91 refcount=1 reference=0
Leaked cluster 92 refcount=1 reference=0
Leaked cluster 93 refcount=1 reference=0
Leaked cluster 94 refcount=1 reference=0
Leaked cluster 95 refcount=1 reference=0
Repairing cluster 14 refcount=1 reference=0
Repairing cluster 15 refcount=1 reference=0
Repairing cluster 56 refcount=1 reference=0
Repairing cluster 57 refcount=1 reference=0
Repairing cluster 58 refcount=1 reference=0
Repairing cluster 59 refcount=1 reference=0
Repairing cluster 60 refcount=1 reference=0
Repairing cluster 61 refcount=1 reference=0
Repairing cluster 62 refcount=1 reference=0
Repairing cluster 63 refcount=1 reference=0
Repairing cluster 64 refcount=1 reference=0
Repairing cluster 65 refcount=1 reference=0
Repairing cluster 66 refcount=1 reference=0
Repairing cluster 67 refcount=1 reference=0
Repairing cluster 68 refcount=1 reference=0
Repairing cluster 69 refcount=1 reference=0
Repairing cluster 70 refcount=1 reference=0
Repairing cluster 71 refcount=1 reference=0
Repairing cluster 72 refcount=1 reference=0
Repairing cluster 73 refcount=1 reference=0
Repairing cluster 74 refcount=1 reference=0
Repairing cluster 75 refcount=1 reference=0
Repairing cluster 76 refcount=1 reference=0
Repairing cluster 77 refcount=1 reference=0
Repairing cluster 78 refcount=1 reference=0
Repairing cluster 79 refcount=1 reference=0
Repairing cluster 80 refcount=1 reference=0
Repairing cluster 81 refcount=1 reference=0
Repairing cluster 82 refcount=1 reference=0
Repairing cluster 83 refcount=1 reference=0
Repairing cluster 84 refcount=1 reference=0
Repairing cluster 85 refcount=1 reference=0
Repairing cluster 86 refcount=1 reference=0
Repairing cluster 87 refcount=1 reference=0
Repairing cluster 88 refcount=1 reference=0
Repairing cluster 89 refcount=1 reference=0
Repairing cluster 90 refcount=1 reference=0
Repairing cluster 91 refcount=1 reference=0
Repairing cluster 92 refcount=1 reference=0
Repairing cluster 93 refcount=1 reference=0
Repairing cluster 94 refcount=1 reference=0
Repairing cluster 95 refcount=1 reference=0
The following inconsistencies were found and repaired:
42 leaked clusters
65536 corruptions
Double checking the fixed image now...
No errors were found on the image.
65536 snapshots should remain:
qemu-img info reports 65536 snapshots
Image header reports 65536 snapshots
*** done

View File

@ -24,6 +24,8 @@ import iotests
from iotests import qemu_img_create, qemu_io_silent_check, file_path, \ from iotests import qemu_img_create, qemu_io_silent_check, file_path, \
qemu_nbd_popen, log qemu_nbd_popen, log
iotests.verify_image_format(supported_fmts=['qcow2'])
disk_a, disk_b, nbd_sock = file_path('disk_a', 'disk_b', 'nbd-sock') disk_a, disk_b, nbd_sock = file_path('disk_a', 'disk_b', 'nbd-sock')
nbd_uri = 'nbd+unix:///?socket=' + nbd_sock nbd_uri = 'nbd+unix:///?socket=' + nbd_sock
size = 5 * 1024 * 1024 size = 5 * 1024 * 1024

View File

@ -29,7 +29,7 @@ status=1 # failure is the default!
_cleanup() _cleanup()
{ {
_cleanup_test_img _cleanup_test_img
rm -f "$TEST_DIR/nbd" rm -f "$SOCK_DIR/nbd"
} }
trap "_cleanup; exit \$status" 0 1 2 3 15 trap "_cleanup; exit \$status" 0 1 2 3 15
@ -143,7 +143,7 @@ echo
IMGOPTS="backing_file=$TEST_IMG.base" _make_test_img $size IMGOPTS="backing_file=$TEST_IMG.base" _make_test_img $size
cat <<EOF | cat <<EOF |
nbd_server_start unix:$TEST_DIR/nbd nbd_server_start unix:$SOCK_DIR/nbd
nbd_server_add -w backing-fmt nbd_server_add -w backing-fmt
savevm snap0 savevm snap0
info snapshots info snapshots

View File

@ -161,7 +161,7 @@ Internal snapshots on backing file:
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/t.IMGFMT.base Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/t.IMGFMT.base
Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT.base,node-name=backing-file -blockdev driver=IMGFMT,file=backing-file,node-name=backing-fmt -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=file -blockdev driver=IMGFMT,file=file,backing=backing-fmt,node-name=fmt Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT.base,node-name=backing-file -blockdev driver=IMGFMT,file=backing-file,node-name=backing-fmt -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=file -blockdev driver=IMGFMT,file=file,backing=backing-fmt,node-name=fmt
QEMU X.Y.Z monitor - type 'help' for more information QEMU X.Y.Z monitor - type 'help' for more information
(qemu) nbd_server_start unix:TEST_DIR/nbd (qemu) nbd_server_start unix:SOCK_DIR/nbd
(qemu) nbd_server_add -w backing-fmt (qemu) nbd_server_add -w backing-fmt
(qemu) savevm snap0 (qemu) savevm snap0
(qemu) info snapshots (qemu) info snapshots

View File

@ -97,6 +97,7 @@ IMGFMT -- $FULL_IMGFMT_DETAILS
IMGPROTO -- $IMGPROTO IMGPROTO -- $IMGPROTO
PLATFORM -- $FULL_HOST_DETAILS PLATFORM -- $FULL_HOST_DETAILS
TEST_DIR -- $TEST_DIR TEST_DIR -- $TEST_DIR
SOCK_DIR -- $SOCK_DIR
SOCKET_SCM_HELPER -- $SOCKET_SCM_HELPER SOCKET_SCM_HELPER -- $SOCKET_SCM_HELPER
EOF EOF
@ -116,10 +117,14 @@ set_prog_path()
if [ -z "$TEST_DIR" ]; then if [ -z "$TEST_DIR" ]; then
TEST_DIR=$PWD/scratch TEST_DIR=$PWD/scratch
fi fi
mkdir -p "$TEST_DIR" || _init_error 'Failed to create TEST_DIR'
if [ ! -e "$TEST_DIR" ]; then tmp_sock_dir=false
mkdir "$TEST_DIR" if [ -z "$SOCK_DIR" ]; then
SOCK_DIR=$(mktemp -d)
tmp_sock_dir=true
fi fi
mkdir -p "$SOCK_DIR" || _init_error 'Failed to create SOCK_DIR'
diff="diff -u" diff="diff -u"
verbose=false verbose=false
@ -534,6 +539,7 @@ if [ -z "$SAMPLE_IMG_DIR" ]; then
fi fi
export TEST_DIR export TEST_DIR
export SOCK_DIR
export SAMPLE_IMG_DIR export SAMPLE_IMG_DIR
if [ -s $tmp.list ] if [ -s $tmp.list ]
@ -716,6 +722,11 @@ END { if (NR > 0) {
rm -f "${TEST_DIR}"/*.out "${TEST_DIR}"/*.err "${TEST_DIR}"/*.time rm -f "${TEST_DIR}"/*.out "${TEST_DIR}"/*.err "${TEST_DIR}"/*.time
rm -f "${TEST_DIR}"/check.pid "${TEST_DIR}"/check.sts rm -f "${TEST_DIR}"/check.pid "${TEST_DIR}"/check.sts
rm -f $tmp.* rm -f $tmp.*
if $tmp_sock_dir
then
rm -rf "$SOCK_DIR"
fi
} }
trap "_wrapup; exit \$status" 0 1 2 3 15 trap "_wrapup; exit \$status" 0 1 2 3 15

View File

@ -43,7 +43,8 @@ _filter_qom_path()
# replace occurrences of the actual TEST_DIR value with TEST_DIR # replace occurrences of the actual TEST_DIR value with TEST_DIR
_filter_testdir() _filter_testdir()
{ {
$SED -e "s#$TEST_DIR/#TEST_DIR/#g" $SED -e "s#$TEST_DIR/#TEST_DIR/#g" \
-e "s#$SOCK_DIR/#SOCK_DIR/#g"
} }
# replace occurrences of the actual IMGFMT value with IMGFMT # replace occurrences of the actual IMGFMT value with IMGFMT
@ -124,6 +125,7 @@ _filter_img_create()
$SED -e "s#$REMOTE_TEST_DIR#TEST_DIR#g" \ $SED -e "s#$REMOTE_TEST_DIR#TEST_DIR#g" \
-e "s#$IMGPROTO:$TEST_DIR#TEST_DIR#g" \ -e "s#$IMGPROTO:$TEST_DIR#TEST_DIR#g" \
-e "s#$TEST_DIR#TEST_DIR#g" \ -e "s#$TEST_DIR#TEST_DIR#g" \
-e "s#$SOCK_DIR#SOCK_DIR#g" \
-e "s#$IMGFMT#IMGFMT#g" \ -e "s#$IMGFMT#IMGFMT#g" \
-e 's#nbd:127.0.0.1:10810#TEST_DIR/t.IMGFMT#g' \ -e 's#nbd:127.0.0.1:10810#TEST_DIR/t.IMGFMT#g' \
-e "s# encryption=off##g" \ -e "s# encryption=off##g" \
@ -160,6 +162,7 @@ _filter_img_info()
$SED -e "s#$REMOTE_TEST_DIR#TEST_DIR#g" \ $SED -e "s#$REMOTE_TEST_DIR#TEST_DIR#g" \
-e "s#$IMGPROTO:$TEST_DIR#TEST_DIR#g" \ -e "s#$IMGPROTO:$TEST_DIR#TEST_DIR#g" \
-e "s#$TEST_DIR#TEST_DIR#g" \ -e "s#$TEST_DIR#TEST_DIR#g" \
-e "s#$SOCK_DIR#SOCK_DIR#g" \
-e "s#$IMGFMT#IMGFMT#g" \ -e "s#$IMGFMT#IMGFMT#g" \
-e 's#nbd://127.0.0.1:10810$#TEST_DIR/t.IMGFMT#g' \ -e 's#nbd://127.0.0.1:10810$#TEST_DIR/t.IMGFMT#g' \
-e 's#json.*vdisk-id.*vxhs"}}#TEST_DIR/t.IMGFMT#' \ -e 's#json.*vdisk-id.*vxhs"}}#TEST_DIR/t.IMGFMT#' \
@ -218,7 +221,7 @@ _filter_nbd()
# Filter out the TCP port number since this changes between runs. # Filter out the TCP port number since this changes between runs.
$SED -e '/nbd\/.*\.c:/d' \ $SED -e '/nbd\/.*\.c:/d' \
-e 's#127\.0\.0\.1:[0-9]*#127.0.0.1:PORT#g' \ -e 's#127\.0\.0\.1:[0-9]*#127.0.0.1:PORT#g' \
-e "s#?socket=$TEST_DIR#?socket=TEST_DIR#g" \ -e "s#?socket=$SOCK_DIR#?socket=SOCK_DIR#g" \
-e 's#\(foo\|PORT/\?\|.sock\): Failed to .*$#\1#' -e 's#\(foo\|PORT/\?\|.sock\): Failed to .*$#\1#'
} }

View File

@ -19,7 +19,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
nbd_unix_socket="${TEST_DIR}/qemu-nbd.sock" nbd_unix_socket="${SOCK_DIR}/qemu-nbd.sock"
nbd_tcp_addr="127.0.0.1" nbd_tcp_addr="127.0.0.1"
nbd_pid_file="${TEST_DIR}/qemu-nbd.pid" nbd_pid_file="${TEST_DIR}/qemu-nbd.pid"
nbd_stderr_fifo="${TEST_DIR}/qemu-nbd.fifo" nbd_stderr_fifo="${TEST_DIR}/qemu-nbd.fifo"

View File

@ -53,6 +53,26 @@ poke_file()
printf "$3" | dd "of=$1" bs=1 "seek=$2" conv=notrunc &>/dev/null printf "$3" | dd "of=$1" bs=1 "seek=$2" conv=notrunc &>/dev/null
} }
# peek_file_le 'test.img' 512 2 => 65534
peek_file_le()
{
# Wrap in echo $() to strip spaces
echo $(od -j"$2" -N"$3" --endian=little -An -vtu"$3" "$1")
}
# peek_file_be 'test.img' 512 2 => 65279
peek_file_be()
{
# Wrap in echo $() to strip spaces
echo $(od -j"$2" -N"$3" --endian=big -An -vtu"$3" "$1")
}
# peek_file_raw 'test.img' 512 2 => '\xff\xfe'
peek_file_raw()
{
dd if="$1" bs=1 skip="$2" count="$3" status=none
}
if ! . ./common.config if ! . ./common.config
then then

View File

@ -274,6 +274,7 @@
257 rw 257 rw
258 rw quick 258 rw quick
260 rw quick 260 rw quick
261 rw
262 rw quick migration 262 rw quick migration
263 rw quick 263 rw quick
264 rw 264 rw

View File

@ -57,6 +57,7 @@ qemu_opts = os.environ.get('QEMU_OPTIONS', '').strip().split(' ')
imgfmt = os.environ.get('IMGFMT', 'raw') imgfmt = os.environ.get('IMGFMT', 'raw')
imgproto = os.environ.get('IMGPROTO', 'file') imgproto = os.environ.get('IMGPROTO', 'file')
test_dir = os.environ.get('TEST_DIR') test_dir = os.environ.get('TEST_DIR')
sock_dir = os.environ.get('SOCK_DIR')
output_dir = os.environ.get('OUTPUT_DIR', '.') output_dir = os.environ.get('OUTPUT_DIR', '.')
cachemode = os.environ.get('CACHEMODE') cachemode = os.environ.get('CACHEMODE')
qemu_default_machine = os.environ.get('QEMU_DEFAULT_MACHINE') qemu_default_machine = os.environ.get('QEMU_DEFAULT_MACHINE')
@ -385,10 +386,10 @@ class FilePaths(object):
qemu_img('create', img_path, '1G') qemu_img('create', img_path, '1G')
# migration_sock_path is automatically deleted # migration_sock_path is automatically deleted
""" """
def __init__(self, names): def __init__(self, names, base_dir=test_dir):
self.paths = [] self.paths = []
for name in names: for name in names:
self.paths.append(os.path.join(test_dir, file_pattern(name))) self.paths.append(os.path.join(base_dir, file_pattern(name)))
def __enter__(self): def __enter__(self):
return self.paths return self.paths
@ -405,8 +406,8 @@ class FilePath(FilePaths):
""" """
FilePath is a specialization of FilePaths that takes a single filename. FilePath is a specialization of FilePaths that takes a single filename.
""" """
def __init__(self, name): def __init__(self, name, base_dir=test_dir):
super(FilePath, self).__init__([name]) super(FilePath, self).__init__([name], base_dir)
def __enter__(self): def __enter__(self):
return self.paths[0] return self.paths[0]
@ -419,7 +420,7 @@ def file_path_remover():
pass pass
def file_path(*names): def file_path(*names, base_dir=test_dir):
''' Another way to get auto-generated filename that cleans itself up. ''' Another way to get auto-generated filename that cleans itself up.
Use is as simple as: Use is as simple as:
@ -435,7 +436,7 @@ def file_path(*names):
paths = [] paths = []
for name in names: for name in names:
filename = file_pattern(name) filename = file_pattern(name)
path = os.path.join(test_dir, filename) path = os.path.join(base_dir, filename)
file_path_remover.paths.append(path) file_path_remover.paths.append(path)
paths.append(path) paths.append(path)
@ -456,7 +457,8 @@ class VM(qtest.QEMUQtestMachine):
name = "qemu%s-%d" % (path_suffix, os.getpid()) name = "qemu%s-%d" % (path_suffix, os.getpid())
super(VM, self).__init__(qemu_prog, qemu_opts, name=name, super(VM, self).__init__(qemu_prog, qemu_opts, name=name,
test_dir=test_dir, test_dir=test_dir,
socket_scm_helper=socket_scm_helper) socket_scm_helper=socket_scm_helper,
sock_dir=sock_dir)
self._num_drives = 0 self._num_drives = 0
def add_object(self, opts): def add_object(self, opts):
@ -838,6 +840,11 @@ class QMPTestCase(unittest.TestCase):
return self.pause_wait(job_id) return self.pause_wait(job_id)
return result return result
def case_skip(self, reason):
'''Skip this test case'''
case_notrun(reason)
self.skipTest(reason)
def notrun(reason): def notrun(reason):
'''Skip this test suite''' '''Skip this test suite'''
@ -849,7 +856,11 @@ def notrun(reason):
sys.exit(0) sys.exit(0)
def case_notrun(reason): def case_notrun(reason):
'''Skip this test case''' '''Mark this test case as not having been run (without actually
skipping it, that is left to the caller). See
QMPTestCase.case_skip() for a variant that actually skips the
current test case.'''
# Each test in qemu-iotests has a number ("seq") # Each test in qemu-iotests has a number ("seq")
seq = os.path.basename(sys.argv[0]) seq = os.path.basename(sys.argv[0])
@ -912,22 +923,34 @@ def qemu_pipe(*args):
def supported_formats(read_only=False): def supported_formats(read_only=False):
'''Set 'read_only' to True to check ro-whitelist '''Set 'read_only' to True to check ro-whitelist
Otherwise, rw-whitelist is checked''' Otherwise, rw-whitelist is checked'''
if not hasattr(supported_formats, "formats"):
supported_formats.formats = {}
if read_only not in supported_formats.formats:
format_message = qemu_pipe("-drive", "format=help") format_message = qemu_pipe("-drive", "format=help")
line = 1 if read_only else 0 line = 1 if read_only else 0
return format_message.splitlines()[line].split(":")[1].split() supported_formats.formats[read_only] = \
format_message.splitlines()[line].split(":")[1].split()
return supported_formats.formats[read_only]
def skip_if_unsupported(required_formats=[], read_only=False): def skip_if_unsupported(required_formats=[], read_only=False):
'''Skip Test Decorator '''Skip Test Decorator
Runs the test if all the required formats are whitelisted''' Runs the test if all the required formats are whitelisted'''
def skip_test_decorator(func): def skip_test_decorator(func):
def func_wrapper(*args, **kwargs): def func_wrapper(test_case: QMPTestCase, *args, **kwargs):
usf_list = list(set(required_formats) - if callable(required_formats):
set(supported_formats(read_only))) fmts = required_formats(test_case)
if usf_list:
case_notrun('{}: formats {} are not whitelisted'.format(
args[0], usf_list))
else: else:
return func(*args, **kwargs) fmts = required_formats
usf_list = list(set(fmts) - set(supported_formats(read_only)))
if usf_list:
test_case.case_skip('{}: formats {} are not whitelisted'.format(
test_case, usf_list))
else:
return func(test_case, *args, **kwargs)
return func_wrapper return func_wrapper
return skip_test_decorator return skip_test_decorator
@ -950,8 +973,15 @@ def execute_unittest(output, verbosity, debug):
unittest.main(testRunner=runner) unittest.main(testRunner=runner)
finally: finally:
if not debug: if not debug:
sys.stderr.write(re.sub(r'Ran (\d+) tests? in [\d.]+s', out = output.getvalue()
r'Ran \1 tests', output.getvalue())) out = re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', out)
# Hide skipped tests from the reference output
out = re.sub(r'OK \(skipped=\d+\)', 'OK', out)
out_first_line, out_rest = out.split('\n', 1)
out = out_first_line.replace('s', '.') + '\n' + out_rest
sys.stderr.write(out)
def execute_test(test_function=None, def execute_test(test_function=None,
supported_fmts=[], supported_oses=['linux'], supported_fmts=[], supported_oses=['linux'],

View File

@ -45,7 +45,7 @@ static int coroutine_fn bdrv_test_co_pdiscard(BlockDriverState *bs,
} }
static int coroutine_fn static int coroutine_fn
bdrv_test_co_truncate(BlockDriverState *bs, int64_t offset, bdrv_test_co_truncate(BlockDriverState *bs, int64_t offset, bool exact,
PreallocMode prealloc, Error **errp) PreallocMode prealloc, Error **errp)
{ {
return 0; return 0;
@ -185,18 +185,18 @@ static void test_sync_op_truncate(BdrvChild *c)
int ret; int ret;
/* Normal success path */ /* Normal success path */
ret = bdrv_truncate(c, 65536, PREALLOC_MODE_OFF, NULL); ret = bdrv_truncate(c, 65536, false, PREALLOC_MODE_OFF, NULL);
g_assert_cmpint(ret, ==, 0); g_assert_cmpint(ret, ==, 0);
/* Early error: Negative offset */ /* Early error: Negative offset */
ret = bdrv_truncate(c, -2, PREALLOC_MODE_OFF, NULL); ret = bdrv_truncate(c, -2, false, PREALLOC_MODE_OFF, NULL);
g_assert_cmpint(ret, ==, -EINVAL); g_assert_cmpint(ret, ==, -EINVAL);
/* Error: Read-only image */ /* Error: Read-only image */
c->bs->read_only = true; c->bs->read_only = true;
c->bs->open_flags &= ~BDRV_O_RDWR; c->bs->open_flags &= ~BDRV_O_RDWR;
ret = bdrv_truncate(c, 65536, PREALLOC_MODE_OFF, NULL); ret = bdrv_truncate(c, 65536, false, PREALLOC_MODE_OFF, NULL);
g_assert_cmpint(ret, ==, -EACCES); g_assert_cmpint(ret, ==, -EACCES);
c->bs->read_only = false; c->bs->read_only = false;

View File

@ -37,6 +37,7 @@ util-obj-y += rcu.o
util-obj-$(CONFIG_MEMBARRIER) += sys_membarrier.o util-obj-$(CONFIG_MEMBARRIER) += sys_membarrier.o
util-obj-y += qemu-coroutine.o qemu-coroutine-lock.o qemu-coroutine-io.o util-obj-y += qemu-coroutine.o qemu-coroutine-lock.o qemu-coroutine-io.o
util-obj-y += qemu-coroutine-sleep.o util-obj-y += qemu-coroutine-sleep.o
util-obj-y += qemu-co-shared-resource.o
util-obj-y += coroutine-$(CONFIG_COROUTINE_BACKEND).o util-obj-y += coroutine-$(CONFIG_COROUTINE_BACKEND).o
util-obj-y += buffer.o util-obj-y += buffer.o
util-obj-y += timed-average.o util-obj-y += timed-average.o

View File

@ -387,6 +387,10 @@ void hbitmap_set(HBitmap *hb, uint64_t start, uint64_t count)
uint64_t first, n; uint64_t first, n;
uint64_t last = start + count - 1; uint64_t last = start + count - 1;
if (count == 0) {
return;
}
trace_hbitmap_set(hb, start, count, trace_hbitmap_set(hb, start, count,
start >> hb->granularity, last >> hb->granularity); start >> hb->granularity, last >> hb->granularity);
@ -478,6 +482,10 @@ void hbitmap_reset(HBitmap *hb, uint64_t start, uint64_t count)
uint64_t last = start + count - 1; uint64_t last = start + count - 1;
uint64_t gran = 1ULL << hb->granularity; uint64_t gran = 1ULL << hb->granularity;
if (count == 0) {
return;
}
assert(QEMU_IS_ALIGNED(start, gran)); assert(QEMU_IS_ALIGNED(start, gran));
assert(QEMU_IS_ALIGNED(count, gran) || (start + count == hb->orig_size)); assert(QEMU_IS_ALIGNED(count, gran) || (start + count == hb->orig_size));

View File

@ -0,0 +1,76 @@
/*
* Helper functionality for distributing a fixed total amount of
* an abstract resource among multiple coroutines.
*
* Copyright (c) 2019 Virtuozzo International GmbH
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include "qemu/coroutine.h"
#include "qemu/co-shared-resource.h"
struct SharedResource {
uint64_t total;
uint64_t available;
CoQueue queue;
};
SharedResource *shres_create(uint64_t total)
{
SharedResource *s = g_new0(SharedResource, 1);
s->total = s->available = total;
qemu_co_queue_init(&s->queue);
return s;
}
void shres_destroy(SharedResource *s)
{
assert(s->available == s->total);
g_free(s);
}
bool co_try_get_from_shres(SharedResource *s, uint64_t n)
{
if (s->available >= n) {
s->available -= n;
return true;
}
return false;
}
void coroutine_fn co_get_from_shres(SharedResource *s, uint64_t n)
{
assert(n <= s->total);
while (!co_try_get_from_shres(s, n)) {
qemu_co_queue_wait(&s->queue, NULL);
}
}
void coroutine_fn co_put_to_shres(SharedResource *s, uint64_t n)
{
assert(s->total - s->available >= n);
s->available += n;
qemu_co_queue_restart_all(&s->queue);
}