Block layer patches:

- qemu-storage-daemon: add --pidfile option
 - qemu-storage-daemon: CLI error messages include the option name now
 - vhost-user-blk export: Misc fixes
 - docs: Improvements for qemu-storage-daemon documentation
 - parallels: load bitmap extension
 - backup-top: Don't crash on post-finalize accesses
 - Improve error messages related to node-name options
 - iotests improvements
 -----BEGIN PGP SIGNATURE-----
 
 iQJFBAABCAAvFiEE3D3rFZqa+V09dFb+fwmycsiPL9YFAmBGWHURHGt3b2xmQHJl
 ZGhhdC5jb20ACgkQfwmycsiPL9ZpyxAAk0gRiayMUidSzgvzU/CeUhzBsC4ayEkn
 dLtTZ8hl7cW/w3GjDK1Wri4MANRN/0YHjiLSzO38lfVpK0z8SJr5aU4CwhRlOKVm
 VWgx+OLlV4Azht9fMNF4SwUXgXhl7pUNiFMNnomb++gvqhjMCedDZcWlnVKhbuQ+
 O3TKGO4tToSGaXP85jCM4xukw5HZ//4QMYg6MH0gDk8ahfE2MhyTHz64oDp412os
 qhxvc4bU2S5xGLaBfLGhsc6VPQFKjblG704P/Y73zeoxq12A0L2Ru98WvrNaXw7Z
 m54jJUINiDkJ7ZOl6W04zdeiLvs3BOrNe+7mxawOTmdkBsLOKErrhrTO1gJmHHmX
 kJLWEh9VYWxVbvE7C3KQt9bclR6wt+aPup4X1XE8pHtocPVONVq5bvctrVgxgK0b
 btN06NcK+2jQxcQkG4MnBJ8S41qmxHyIEQlQWKyUWXvKt6zsFU/NuWKMQrAfYZZi
 5J+RPU/fB073LY4lpAgou0OP1/RIvQmi5zWzjWm/Qbp3JpgC+azcYvxn7UU7J71P
 +u8IEQ4+Q9s0gvXQAh/U8AQg2eOqAwEAyFUJl9wpPN56O03dbI8KyCV5ECIRJu49
 CC8uKlJxZkbw9ZBs11SAmm/0J64WcNb2AMWxDPC8Z6oQbVaRRRznoRwRP2H6odUu
 uBolS43+5cI=
 =eAjH
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' into staging

Block layer patches:

- qemu-storage-daemon: add --pidfile option
- qemu-storage-daemon: CLI error messages include the option name now
- vhost-user-blk export: Misc fixes
- docs: Improvements for qemu-storage-daemon documentation
- parallels: load bitmap extension
- backup-top: Don't crash on post-finalize accesses
- Improve error messages related to node-name options
- iotests improvements

# gpg: Signature made Mon 08 Mar 2021 17:01:41 GMT
# gpg:                using RSA key DC3DEB159A9AF95D3D7456FE7F09B272C88F2FD6
# gpg:                issuer "kwolf@redhat.com"
# gpg: Good signature from "Kevin Wolf <kwolf@redhat.com>" [full]
# Primary key fingerprint: DC3D EB15 9A9A F95D 3D74  56FE 7F09 B272 C88F 2FD6

* remotes/kevin/tags/for-upstream: (30 commits)
  blockdev: Clarify error messages pertaining to 'node-name'
  block: Clarify error messages pertaining to 'node-name'
  docs: qsd: Explain --export nbd,name=... default
  MAINTAINERS: update parallels block driver
  iotests: add parallels-read-bitmap test
  iotests.py: add unarchive_sample_image() helper
  parallels: support bitmap extension for read-only mode
  block/parallels: BDRVParallelsState: add cluster_size field
  parallels.txt: fix bitmap L1 table description
  qcow2-bitmap: make bytes_covered_by_bitmap_cluster() public
  block/export: port virtio-blk read/write range check
  block/export: port virtio-blk discard/write zeroes input validation
  block/export: fix vhost-user-blk export sector number calculation
  block/export: use VIRTIO_BLK_SECTOR_BITS
  block/export: fix blk_size double byteswap
  libqtest: add qtest_remove_abrt_handler()
  libqtest: add qtest_kill_qemu()
  libqtest: add qtest_socket_server()
  vhost-user-blk: fix blkcfg->num_queues endianness
  docs: replace insecure /tmp examples in qsd docs
  ...

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
master
Peter Maydell 2021-03-09 21:31:18 +00:00
commit a557b00469
51 changed files with 969 additions and 175 deletions

View File

@ -3132,10 +3132,13 @@ F: block/dmg.c
parallels
M: Stefan Hajnoczi <stefanha@redhat.com>
M: Denis V. Lunev <den@openvz.org>
M: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
L: qemu-block@nongnu.org
S: Supported
F: block/parallels.c
F: block/parallels-ext.c
F: docs/interop/parallels.txt
T: git https://src.openvz.org/scm/~vsementsov/qemu.git parallels
qed
M: Stefan Hajnoczi <stefanha@redhat.com>

View File

@ -1440,7 +1440,7 @@ static void bdrv_assign_node_name(BlockDriverState *bs,
* Check for empty string or invalid characters, but not if it is
* generated (generated names use characters not available to the user)
*/
error_setg(errp, "Invalid node name");
error_setg(errp, "Invalid node-name: '%s'", node_name);
return;
}
@ -1453,7 +1453,7 @@ static void bdrv_assign_node_name(BlockDriverState *bs,
/* takes care of avoiding duplicates node names */
if (bdrv_find_node(node_name)) {
error_setg(errp, "Duplicate node name");
error_setg(errp, "Duplicate nodes with node-name='%s'", node_name);
goto out;
}
@ -5432,7 +5432,7 @@ BlockDriverState *bdrv_lookup_bs(const char *device,
}
}
error_setg(errp, "Cannot find device=%s nor node_name=%s",
error_setg(errp, "Cannot find device=\'%s\' nor node-name=\'%s\'",
device ? device : "",
node_name ? node_name : "");
return NULL;
@ -6752,7 +6752,7 @@ BlockDriverState *check_to_replace_node(BlockDriverState *parent_bs,
AioContext *aio_context;
if (!to_replace_bs) {
error_setg(errp, "Node name '%s' not found", node_name);
error_setg(errp, "Failed to find node with node-name='%s'", node_name);
return NULL;
}

View File

@ -45,6 +45,12 @@ static coroutine_fn int backup_top_co_preadv(
BlockDriverState *bs, uint64_t offset, uint64_t bytes,
QEMUIOVector *qiov, int flags)
{
BDRVBackupTopState *s = bs->opaque;
if (!s->active) {
return -EIO;
}
return bdrv_co_preadv(bs->backing, offset, bytes, qiov, flags);
}
@ -54,6 +60,10 @@ static coroutine_fn int backup_top_cbw(BlockDriverState *bs, uint64_t offset,
BDRVBackupTopState *s = bs->opaque;
uint64_t off, end;
if (!s->active) {
return -EIO;
}
if (flags & BDRV_REQ_WRITE_UNCHANGED) {
return 0;
}

View File

@ -103,6 +103,7 @@ static void backup_abort(Job *job)
static void backup_clean(Job *job)
{
BackupBlockJob *s = container_of(job, BackupBlockJob, common.job);
block_job_remove_all_bdrv(&s->common);
bdrv_backup_top_drop(s->backup_top);
}

View File

@ -726,6 +726,19 @@ uint64_t bdrv_dirty_bitmap_serialization_align(const BdrvDirtyBitmap *bitmap)
return hbitmap_serialization_align(bitmap->bitmap);
}
/* Return the disk size covered by a chunk of serialized bitmap data. */
uint64_t bdrv_dirty_bitmap_serialization_coverage(int serialized_chunk_size,
const BdrvDirtyBitmap *bitmap)
{
uint64_t granularity = bdrv_dirty_bitmap_granularity(bitmap);
uint64_t limit = granularity * (serialized_chunk_size << 3);
assert(QEMU_IS_ALIGNED(limit,
bdrv_dirty_bitmap_serialization_align(bitmap)));
return limit;
}
void bdrv_dirty_bitmap_serialize_part(const BdrvDirtyBitmap *bitmap,
uint8_t *buf, uint64_t offset,
uint64_t bytes)

View File

@ -20,8 +20,17 @@
#include "sysemu/block-backend.h"
#include "util/block-helpers.h"
/*
* Sector units are 512 bytes regardless of the
* virtio_blk_config->blk_size value.
*/
#define VIRTIO_BLK_SECTOR_BITS 9
#define VIRTIO_BLK_SECTOR_SIZE (1ull << VIRTIO_BLK_SECTOR_BITS)
enum {
VHOST_USER_BLK_NUM_QUEUES_DEFAULT = 1,
VHOST_USER_BLK_MAX_DISCARD_SECTORS = 32768,
VHOST_USER_BLK_MAX_WRITE_ZEROES_SECTORS = 32768,
};
struct virtio_blk_inhdr {
unsigned char status;
@ -58,30 +67,102 @@ static void vu_blk_req_complete(VuBlkReq *req)
free(req);
}
static bool vu_blk_sect_range_ok(VuBlkExport *vexp, uint64_t sector,
size_t size)
{
uint64_t nb_sectors = size >> BDRV_SECTOR_BITS;
uint64_t total_sectors;
if (nb_sectors > BDRV_REQUEST_MAX_SECTORS) {
return false;
}
if ((sector << VIRTIO_BLK_SECTOR_BITS) % vexp->blk_size) {
return false;
}
blk_get_geometry(vexp->export.blk, &total_sectors);
if (sector > total_sectors || nb_sectors > total_sectors - sector) {
return false;
}
return true;
}
static int coroutine_fn
vu_blk_discard_write_zeroes(BlockBackend *blk, struct iovec *iov,
vu_blk_discard_write_zeroes(VuBlkExport *vexp, struct iovec *iov,
uint32_t iovcnt, uint32_t type)
{
BlockBackend *blk = vexp->export.blk;
struct virtio_blk_discard_write_zeroes desc;
ssize_t size = iov_to_buf(iov, iovcnt, 0, &desc, sizeof(desc));
ssize_t size;
uint64_t sector;
uint32_t num_sectors;
uint32_t max_sectors;
uint32_t flags;
int bytes;
/* Only one desc is currently supported */
if (unlikely(iov_size(iov, iovcnt) > sizeof(desc))) {
return VIRTIO_BLK_S_UNSUPP;
}
size = iov_to_buf(iov, iovcnt, 0, &desc, sizeof(desc));
if (unlikely(size != sizeof(desc))) {
error_report("Invalid size %zd, expect %zu", size, sizeof(desc));
return -EINVAL;
error_report("Invalid size %zd, expected %zu", size, sizeof(desc));
return VIRTIO_BLK_S_IOERR;
}
uint64_t range[2] = { le64_to_cpu(desc.sector) << 9,
le32_to_cpu(desc.num_sectors) << 9 };
if (type == VIRTIO_BLK_T_DISCARD) {
if (blk_co_pdiscard(blk, range[0], range[1]) == 0) {
return 0;
sector = le64_to_cpu(desc.sector);
num_sectors = le32_to_cpu(desc.num_sectors);
flags = le32_to_cpu(desc.flags);
max_sectors = (type == VIRTIO_BLK_T_WRITE_ZEROES) ?
VHOST_USER_BLK_MAX_WRITE_ZEROES_SECTORS :
VHOST_USER_BLK_MAX_DISCARD_SECTORS;
/* This check ensures that 'bytes' fits in an int */
if (unlikely(num_sectors > max_sectors)) {
return VIRTIO_BLK_S_IOERR;
}
bytes = num_sectors << VIRTIO_BLK_SECTOR_BITS;
if (unlikely(!vu_blk_sect_range_ok(vexp, sector, bytes))) {
return VIRTIO_BLK_S_IOERR;
}
/*
* The device MUST set the status byte to VIRTIO_BLK_S_UNSUPP for discard
* and write zeroes commands if any unknown flag is set.
*/
if (unlikely(flags & ~VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP)) {
return VIRTIO_BLK_S_UNSUPP;
}
if (type == VIRTIO_BLK_T_WRITE_ZEROES) {
int blk_flags = 0;
if (flags & VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP) {
blk_flags |= BDRV_REQ_MAY_UNMAP;
}
} else if (type == VIRTIO_BLK_T_WRITE_ZEROES) {
if (blk_co_pwrite_zeroes(blk, range[0], range[1], 0) == 0) {
return 0;
if (blk_co_pwrite_zeroes(blk, sector << VIRTIO_BLK_SECTOR_BITS,
bytes, blk_flags) == 0) {
return VIRTIO_BLK_S_OK;
}
} else if (type == VIRTIO_BLK_T_DISCARD) {
/*
* The device MUST set the status byte to VIRTIO_BLK_S_UNSUPP for
* discard commands if the unmap flag is set.
*/
if (unlikely(flags & VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP)) {
return VIRTIO_BLK_S_UNSUPP;
}
if (blk_co_pdiscard(blk, sector << VIRTIO_BLK_SECTOR_BITS,
bytes) == 0) {
return VIRTIO_BLK_S_OK;
}
}
return -EINVAL;
return VIRTIO_BLK_S_IOERR;
}
static void coroutine_fn vu_blk_virtio_process_req(void *opaque)
@ -128,6 +209,8 @@ static void coroutine_fn vu_blk_virtio_process_req(void *opaque)
switch (type & ~VIRTIO_BLK_T_BARRIER) {
case VIRTIO_BLK_T_IN:
case VIRTIO_BLK_T_OUT: {
QEMUIOVector qiov;
int64_t offset;
ssize_t ret = 0;
bool is_write = type & VIRTIO_BLK_T_OUT;
req->sector_num = le64_to_cpu(req->out.sector);
@ -137,13 +220,24 @@ static void coroutine_fn vu_blk_virtio_process_req(void *opaque)
break;
}
int64_t offset = req->sector_num * vexp->blk_size;
QEMUIOVector qiov;
if (is_write) {
qemu_iovec_init_external(&qiov, out_iov, out_num);
ret = blk_co_pwritev(blk, offset, qiov.size, &qiov, 0);
} else {
qemu_iovec_init_external(&qiov, in_iov, in_num);
}
if (unlikely(!vu_blk_sect_range_ok(vexp,
req->sector_num,
qiov.size))) {
req->in->status = VIRTIO_BLK_S_IOERR;
break;
}
offset = req->sector_num << VIRTIO_BLK_SECTOR_BITS;
if (is_write) {
ret = blk_co_pwritev(blk, offset, qiov.size, &qiov, 0);
} else {
ret = blk_co_preadv(blk, offset, qiov.size, &qiov, 0);
}
if (ret >= 0) {
@ -170,19 +264,13 @@ static void coroutine_fn vu_blk_virtio_process_req(void *opaque)
}
case VIRTIO_BLK_T_DISCARD:
case VIRTIO_BLK_T_WRITE_ZEROES: {
int rc;
if (!vexp->writable) {
req->in->status = VIRTIO_BLK_S_IOERR;
break;
}
rc = vu_blk_discard_write_zeroes(blk, &elem->out_sg[1], out_num, type);
if (rc == 0) {
req->in->status = VIRTIO_BLK_S_OK;
} else {
req->in->status = VIRTIO_BLK_S_IOERR;
}
req->in->status = vu_blk_discard_write_zeroes(vexp, out_iov, out_num,
type);
break;
}
default:
@ -347,17 +435,21 @@ vu_blk_initialize_config(BlockDriverState *bs,
uint32_t blk_size,
uint16_t num_queues)
{
config->capacity = cpu_to_le64(bdrv_getlength(bs) >> BDRV_SECTOR_BITS);
config->capacity =
cpu_to_le64(bdrv_getlength(bs) >> VIRTIO_BLK_SECTOR_BITS);
config->blk_size = cpu_to_le32(blk_size);
config->size_max = cpu_to_le32(0);
config->seg_max = cpu_to_le32(128 - 2);
config->min_io_size = cpu_to_le16(1);
config->opt_io_size = cpu_to_le32(1);
config->num_queues = cpu_to_le16(num_queues);
config->max_discard_sectors = cpu_to_le32(32768);
config->max_discard_sectors =
cpu_to_le32(VHOST_USER_BLK_MAX_DISCARD_SECTORS);
config->max_discard_seg = cpu_to_le32(1);
config->discard_sector_alignment = cpu_to_le32(config->blk_size >> 9);
config->max_write_zeroes_sectors = cpu_to_le32(32768);
config->discard_sector_alignment =
cpu_to_le32(blk_size >> VIRTIO_BLK_SECTOR_BITS);
config->max_write_zeroes_sectors
= cpu_to_le32(VHOST_USER_BLK_MAX_WRITE_ZEROES_SECTORS);
config->max_write_zeroes_seg = cpu_to_le32(1);
}
@ -383,7 +475,7 @@ static int vu_blk_exp_create(BlockExport *exp, BlockExportOptions *opts,
if (vu_opts->has_logical_block_size) {
logical_block_size = vu_opts->logical_block_size;
} else {
logical_block_size = BDRV_SECTOR_SIZE;
logical_block_size = VIRTIO_BLK_SECTOR_SIZE;
}
check_block_size(exp->id, "logical-block-size", logical_block_size,
&local_err);

View File

@ -57,7 +57,8 @@ block_ss.add(when: 'CONFIG_QED', if_true: files(
'qed-table.c',
'qed.c',
))
block_ss.add(when: [libxml2, 'CONFIG_PARALLELS'], if_true: files('parallels.c'))
block_ss.add(when: [libxml2, 'CONFIG_PARALLELS'],
if_true: files('parallels.c', 'parallels-ext.c'))
block_ss.add(when: 'CONFIG_WIN32', if_true: files('file-win32.c', 'win32-aio.c'))
block_ss.add(when: 'CONFIG_POSIX', if_true: [files('file-posix.c'), coref, iokit])
block_ss.add(when: libiscsi, if_true: files('iscsi-opts.c'))

300
block/parallels-ext.c Normal file
View File

@ -0,0 +1,300 @@
/*
* Support of Parallels Format Extension. It's a part of Parallels format
* driver.
*
* Copyright (c) 2021 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 "qapi/error.h"
#include "block/block_int.h"
#include "parallels.h"
#include "crypto/hash.h"
#include "qemu/uuid.h"
#define PARALLELS_FORMAT_EXTENSION_MAGIC 0xAB234CEF23DCEA87ULL
#define PARALLELS_END_OF_FEATURES_MAGIC 0x0ULL
#define PARALLELS_DIRTY_BITMAP_FEATURE_MAGIC 0x20385FAE252CB34AULL
typedef struct ParallelsFormatExtensionHeader {
uint64_t magic; /* PARALLELS_FORMAT_EXTENSION_MAGIC */
uint8_t check_sum[16];
} QEMU_PACKED ParallelsFormatExtensionHeader;
typedef struct ParallelsFeatureHeader {
uint64_t magic;
uint64_t flags;
uint32_t data_size;
uint32_t _unused;
} QEMU_PACKED ParallelsFeatureHeader;
typedef struct ParallelsDirtyBitmapFeature {
uint64_t size;
uint8_t id[16];
uint32_t granularity;
uint32_t l1_size;
/* L1 table follows */
} QEMU_PACKED ParallelsDirtyBitmapFeature;
/* Given L1 table read bitmap data from the image and populate @bitmap */
static int parallels_load_bitmap_data(BlockDriverState *bs,
const uint64_t *l1_table,
uint32_t l1_size,
BdrvDirtyBitmap *bitmap,
Error **errp)
{
BDRVParallelsState *s = bs->opaque;
int ret = 0;
uint64_t offset, limit;
uint64_t bm_size = bdrv_dirty_bitmap_size(bitmap);
uint8_t *buf = NULL;
uint64_t i, tab_size =
DIV_ROUND_UP(bdrv_dirty_bitmap_serialization_size(bitmap, 0, bm_size),
s->cluster_size);
if (tab_size != l1_size) {
error_setg(errp, "Bitmap table size %" PRIu32 " does not correspond "
"to bitmap size and cluster size. Expected %" PRIu64,
l1_size, tab_size);
return -EINVAL;
}
buf = qemu_blockalign(bs, s->cluster_size);
limit = bdrv_dirty_bitmap_serialization_coverage(s->cluster_size, bitmap);
for (i = 0, offset = 0; i < tab_size; ++i, offset += limit) {
uint64_t count = MIN(bm_size - offset, limit);
uint64_t entry = l1_table[i];
if (entry == 0) {
/* No need to deserialize zeros because @bitmap is cleared. */
continue;
}
if (entry == 1) {
bdrv_dirty_bitmap_deserialize_ones(bitmap, offset, count, false);
} else {
ret = bdrv_pread(bs->file, entry << BDRV_SECTOR_BITS, buf,
s->cluster_size);
if (ret < 0) {
error_setg_errno(errp, -ret,
"Failed to read bitmap data cluster");
goto finish;
}
bdrv_dirty_bitmap_deserialize_part(bitmap, buf, offset, count,
false);
}
}
ret = 0;
bdrv_dirty_bitmap_deserialize_finish(bitmap);
finish:
qemu_vfree(buf);
return ret;
}
/*
* @data buffer (of @data_size size) is the Dirty bitmaps feature which
* consists of ParallelsDirtyBitmapFeature followed by L1 table.
*/
static BdrvDirtyBitmap *parallels_load_bitmap(BlockDriverState *bs,
uint8_t *data,
size_t data_size,
Error **errp)
{
int ret;
ParallelsDirtyBitmapFeature bf;
g_autofree uint64_t *l1_table = NULL;
BdrvDirtyBitmap *bitmap;
QemuUUID uuid;
char uuidstr[UUID_FMT_LEN + 1];
int i;
if (data_size < sizeof(bf)) {
error_setg(errp, "Too small Bitmap Feature area in Parallels Format "
"Extension: %zu bytes, expected at least %zu bytes",
data_size, sizeof(bf));
return NULL;
}
memcpy(&bf, data, sizeof(bf));
bf.size = le64_to_cpu(bf.size);
bf.granularity = le32_to_cpu(bf.granularity) << BDRV_SECTOR_BITS;
bf.l1_size = le32_to_cpu(bf.l1_size);
data += sizeof(bf);
data_size -= sizeof(bf);
if (bf.size != bs->total_sectors) {
error_setg(errp, "Bitmap size (in sectors) %" PRId64 " differs from "
"disk size in sectors %" PRId64, bf.size, bs->total_sectors);
return NULL;
}
if (bf.l1_size * sizeof(uint64_t) > data_size) {
error_setg(errp, "Bitmaps feature corrupted: l1 table exceeds "
"extension data_size");
return NULL;
}
memcpy(&uuid, bf.id, sizeof(uuid));
qemu_uuid_unparse(&uuid, uuidstr);
bitmap = bdrv_create_dirty_bitmap(bs, bf.granularity, uuidstr, errp);
if (!bitmap) {
return NULL;
}
l1_table = g_new(uint64_t, bf.l1_size);
for (i = 0; i < bf.l1_size; i++, data += sizeof(uint64_t)) {
l1_table[i] = ldq_le_p(data);
}
ret = parallels_load_bitmap_data(bs, l1_table, bf.l1_size, bitmap, errp);
if (ret < 0) {
bdrv_release_dirty_bitmap(bitmap);
return NULL;
}
/* We support format extension only for RO parallels images. */
assert(!(bs->open_flags & BDRV_O_RDWR));
bdrv_dirty_bitmap_set_readonly(bitmap, true);
return bitmap;
}
static int parallels_parse_format_extension(BlockDriverState *bs,
uint8_t *ext_cluster, Error **errp)
{
BDRVParallelsState *s = bs->opaque;
int ret;
int remaining = s->cluster_size;
uint8_t *pos = ext_cluster;
ParallelsFormatExtensionHeader eh;
g_autofree uint8_t *hash = NULL;
size_t hash_len = 0;
GSList *bitmaps = NULL, *el;
memcpy(&eh, pos, sizeof(eh));
eh.magic = le64_to_cpu(eh.magic);
pos += sizeof(eh);
remaining -= sizeof(eh);
if (eh.magic != PARALLELS_FORMAT_EXTENSION_MAGIC) {
error_setg(errp, "Wrong parallels Format Extension magic: 0x%" PRIx64
", expected: 0x%llx", eh.magic,
PARALLELS_FORMAT_EXTENSION_MAGIC);
goto fail;
}
ret = qcrypto_hash_bytes(QCRYPTO_HASH_ALG_MD5, (char *)pos, remaining,
&hash, &hash_len, errp);
if (ret < 0) {
goto fail;
}
if (hash_len != sizeof(eh.check_sum) ||
memcmp(hash, eh.check_sum, sizeof(eh.check_sum)) != 0) {
error_setg(errp, "Wrong checksum in Format Extension header. Format "
"extension is corrupted.");
goto fail;
}
while (true) {
ParallelsFeatureHeader fh;
BdrvDirtyBitmap *bitmap;
if (remaining < sizeof(fh)) {
error_setg(errp, "Can not read feature header, as remaining bytes "
"(%d) in Format Extension is less than Feature header "
"size (%zu)", remaining, sizeof(fh));
goto fail;
}
memcpy(&fh, pos, sizeof(fh));
pos += sizeof(fh);
remaining -= sizeof(fh);
fh.magic = le64_to_cpu(fh.magic);
fh.flags = le64_to_cpu(fh.flags);
fh.data_size = le32_to_cpu(fh.data_size);
if (fh.flags) {
error_setg(errp, "Flags for extension feature are unsupported");
goto fail;
}
if (fh.data_size > remaining) {
error_setg(errp, "Feature data_size exceedes Format Extension "
"cluster");
goto fail;
}
switch (fh.magic) {
case PARALLELS_END_OF_FEATURES_MAGIC:
return 0;
case PARALLELS_DIRTY_BITMAP_FEATURE_MAGIC:
bitmap = parallels_load_bitmap(bs, pos, fh.data_size, errp);
if (!bitmap) {
goto fail;
}
bitmaps = g_slist_append(bitmaps, bitmap);
break;
default:
error_setg(errp, "Unknown feature: 0x%" PRIu64, fh.magic);
goto fail;
}
pos = ext_cluster + QEMU_ALIGN_UP(pos + fh.data_size - ext_cluster, 8);
}
fail:
for (el = bitmaps; el; el = el->next) {
bdrv_release_dirty_bitmap(el->data);
}
g_slist_free(bitmaps);
return -EINVAL;
}
int parallels_read_format_extension(BlockDriverState *bs,
int64_t ext_off, Error **errp)
{
BDRVParallelsState *s = bs->opaque;
int ret;
uint8_t *ext_cluster = qemu_blockalign(bs, s->cluster_size);
assert(ext_off > 0);
ret = bdrv_pread(bs->file, ext_off, ext_cluster, s->cluster_size);
if (ret < 0) {
error_setg_errno(errp, -ret, "Failed to read Format Extension cluster");
goto out;
}
ret = parallels_parse_format_extension(bs, ext_cluster, errp);
out:
qemu_vfree(ext_cluster);
return ret;
}

View File

@ -29,6 +29,7 @@
*/
#include "qemu/osdep.h"
#include "qemu/error-report.h"
#include "qapi/error.h"
#include "block/block_int.h"
#include "block/qdict.h"
@ -421,7 +422,6 @@ static int coroutine_fn parallels_co_check(BlockDriverState *bs,
int ret;
uint32_t i;
bool flush_bat = false;
int cluster_size = s->tracks << BDRV_SECTOR_BITS;
size = bdrv_getlength(bs->file->bs);
if (size < 0) {
@ -472,7 +472,7 @@ static int coroutine_fn parallels_co_check(BlockDriverState *bs,
high_off = off;
}
if (prev_off != 0 && (prev_off + cluster_size) != off) {
if (prev_off != 0 && (prev_off + s->cluster_size) != off) {
res->bfi.fragmented_clusters++;
}
prev_off = off;
@ -487,10 +487,10 @@ static int coroutine_fn parallels_co_check(BlockDriverState *bs,
}
}
res->image_end_offset = high_off + cluster_size;
res->image_end_offset = high_off + s->cluster_size;
if (size > res->image_end_offset) {
int64_t count;
count = DIV_ROUND_UP(size - res->image_end_offset, cluster_size);
count = DIV_ROUND_UP(size - res->image_end_offset, s->cluster_size);
fprintf(stderr, "%s space leaked at the end of the image %" PRId64 "\n",
fix & BDRV_FIX_LEAKS ? "Repairing" : "ERROR",
size - res->image_end_offset);
@ -771,6 +771,7 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
ret = -EFBIG;
goto fail;
}
s->cluster_size = s->tracks << BDRV_SECTOR_BITS;
s->bat_size = le32_to_cpu(ph.bat_entries);
if (s->bat_size > INT_MAX / sizeof(uint32_t)) {
@ -843,6 +844,23 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
goto fail_options;
}
if (ph.ext_off) {
if (flags & BDRV_O_RDWR) {
/*
* It's unsafe to open image RW if there is an extension (as we
* don't support it). But parallels driver in QEMU historically
* ignores the extension, so print warning and don't care.
*/
warn_report("Format Extension ignored in RW mode");
} else {
ret = parallels_read_format_extension(
bs, le64_to_cpu(ph.ext_off) << BDRV_SECTOR_BITS, errp);
if (ret < 0) {
goto fail;
}
}
}
if ((flags & BDRV_O_RDWR) && !(flags & BDRV_O_INACTIVE)) {
s->header->inuse = cpu_to_le32(HEADER_INUSE_MAGIC);
ret = parallels_update_header(bs);

View File

@ -48,7 +48,8 @@ typedef struct ParallelsHeader {
uint64_t nb_sectors;
uint32_t inuse;
uint32_t data_off;
char padding[12];
uint32_t flags;
uint64_t ext_off;
} QEMU_PACKED ParallelsHeader;
typedef enum ParallelsPreallocMode {
@ -79,9 +80,13 @@ typedef struct BDRVParallelsState {
ParallelsPreallocMode prealloc_mode;
unsigned int tracks;
unsigned int cluster_size;
unsigned int off_multiplier;
Error *migration_blocker;
} BDRVParallelsState;
int parallels_read_format_extension(BlockDriverState *bs,
int64_t ext_off, Error **errp);
#endif

View File

@ -278,18 +278,6 @@ static int free_bitmap_clusters(BlockDriverState *bs, Qcow2BitmapTable *tb)
return 0;
}
/* Return the disk size covered by a single qcow2 cluster of bitmap data. */
static uint64_t bytes_covered_by_bitmap_cluster(const BDRVQcow2State *s,
const BdrvDirtyBitmap *bitmap)
{
uint64_t granularity = bdrv_dirty_bitmap_granularity(bitmap);
uint64_t limit = granularity * (s->cluster_size << 3);
assert(QEMU_IS_ALIGNED(limit,
bdrv_dirty_bitmap_serialization_align(bitmap)));
return limit;
}
/* load_bitmap_data
* @bitmap_table entries must satisfy specification constraints.
* @bitmap must be cleared */
@ -312,7 +300,7 @@ static int load_bitmap_data(BlockDriverState *bs,
}
buf = g_malloc(s->cluster_size);
limit = bytes_covered_by_bitmap_cluster(s, bitmap);
limit = bdrv_dirty_bitmap_serialization_coverage(s->cluster_size, bitmap);
for (i = 0, offset = 0; i < tab_size; ++i, offset += limit) {
uint64_t count = MIN(bm_size - offset, limit);
uint64_t entry = bitmap_table[i];
@ -1303,7 +1291,7 @@ static uint64_t *store_bitmap_data(BlockDriverState *bs,
}
buf = g_malloc(s->cluster_size);
limit = bytes_covered_by_bitmap_cluster(s, bitmap);
limit = bdrv_dirty_bitmap_serialization_coverage(s->cluster_size, bitmap);
assert(DIV_ROUND_UP(bm_size, limit) == tb_size);
offset = 0;

View File

@ -1515,13 +1515,13 @@ static void external_snapshot_prepare(BlkActionState *common,
s->has_snapshot_node_name ? s->snapshot_node_name : NULL;
if (node_name && !snapshot_node_name) {
error_setg(errp, "New overlay node name missing");
error_setg(errp, "New overlay node-name missing");
goto out;
}
if (snapshot_node_name &&
bdrv_lookup_bs(snapshot_node_name, snapshot_node_name, NULL)) {
error_setg(errp, "New overlay node name already in use");
error_setg(errp, "New overlay node-name already in use");
goto out;
}
@ -3598,13 +3598,14 @@ void qmp_x_blockdev_reopen(BlockdevOptions *options, Error **errp)
/* Check for the selected node name */
if (!options->has_node_name) {
error_setg(errp, "Node name not specified");
error_setg(errp, "node-name not specified");
goto fail;
}
bs = bdrv_find_node(options->node_name);
if (!bs) {
error_setg(errp, "Cannot find node named '%s'", options->node_name);
error_setg(errp, "Failed to find node with node-name='%s'",
options->node_name);
goto fail;
}
@ -3635,7 +3636,7 @@ void qmp_blockdev_del(const char *node_name, Error **errp)
bs = bdrv_find_node(node_name);
if (!bs) {
error_setg(errp, "Cannot find node %s", node_name);
error_setg(errp, "Failed to find node with node-name='%s'", node_name);
return;
}
if (bdrv_has_blk(bs)) {
@ -3758,7 +3759,7 @@ void qmp_x_blockdev_set_iothread(const char *node_name, StrOrNull *iothread,
bs = bdrv_find_node(node_name);
if (!bs) {
error_setg(errp, "Cannot find node %s", node_name);
error_setg(errp, "Failed to find node with node-name='%s'", node_name);
return;
}

View File

@ -318,8 +318,12 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
info->status = job->job.status;
info->auto_finalize = job->job.auto_finalize;
info->auto_dismiss = job->job.auto_dismiss;
info->has_error = job->job.ret != 0;
info->error = job->job.ret ? g_strdup(strerror(-job->job.ret)) : NULL;
if (job->job.ret) {
info->has_error = true;
info->error = job->job.err ?
g_strdup(error_get_pretty(job->job.err)) :
g_strdup(strerror(-job->job.ret));
}
return info;
}
@ -356,7 +360,7 @@ static void block_job_event_completed(Notifier *n, void *opaque)
}
if (job->job.ret < 0) {
msg = strerror(-job->job.ret);
msg = error_get_pretty(job->job.err);
}
qapi_event_send_block_job_completed(job_type(&job->job),

View File

@ -208,21 +208,25 @@ of its data area are:
28 - 31: l1_size
The number of entries in the L1 table of the bitmap.
variable: l1_table (8 * l1_size bytes)
L1 offset table (in bytes)
variable: L1 offset table (l1_table), size: 8 * l1_size bytes
A dirty bitmap is stored using a one-level structure for the mapping to host
clusters - an L1 table.
The dirty bitmap described by this feature extension is stored in a set of
clusters inside the Parallels image file. The offsets of these clusters are
saved in the L1 offset table specified by the feature extension. Each L1 table
entry is a 64 bit integer as described below:
Given an offset in bytes into the bitmap data, the offset in bytes into the
image file can be obtained as follows:
Given an offset in bytes into the bitmap data, corresponding L1 entry is
offset = l1_table[offset / cluster_size] + (offset % cluster_size)
l1_table[offset / cluster_size]
If an L1 table entry is 0, the corresponding cluster of the bitmap is assumed
to be zero.
If an L1 table entry is 0, all bits in the corresponding cluster of the bitmap
are assumed to be 0.
If an L1 table entry is 1, the corresponding cluster of the bitmap is assumed
to have all bits set.
If an L1 table entry is 1, all bits in the corresponding cluster of the bitmap
are assumed to be 1.
If an L1 table entry is not 0 or 1, it allocates a cluster from the data area.
If an L1 table entry is not 0 or 1, it contains the corresponding cluster
offset (in 512b sectors). Given an offset in bytes into the bitmap data the
offset in bytes into the image file can be obtained as follows:
offset = l1_table[offset / cluster_size] * 512 + (offset % cluster_size)

View File

@ -69,7 +69,7 @@ Standard options:
a description of character device properties. A common character device
definition configures a UNIX domain socket::
--chardev socket,id=char1,path=/tmp/qmp.sock,server=on,wait=off
--chardev socket,id=char1,path=/var/run/qsd-qmp.sock,server=on,wait=off
.. option:: --export [type=]nbd,id=<id>,node-name=<node-name>[,name=<export-name>][,writable=on|off][,bitmap=<name>]
--export [type=]vhost-user-blk,id=<id>,node-name=<node-name>,addr.type=unix,addr.path=<socket-path>[,writable=on|off][,logical-block-size=<block-size>][,num-queues=<num-queues>]
@ -80,8 +80,9 @@ Standard options:
requests for modifying data (the default is off).
The ``nbd`` export type requires ``--nbd-server`` (see below). ``name`` is
the NBD export name. ``bitmap`` is the name of a dirty bitmap reachable from
the block node, so the NBD client can use NBD_OPT_SET_META_CONTEXT with the
the NBD export name (if not specified, it defaults to the given
``node-name``). ``bitmap`` is the name of a dirty bitmap reachable from the
block node, so the NBD client can use NBD_OPT_SET_META_CONTEXT with the
metadata context name "qemu:dirty-bitmap:BITMAP" to inspect the bitmap.
The ``vhost-user-blk`` export type takes a vhost-user socket address on which
@ -101,14 +102,17 @@ Standard options:
.. option:: --nbd-server addr.type=inet,addr.host=<host>,addr.port=<port>[,tls-creds=<id>][,tls-authz=<id>][,max-connections=<n>]
--nbd-server addr.type=unix,addr.path=<path>[,tls-creds=<id>][,tls-authz=<id>][,max-connections=<n>]
--nbd-server addr.type=fd,addr.str=<fd>[,tls-creds=<id>][,tls-authz=<id>][,max-connections=<n>]
is a server for NBD exports. Both TCP and UNIX domain sockets are supported.
TLS encryption can be configured using ``--object`` tls-creds-* and authz-*
secrets (see below).
A listen socket can be provided via file descriptor passing (see Examples
below). TLS encryption can be configured using ``--object`` tls-creds-* and
authz-* secrets (see below).
To configure an NBD server on UNIX domain socket path ``/tmp/nbd.sock``::
To configure an NBD server on UNIX domain socket path
``/var/run/qsd-nbd.sock``::
--nbd-server addr.type=unix,addr.path=/tmp/nbd.sock
--nbd-server addr.type=unix,addr.path=/var/run/qsd-nbd.sock
.. option:: --object help
--object <type>,help
@ -118,6 +122,20 @@ Standard options:
List object properties with ``<type>,help``. See the :manpage:`qemu(1)`
manual page for a description of the object properties.
.. option:: --pidfile PATH
is the path to a file where the daemon writes its pid. This allows scripts to
stop the daemon by sending a signal::
$ kill -SIGTERM $(<path/to/qsd.pid)
A file lock is applied to the file so only one instance of the daemon can run
with a given pid file path. The daemon unlinks its pid file when terminating.
The pid file is written after chardevs, exports, and NBD servers have been
created but before accepting connections. The daemon has started successfully
when the pid file is written and clients may begin connecting.
Examples
--------
Launch the daemon with QMP monitor socket ``qmp.sock`` so clients can execute
@ -127,6 +145,42 @@ QMP commands::
--chardev socket,path=qmp.sock,server=on,wait=off,id=char1 \
--monitor chardev=char1
Launch the daemon from Python with a QMP monitor socket using file descriptor
passing so there is no need to busy wait for the QMP monitor to become
available::
#!/usr/bin/env python3
import subprocess
import socket
sock_path = '/var/run/qmp.sock'
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as listen_sock:
listen_sock.bind(sock_path)
listen_sock.listen()
fd = listen_sock.fileno()
subprocess.Popen(
['qemu-storage-daemon',
'--chardev', f'socket,fd={fd},server=on,id=char1',
'--monitor', 'chardev=char1'],
pass_fds=[fd],
)
# listen_sock was automatically closed when leaving the 'with' statement
# body. If the daemon process terminated early then the following connect()
# will fail with "Connection refused" because no process has the listen
# socket open anymore. Launch errors can be detected this way.
qmp_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
qmp_sock.connect(sock_path)
...QMP interaction...
The same socket spawning approach also works with the ``--nbd-server
addr.type=fd,addr.str=<fd>`` and ``--export
type=vhost-user-blk,addr.type=fd,addr.str=<fd>`` options.
Export raw image file ``disk.img`` over NBD UNIX domain socket ``nbd.sock``::
$ qemu-storage-daemon \

View File

@ -54,6 +54,9 @@ static void vhost_user_blk_update_config(VirtIODevice *vdev, uint8_t *config)
{
VHostUserBlk *s = VHOST_USER_BLK(vdev);
/* Our num_queues overrides the device backend */
virtio_stw_p(vdev, &s->blkcfg.num_queues, s->num_queues);
memcpy(config, &s->blkcfg, sizeof(struct virtio_blk_config));
}
@ -491,10 +494,6 @@ reconnect:
goto reconnect;
}
if (s->blkcfg.num_queues != s->num_queues) {
s->blkcfg.num_queues = s->num_queues;
}
return;
virtio_err:

View File

@ -57,6 +57,8 @@ void bdrv_dirty_iter_free(BdrvDirtyBitmapIter *iter);
uint64_t bdrv_dirty_bitmap_serialization_size(const BdrvDirtyBitmap *bitmap,
uint64_t offset, uint64_t bytes);
uint64_t bdrv_dirty_bitmap_serialization_align(const BdrvDirtyBitmap *bitmap);
uint64_t bdrv_dirty_bitmap_serialization_coverage(int serialized_chunk_size,
const BdrvDirtyBitmap *bitmap);
void bdrv_dirty_bitmap_serialize_part(const BdrvDirtyBitmap *bitmap,
uint8_t *buf, uint64_t offset,
uint64_t bytes);

View File

@ -59,6 +59,7 @@
#include "sysemu/runstate.h"
#include "trace/control.h"
static const char *pid_file;
static volatile bool exit_requested = false;
void qemu_system_killed(int signal, pid_t pid)
@ -115,6 +116,8 @@ static void help(void)
" See the qemu(1) man page for documentation of the\n"
" objects that can be added.\n"
"\n"
" --pidfile <path> write process ID to a file after startup\n"
"\n"
QEMU_HELP_BOTTOM "\n",
error_get_progname());
}
@ -126,6 +129,7 @@ enum {
OPTION_MONITOR,
OPTION_NBD_SERVER,
OPTION_OBJECT,
OPTION_PIDFILE,
};
extern QemuOptsList qemu_chardev_opts;
@ -152,6 +156,20 @@ static void init_qmp_commands(void)
qmp_marshal_qmp_capabilities, QCO_ALLOW_PRECONFIG);
}
static int getopt_set_loc(int argc, char **argv, const char *optstring,
const struct option *longopts)
{
int c, save_index;
optarg = NULL;
save_index = optind;
c = getopt_long(argc, argv, optstring, longopts, NULL);
if (optarg) {
loc_set_cmdline(argv, save_index, MAX(1, optind - save_index));
}
return c;
}
static void process_options(int argc, char *argv[])
{
int c;
@ -164,6 +182,7 @@ static void process_options(int argc, char *argv[])
{"monitor", required_argument, NULL, OPTION_MONITOR},
{"nbd-server", required_argument, NULL, OPTION_NBD_SERVER},
{"object", required_argument, NULL, OPTION_OBJECT},
{"pidfile", required_argument, NULL, OPTION_PIDFILE},
{"trace", required_argument, NULL, 'T'},
{"version", no_argument, NULL, 'V'},
{0, 0, 0, 0}
@ -174,7 +193,7 @@ static void process_options(int argc, char *argv[])
* they are given on the command lines. This means that things must be
* defined first before they can be referenced in another option.
*/
while ((c = getopt_long(argc, argv, "hT:V", long_options, NULL)) != -1) {
while ((c = getopt_set_loc(argc, argv, "-hT:V", long_options)) != -1) {
switch (c) {
case '?':
exit(EXIT_FAILURE);
@ -275,14 +294,38 @@ static void process_options(int argc, char *argv[])
qobject_unref(args);
break;
}
case OPTION_PIDFILE:
pid_file = optarg;
break;
case 1:
error_report("Unexpected argument");
exit(EXIT_FAILURE);
default:
g_assert_not_reached();
}
}
if (optind != argc) {
error_report("Unexpected argument: %s", argv[optind]);
loc_set_none();
}
static void pid_file_cleanup(void)
{
unlink(pid_file);
}
static void pid_file_init(void)
{
Error *err = NULL;
if (!pid_file) {
return;
}
if (!qemu_write_pidfile(pid_file, &err)) {
error_reportf_err(err, "cannot create PID file: ");
exit(EXIT_FAILURE);
}
atexit(pid_file_cleanup);
}
int main(int argc, char *argv[])
@ -312,6 +355,13 @@ int main(int argc, char *argv[])
qemu_init_main_loop(&error_fatal);
process_options(argc, argv);
/*
* Write the pid file after creating chardevs, exports, and NBD servers but
* before accepting connections. This ordering is documented. Do not change
* it.
*/
pid_file_init();
while (!exit_requested) {
main_loop_wait(false);
}

View File

@ -153,7 +153,7 @@ class TestSingleDrive(iotests.QMPTestCase):
def test_device_not_found(self):
result = self.vm.qmp('block-stream', device='nonexistent')
self.assert_qmp(result, 'error/desc',
'Cannot find device=nonexistent nor node_name=nonexistent')
'Cannot find device=\'nonexistent\' nor node-name=\'nonexistent\'')
def test_job_id_missing(self):
result = self.vm.qmp('block-stream', device='mid')
@ -507,7 +507,7 @@ class TestParallelOps(iotests.QMPTestCase):
# Error: the base node does not exist
result = self.vm.qmp('block-stream', device='node4', base_node='none', job_id='stream')
self.assert_qmp(result, 'error/desc',
'Cannot find device= nor node_name=none')
'Cannot find device=\'\' nor node-name=\'none\'')
# Error: the base node is not a backing file of the top node
result = self.vm.qmp('block-stream', device='node4', base_node='node6', job_id='stream')

View File

@ -175,13 +175,13 @@ class TestSingleDrive(ImageCommitTestCase):
self.assert_no_active_block_jobs()
result = self.vm.qmp('block-commit', device='drive0', top_node='badfile', base_node='base')
self.assert_qmp(result, 'error/class', 'GenericError')
self.assert_qmp(result, 'error/desc', "Cannot find device= nor node_name=badfile")
self.assert_qmp(result, 'error/desc', "Cannot find device='' nor node-name='badfile'")
def test_base_node_invalid(self):
self.assert_no_active_block_jobs()
result = self.vm.qmp('block-commit', device='drive0', top_node='mid', base_node='badfile')
self.assert_qmp(result, 'error/class', 'GenericError')
self.assert_qmp(result, 'error/desc', "Cannot find device= nor node_name=badfile")
self.assert_qmp(result, 'error/desc', "Cannot find device='' nor node-name='badfile'")
def test_top_path_and_node(self):
self.assert_no_active_block_jobs()

View File

@ -61,13 +61,13 @@ QEMU X.Y.Z monitor - type 'help' for more information
(qemu) quit
Testing: -drive file=TEST_DIR/t.qcow2,node-name=123foo
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,node-name=123foo: Invalid node name
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,node-name=123foo: Invalid node-name: '123foo'
Testing: -drive file=TEST_DIR/t.qcow2,node-name=_foo
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,node-name=_foo: Invalid node name
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,node-name=_foo: Invalid node-name: '_foo'
Testing: -drive file=TEST_DIR/t.qcow2,node-name=foo#12
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,node-name=foo#12: Invalid node name
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,node-name=foo#12: Invalid node-name: 'foo#12'
=== Device without drive ===

View File

@ -140,7 +140,7 @@ Testing:
QMP_VERSION
{"return": {}}
{"error": {"class": "GenericError", "desc": "blkverify=on can only be set if there are exactly two files and vote-threshold is 2"}}
{"error": {"class": "GenericError", "desc": "Cannot find device=drive0-quorum nor node_name=drive0-quorum"}}
{"error": {"class": "GenericError", "desc": "Cannot find device='drive0-quorum' nor node-name='drive0-quorum'"}}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}}

View File

@ -24,7 +24,7 @@ Formatting 'TEST_DIR/1-snapshot-v0.qcow2', fmt=qcow2 cluster_size=65536 extended
{ 'execute': 'blockdev-snapshot-sync',
'arguments': { 'snapshot-file':'TEST_DIR/1-snapshot-v0.IMGFMT',
'format': 'IMGFMT' } }
{"error": {"class": "GenericError", "desc": "Cannot find device= nor node_name="}}
{"error": {"class": "GenericError", "desc": "Cannot find device='' nor node-name=''"}}
=== Invalid command - missing snapshot-file ===
@ -222,10 +222,10 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/
{ 'execute': 'blockdev-snapshot',
'arguments': { 'node': 'virtio0',
'overlay':'snap_14' } }
{"error": {"class": "GenericError", "desc": "Cannot find device=snap_14 nor node_name=snap_14"}}
{"error": {"class": "GenericError", "desc": "Cannot find device='snap_14' nor node-name='snap_14'"}}
{ 'execute': 'blockdev-snapshot',
'arguments': { 'node':'nodevice',
'overlay':'snap_13' }
}
{"error": {"class": "GenericError", "desc": "Cannot find device=nodevice nor node_name=nodevice"}}
{"error": {"class": "GenericError", "desc": "Cannot find device='nodevice' nor node-name='nodevice'"}}
*** done

View File

@ -143,9 +143,7 @@ run_qemu <<EOF
"arguments": {
"qom-type": "secret",
"id": "sec0",
"props": {
"data": "123456"
}
"data": "123456"
}
}
{ "execute": "blockdev-add",
@ -176,9 +174,7 @@ run_qemu <<EOF
"arguments": {
"qom-type": "secret",
"id": "sec0",
"props": {
"data": "123456"
}
"data": "123456"
}
}
{ "execute": "blockdev-add",

View File

@ -17,7 +17,7 @@ Testing: -drive driver=IMGFMT,id=disk,node-name=test-node,file=TEST_DIR/t.IMGFMT
QMP_VERSION
{"return": {}}
{"error": {"class": "GenericError", "desc": "node-name=disk is conflicting with a device id"}}
{"error": {"class": "GenericError", "desc": "Duplicate node name"}}
{"error": {"class": "GenericError", "desc": "Duplicate nodes with node-name='test-node'"}}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}}

View File

@ -67,10 +67,8 @@ run_qemu <<EOF
"arguments": {
"qom-type": "throttle-group",
"id": "group0",
"props": {
"limits" : {
"iops-total": 1000
}
"limits" : {
"iops-total": 1000
}
}
}
@ -96,10 +94,8 @@ run_qemu <<EOF
"arguments": {
"qom-type": "throttle-group",
"id": "group0",
"props" : {
"limits": {
"iops-total": 1000
}
"limits": {
"iops-total": 1000
}
}
}
@ -136,10 +132,8 @@ run_qemu <<EOF
"arguments": {
"qom-type": "throttle-group",
"id": "group0",
"props" : {
"limits": {
"iops-total": 1000
}
"limits": {
"iops-total": 1000
}
}
}

View File

@ -155,7 +155,7 @@ Format specific information:
{"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "qcow2", "file": "this doesn't exist", "size": 33554432}}}
{"return": {}}
Job failed: Cannot find device=this doesn't exist nor node_name=this doesn't exist
Job failed: Cannot find device='this doesn't exist' nor node-name='this doesn't exist'
{"execute": "job-dismiss", "arguments": {"id": "job0"}}
{"return": {}}

View File

@ -108,7 +108,7 @@ Format specific information:
{"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "luks", "file": "this doesn't exist", "size": 67108864}}}
{"return": {}}
Job failed: Cannot find device=this doesn't exist nor node_name=this doesn't exist
Job failed: Cannot find device='this doesn't exist' nor node-name='this doesn't exist'
{"execute": "job-dismiss", "arguments": {"id": "job0"}}
{"return": {}}

View File

@ -62,7 +62,7 @@ cluster_size: 1048576
{"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "vdi", "file": "this doesn't exist", "size": 33554432}}}
{"return": {}}
Job failed: Cannot find device=this doesn't exist nor node_name=this doesn't exist
Job failed: Cannot find device='this doesn't exist' nor node-name='this doesn't exist'
{"execute": "job-dismiss", "arguments": {"id": "job0"}}
{"return": {}}

View File

@ -52,7 +52,7 @@ virtual size: 32 MiB (33554432 bytes)
{"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "parallels", "file": "this doesn't exist", "size": 33554432}}}
{"return": {}}
Job failed: Cannot find device=this doesn't exist nor node_name=this doesn't exist
Job failed: Cannot find device='this doesn't exist' nor node-name='this doesn't exist'
{"execute": "job-dismiss", "arguments": {"id": "job0"}}
{"return": {}}

View File

@ -55,7 +55,7 @@ cluster_size: 268435456
{"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "vhdx", "file": "this doesn't exist", "size": 33554432}}}
{"return": {}}
Job failed: Cannot find device=this doesn't exist nor node_name=this doesn't exist
Job failed: Cannot find device='this doesn't exist' nor node-name='this doesn't exist'
{"execute": "job-dismiss", "arguments": {"id": "job0"}}
{"return": {}}

View File

@ -152,7 +152,7 @@ with iotests.VM() as vm, \
vm.launch()
ret = vm.qmp('object-add', qom_type='throttle-group', id='tg',
props={'x-bps-read': 4096})
limits={'bps-read': 4096})
assert ret['return'] == {}
ret = vm.qmp('blockdev-add',

View File

@ -53,7 +53,7 @@ exports available: 0
{"return": {}}
{"execute":"nbd-server-add",
"arguments":{"device":"nosuch"}}
{"error": {"class": "GenericError", "desc": "Cannot find device=nosuch nor node_name=nosuch"}}
{"error": {"class": "GenericError", "desc": "Cannot find device='nosuch' nor node-name='nosuch'"}}
{"execute":"nbd-server-add",
"arguments":{"device":"n"}}
{"error": {"class": "GenericError", "desc": "Block export id 'n' is already in use"}}
@ -154,7 +154,7 @@ exports available: 0
{"return": {}}
{"execute":"nbd-server-add",
"arguments":{"device":"nosuch"}}
{"error": {"class": "GenericError", "desc": "Cannot find device=nosuch nor node_name=nosuch"}}
{"error": {"class": "GenericError", "desc": "Cannot find device='nosuch' nor node-name='nosuch'"}}
{"execute":"nbd-server-add",
"arguments":{"device":"n"}}
{"error": {"class": "GenericError", "desc": "Block export id 'n' is already in use"}}

View File

@ -57,7 +57,7 @@ vm.add_args('-drive', 'id=src,file=' + disk)
vm.launch()
log(vm.qmp('object-add', qom_type='throttle-group', id='tg0',
props={ 'x-bps-total': size }))
limits={'bps-total': size}))
log(vm.qmp('blockdev-add',
**{ 'node-name': 'target',

View File

@ -85,7 +85,7 @@ Format specific information:
{"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "vmdk", "file": "this doesn't exist", "size": 33554432}}}
{"return": {}}
Job failed: Cannot find device=this doesn't exist nor node_name=this doesn't exist
Job failed: Cannot find device='this doesn't exist' nor node-name='this doesn't exist'
{"execute": "job-dismiss", "arguments": {"id": "job0"}}
{"return": {}}

View File

@ -140,8 +140,8 @@ class TestBlockdevReopen(iotests.QMPTestCase):
self.reopen(opts, {'file': 'hd0-file'})
# We cannot change any of these
self.reopen(opts, {'node-name': 'not-found'}, "Cannot find node named 'not-found'")
self.reopen(opts, {'node-name': ''}, "Cannot find node named ''")
self.reopen(opts, {'node-name': 'not-found'}, "Failed to find node with node-name='not-found'")
self.reopen(opts, {'node-name': ''}, "Failed to find node with node-name=''")
self.reopen(opts, {'node-name': None}, "Invalid parameter type for 'node-name', expected: string")
self.reopen(opts, {'driver': 'raw'}, "Cannot change the option 'driver'")
self.reopen(opts, {'driver': ''}, "Invalid parameter ''")
@ -158,7 +158,7 @@ class TestBlockdevReopen(iotests.QMPTestCase):
# node-name is optional in BlockdevOptions, but x-blockdev-reopen needs it
del opts['node-name']
self.reopen(opts, {}, "Node name not specified")
self.reopen(opts, {}, "node-name not specified")
# Check that nothing has changed
self.check_node_graph(original_graph)
@ -187,8 +187,8 @@ class TestBlockdevReopen(iotests.QMPTestCase):
self.reopen(opts, {'backing': backing_node_name})
# We can't use a non-existing or empty (non-NULL) node as the backing image
self.reopen(opts, {'backing': 'not-found'}, "Cannot find device= nor node_name=not-found")
self.reopen(opts, {'backing': ''}, "Cannot find device= nor node_name=")
self.reopen(opts, {'backing': 'not-found'}, "Cannot find device=\'\' nor node-name=\'not-found\'")
self.reopen(opts, {'backing': ''}, "Cannot find device=\'\' nor node-name=\'\'")
# We can reopen the image just fine if we specify the backing options
opts['backing'] = {'driver': iotests.imgfmt,
@ -644,12 +644,12 @@ class TestBlockdevReopen(iotests.QMPTestCase):
###### throttle ######
######################
opts = { 'qom-type': 'throttle-group', 'id': 'group0',
'props': { 'limits': { 'iops-total': 1000 } } }
'limits': { 'iops-total': 1000 } }
result = self.vm.qmp('object-add', conv_keys = False, **opts)
self.assert_qmp(result, 'return', {})
opts = { 'qom-type': 'throttle-group', 'id': 'group1',
'props': { 'limits': { 'iops-total': 2000 } } }
'limits': { 'iops-total': 2000 } }
result = self.vm.qmp('object-add', conv_keys = False, **opts)
self.assert_qmp(result, 'return', {})

View File

@ -18,7 +18,7 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 backing_file=TEST_DIR/t.
'filter-node-name': '1234'}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job0"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job0"}}
{"error": {"class": "GenericError", "desc": "Invalid node name"}}
{"error": {"class": "GenericError", "desc": "Invalid node-name: '1234'"}}
=== Send a write command to a drive opened in read-only mode (2)

View File

@ -103,9 +103,9 @@ def test_concurrent_finish(write_to_stream_node):
vm.qmp_log('object-add',
qom_type='throttle-group',
id='tg',
props={
'x-iops-write': 1,
'x-iops-write-max': 1
limits={
'iops-write': 1,
'iops-write-max': 1
})
vm.qmp_log('blockdev-add',

View File

@ -2,7 +2,7 @@ Running tests:
=== Commit and stream finish concurrently (letting stream write) ===
{"execute": "object-add", "arguments": {"id": "tg", "props": {"x-iops-write": 1, "x-iops-write-max": 1}, "qom-type": "throttle-group"}}
{"execute": "object-add", "arguments": {"id": "tg", "limits": {"iops-write": 1, "iops-write-max": 1}, "qom-type": "throttle-group"}}
{"return": {}}
{"execute": "blockdev-add", "arguments": {"backing": {"backing": {"backing": {"backing": {"driver": "raw", "file": {"driver": "file", "filename": "TEST_DIR/PID-node0.img"}, "node-name": "node0"}, "driver": "IMGFMT", "file": {"driver": "file", "filename": "TEST_DIR/PID-node1.img"}, "node-name": "node1"}, "driver": "IMGFMT", "file": {"driver": "file", "filename": "TEST_DIR/PID-node2.img"}, "node-name": "node2"}, "driver": "IMGFMT", "file": {"driver": "file", "filename": "TEST_DIR/PID-node3.img"}, "node-name": "node3"}, "driver": "IMGFMT", "file": {"driver": "throttle", "file": {"driver": "file", "filename": "TEST_DIR/PID-node4.img"}, "throttle-group": "tg"}, "node-name": "node4"}}
{"return": {}}
@ -18,7 +18,7 @@ Running tests:
=== Commit and stream finish concurrently (letting commit write) ===
{"execute": "object-add", "arguments": {"id": "tg", "props": {"x-iops-write": 1, "x-iops-write-max": 1}, "qom-type": "throttle-group"}}
{"execute": "object-add", "arguments": {"id": "tg", "limits": {"iops-write": 1, "iops-write-max": 1}, "qom-type": "throttle-group"}}
{"return": {}}
{"execute": "blockdev-add", "arguments": {"backing": {"backing": {"backing": {"backing": {"driver": "raw", "file": {"driver": "throttle", "file": {"driver": "file", "filename": "TEST_DIR/PID-node0.img"}, "throttle-group": "tg"}, "node-name": "node0"}, "driver": "IMGFMT", "file": {"driver": "file", "filename": "TEST_DIR/PID-node1.img"}, "node-name": "node1"}, "driver": "IMGFMT", "file": {"driver": "file", "filename": "TEST_DIR/PID-node2.img"}, "node-name": "node2"}, "driver": "IMGFMT", "file": {"driver": "file", "filename": "TEST_DIR/PID-node3.img"}, "node-name": "node3"}, "driver": "IMGFMT", "file": {"driver": "file", "filename": "TEST_DIR/PID-node4.img"}, "node-name": "node4"}}
{"return": {}}

View File

@ -97,3 +97,56 @@ vm.qmp_log('blockdev-add', **{
vm.qmp_log('blockdev-backup', sync='full', device='source', target='target')
vm.shutdown()
print('\n=== backup-top should be gone after job-finalize ===\n')
# Check that the backup-top node is gone after job-finalize.
#
# During finalization, the node becomes inactive and can no longer
# function. If it is still present, new parents might be attached, and
# there would be no meaningful way to handle their I/O requests.
vm = iotests.VM()
vm.launch()
vm.qmp_log('blockdev-add', **{
'node-name': 'source',
'driver': 'null-co',
})
vm.qmp_log('blockdev-add', **{
'node-name': 'target',
'driver': 'null-co',
})
vm.qmp_log('blockdev-backup',
job_id='backup',
device='source',
target='target',
sync='full',
filter_node_name='backup-filter',
auto_finalize=False,
auto_dismiss=False)
vm.event_wait('BLOCK_JOB_PENDING', 5.0)
# The backup-top filter should still be present prior to finalization
assert vm.node_info('backup-filter') is not None
vm.qmp_log('job-finalize', id='backup')
vm.event_wait('BLOCK_JOB_COMPLETED', 5.0)
# The filter should be gone now. Check that by trying to access it
# with qemu-io (which will most likely crash qemu if it is still
# there.).
vm.qmp_log('human-monitor-command',
command_line='qemu-io backup-filter "write 0 1M"')
# (Also, do an explicit check.)
assert vm.node_info('backup-filter') is None
vm.qmp_log('job-dismiss', id='backup')
vm.event_wait('JOB_STATUS_CHANGE', 5.0, {'data': {'status': 'null'}})
vm.shutdown()

View File

@ -6,3 +6,18 @@
{"return": {}}
{"execute": "blockdev-backup", "arguments": {"device": "source", "sync": "full", "target": "target"}}
{"error": {"class": "GenericError", "desc": "Cannot set permissions for backup-top filter: Conflicts with use by other as 'image', which uses 'write' on base"}}
=== backup-top should be gone after job-finalize ===
{"execute": "blockdev-add", "arguments": {"driver": "null-co", "node-name": "source"}}
{"return": {}}
{"execute": "blockdev-add", "arguments": {"driver": "null-co", "node-name": "target"}}
{"return": {}}
{"execute": "blockdev-backup", "arguments": {"auto-dismiss": false, "auto-finalize": false, "device": "source", "filter-node-name": "backup-filter", "job-id": "backup", "sync": "full", "target": "target"}}
{"return": {}}
{"execute": "job-finalize", "arguments": {"id": "backup"}}
{"return": {}}
{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io backup-filter \"write 0 1M\""}}
{"return": "Error: Cannot find device='' nor node-name='backup-filter'\r\n"}
{"execute": "job-dismiss", "arguments": {"id": "backup"}}
{"return": {}}

View File

@ -43,7 +43,7 @@ class Secret:
def to_qmp_object(self):
return { "qom_type" : "secret", "id": self.id(),
"props": { "data": self.secret() } }
"data": self.secret() }
################################################################################
class EncryptionSetupTestCase(iotests.QMPTestCase):

View File

@ -43,7 +43,7 @@ class Secret:
def to_qmp_object(self):
return { "qom_type" : "secret", "id": self.id(),
"props": { "data": self.secret() } }
"data": self.secret() }
################################################################################

View File

@ -22,7 +22,7 @@
import os
import random
import re
from typing import Dict, List, Optional, Union
from typing import Dict, List, Optional
import iotests
@ -30,7 +30,7 @@ import iotests
# pylint: disable=wrong-import-order
import qemu
BlockBitmapMapping = List[Dict[str, Union[str, List[Dict[str, str]]]]]
BlockBitmapMapping = List[Dict[str, object]]
mig_sock = os.path.join(iotests.sock_dir, 'mig_sock')
@ -189,8 +189,8 @@ class TestAliasMigration(TestDirtyBitmapMigration):
# Check for error message on the destination
if self.src_node_name != self.dst_node_name:
self.verify_dest_error(f"Cannot find "
f"device={self.src_node_name} nor "
f"node_name={self.src_node_name}")
f"device='{self.src_node_name}' nor "
f"node-name='{self.src_node_name}'")
else:
self.verify_dest_error(None)
@ -602,7 +602,8 @@ class TestCrossAliasMigration(TestDirtyBitmapMigration):
class TestAliasTransformMigration(TestDirtyBitmapMigration):
"""
Tests the 'transform' option which modifies bitmap persistence on migration.
Tests the 'transform' option which modifies bitmap persistence on
migration.
"""
src_node_name = 'node-a'
@ -674,7 +675,8 @@ class TestAliasTransformMigration(TestDirtyBitmapMigration):
bitmaps = self.vm_b.query_bitmaps()
for node in bitmaps:
bitmaps[node] = sorted(((bmap['name'], bmap['persistent']) for bmap in bitmaps[node]))
bitmaps[node] = sorted(((bmap['name'], bmap['persistent'])
for bmap in bitmaps[node]))
self.assertEqual(bitmaps,
{'node-a': [('bmap-a', True), ('bmap-b', False)],

View File

@ -17,6 +17,7 @@
#
import atexit
import bz2
from collections import OrderedDict
import faulthandler
import io
@ -24,6 +25,7 @@ import json
import logging
import os
import re
import shutil
import signal
import struct
import subprocess
@ -96,6 +98,14 @@ luks_default_secret_object = 'secret,id=keysec0,data=' + \
os.environ.get('IMGKEYSECRET', '')
luks_default_key_secret_opt = 'key-secret=keysec0'
sample_img_dir = os.environ['SAMPLE_IMG_DIR']
def unarchive_sample_image(sample, fname):
sample_fname = os.path.join(sample_img_dir, sample + '.bz2')
with bz2.open(sample_fname) as f_in, open(fname, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
def qemu_tool_pipe_and_status(tool: str, args: Sequence[str],
connect_stderr: bool = True) -> Tuple[str, int]:

View File

@ -0,0 +1,51 @@
#!/bin/bash
#
# Test parallels load bitmap
#
# Copyright (c) 2021 Virtuozzo International GmbH.
#
# 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/>.
#
CT=parallels-with-bitmap-ct
DIR=$PWD/parallels-with-bitmap-dir
IMG=$DIR/root.hds
XML=$DIR/DiskDescriptor.xml
TARGET=parallels-with-bitmap.bz2
rm -rf $DIR
prlctl create $CT --vmtype ct
prlctl set $CT --device-add hdd --image $DIR --recreate --size 2G
# cleanup the image
qemu-img create -f parallels $IMG 64G
# create bitmap
prlctl backup $CT
prlctl set $CT --device-del hdd1
prlctl destroy $CT
dev=$(ploop mount $XML | sed -n 's/^Adding delta dev=\(\/dev\/ploop[0-9]\+\).*/\1/p')
dd if=/dev/zero of=$dev bs=64K seek=5 count=2 oflag=direct
dd if=/dev/zero of=$dev bs=64K seek=30 count=1 oflag=direct
dd if=/dev/zero of=$dev bs=64K seek=10 count=3 oflag=direct
ploop umount $XML # bitmap name will be in the output
bzip2 -z $IMG
mv $IMG.bz2 $TARGET
rm -rf $DIR

View File

@ -0,0 +1,55 @@
#!/usr/bin/env python3
#
# Test parallels load bitmap
#
# Copyright (c) 2021 Virtuozzo International GmbH.
#
# 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/>.
#
import json
import iotests
from iotests import qemu_nbd_popen, qemu_img_pipe, log, file_path
iotests.script_initialize(supported_fmts=['parallels'])
nbd_sock = file_path('nbd-sock', base_dir=iotests.sock_dir)
disk = iotests.file_path('disk')
bitmap = 'e4f2eed0-37fe-4539-b50b-85d2e7fd235f'
nbd_opts = f'driver=nbd,server.type=unix,server.path={nbd_sock}' \
f',x-dirty-bitmap=qemu:dirty-bitmap:{bitmap}'
iotests.unarchive_sample_image('parallels-with-bitmap', disk)
with qemu_nbd_popen('--read-only', f'--socket={nbd_sock}',
f'--bitmap={bitmap}', '-f', iotests.imgfmt, disk):
out = qemu_img_pipe('map', '--output=json', '--image-opts', nbd_opts)
chunks = json.loads(out)
cluster = 64 * 1024
log('dirty clusters (cluster size is 64K):')
for c in chunks:
assert c['start'] % cluster == 0
assert c['length'] % cluster == 0
if c['data']:
continue
a = c['start'] // cluster
b = (c['start'] + c['length']) // cluster
if b - a > 1:
log(f'{a}-{b-1}')
else:
log(a)

View File

@ -0,0 +1,6 @@
Start NBD server
dirty clusters (cluster size is 64K):
5-6
10-12
30
Kill NBD server

View File

@ -74,6 +74,17 @@ QTestState *qtest_init_without_qmp_handshake(const char *extra_args);
*/
QTestState *qtest_init_with_serial(const char *extra_args, int *sock_fd);
/**
* qtest_kill_qemu:
* @s: #QTestState instance to operate on.
*
* Kill the QEMU process and wait for it to terminate. It is safe to call this
* function multiple times. Normally qtest_quit() is used instead because it
* also frees QTestState. Use qtest_kill_qemu() when you just want to kill QEMU
* and qtest_quit() will be called later.
*/
void qtest_kill_qemu(QTestState *s);
/**
* qtest_quit:
* @s: #QTestState instance to operate on.
@ -132,6 +143,14 @@ void qtest_qmp_send(QTestState *s, const char *fmt, ...)
void qtest_qmp_send_raw(QTestState *s, const char *fmt, ...)
GCC_FMT_ATTR(2, 3);
/**
* qtest_socket_server:
* @socket_path: the UNIX domain socket path
*
* Create and return a listen socket file descriptor, or abort on failure.
*/
int qtest_socket_server(const char *socket_path);
/**
* qtest_vqmp_fds:
* @s: #QTestState instance to operate on.
@ -630,8 +649,26 @@ void qtest_add_data_func_full(const char *str, void *data,
g_free(path); \
} while (0)
/**
* qtest_add_abrt_handler:
* @fn: Handler function
* @data: Argument that is passed to the handler
*
* Add a handler function that is invoked on SIGABRT. This can be used to
* terminate processes and perform other cleanup. The handler can be removed
* with qtest_remove_abrt_handler().
*/
void qtest_add_abrt_handler(GHookFunc fn, const void *data);
/**
* qtest_remove_abrt_handler:
* @data: Argument previously passed to qtest_add_abrt_handler()
*
* Remove an abrt handler that was previously added with
* qtest_add_abrt_handler().
*/
void qtest_remove_abrt_handler(void *data);
/**
* qtest_qmp_assert_success:
* @qts: QTestState instance to operate on

View File

@ -81,24 +81,8 @@ static void qtest_client_set_rx_handler(QTestState *s, QTestRecvFn recv);
static int init_socket(const char *socket_path)
{
struct sockaddr_un addr;
int sock;
int ret;
sock = socket(PF_UNIX, SOCK_STREAM, 0);
g_assert_cmpint(sock, !=, -1);
addr.sun_family = AF_UNIX;
snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socket_path);
int sock = qtest_socket_server(socket_path);
qemu_set_cloexec(sock);
do {
ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr));
} while (ret == -1 && errno == EINTR);
g_assert_cmpint(ret, !=, -1);
ret = listen(sock, 1);
g_assert_cmpint(ret, !=, -1);
return sock;
}
@ -149,7 +133,7 @@ void qtest_set_expected_status(QTestState *s, int status)
s->expected_status = status;
}
static void kill_qemu(QTestState *s)
void qtest_kill_qemu(QTestState *s)
{
pid_t pid = s->qemu_pid;
int wstatus;
@ -159,6 +143,7 @@ static void kill_qemu(QTestState *s)
kill(pid, SIGTERM);
TFR(pid = waitpid(s->qemu_pid, &s->wstatus, 0));
assert(pid == s->qemu_pid);
s->qemu_pid = -1;
}
/*
@ -185,7 +170,7 @@ static void kill_qemu(QTestState *s)
static void kill_qemu_hook_func(void *s)
{
kill_qemu(s);
qtest_kill_qemu(s);
}
static void sigabrt_handler(int signo)
@ -211,15 +196,30 @@ static void cleanup_sigabrt_handler(void)
sigaction(SIGABRT, &sigact_old, NULL);
}
static bool hook_list_is_empty(GHookList *hook_list)
{
GHook *hook = g_hook_first_valid(hook_list, TRUE);
if (!hook) {
return false;
}
g_hook_unref(hook_list, hook);
return true;
}
void qtest_add_abrt_handler(GHookFunc fn, const void *data)
{
GHook *hook;
/* Only install SIGABRT handler once */
if (!abrt_hooks.is_setup) {
g_hook_list_init(&abrt_hooks, sizeof(GHook));
}
setup_sigabrt_handler();
/* Only install SIGABRT handler once */
if (hook_list_is_empty(&abrt_hooks)) {
setup_sigabrt_handler();
}
hook = g_hook_alloc(&abrt_hooks);
hook->func = fn;
@ -228,6 +228,17 @@ void qtest_add_abrt_handler(GHookFunc fn, const void *data)
g_hook_prepend(&abrt_hooks, hook);
}
void qtest_remove_abrt_handler(void *data)
{
GHook *hook = g_hook_find_data(&abrt_hooks, TRUE, data);
g_hook_destroy_link(&abrt_hooks, hook);
/* Uninstall SIGABRT handler on last instance */
if (hook_list_is_empty(&abrt_hooks)) {
cleanup_sigabrt_handler();
}
}
static const char *qtest_qemu_binary(void)
{
const char *qemu_bin;
@ -384,12 +395,9 @@ QTestState *qtest_init_with_serial(const char *extra_args, int *sock_fd)
void qtest_quit(QTestState *s)
{
g_hook_destroy_link(&abrt_hooks, g_hook_find_data(&abrt_hooks, TRUE, s));
qtest_remove_abrt_handler(s);
/* Uninstall SIGABRT handler on last instance */
cleanup_sigabrt_handler();
kill_qemu(s);
qtest_kill_qemu(s);
close(s->fd);
close(s->qmp_fd);
g_string_free(s->rx, true);
@ -638,6 +646,28 @@ QDict *qtest_qmp_receive_dict(QTestState *s)
return qmp_fd_receive(s->qmp_fd);
}
int qtest_socket_server(const char *socket_path)
{
struct sockaddr_un addr;
int sock;
int ret;
sock = socket(PF_UNIX, SOCK_STREAM, 0);
g_assert_cmpint(sock, !=, -1);
addr.sun_family = AF_UNIX;
snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socket_path);
do {
ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr));
} while (ret == -1 && errno == EINTR);
g_assert_cmpint(ret, !=, -1);
ret = listen(sock, 1);
g_assert_cmpint(ret, !=, -1);
return sock;
}
/**
* Allow users to send a message without waiting for the reply,
* in the case that they choose to discard all replies up until