mirror of https://gitee.com/openkylin/qemu.git
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>
This commit is contained in:
commit
a557b00469
|
@ -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>
|
||||
|
|
8
block.c
8
block.c
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
13
blockdev.c
13
blockdev.c
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
10
blockjob.c
10
blockjob.c
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 ===
|
||||
|
|
|
@ -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"}}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"}}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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": {}}
|
||||
|
||||
|
|
|
@ -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": {}}
|
||||
|
||||
|
|
|
@ -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": {}}
|
||||
|
||||
|
|
|
@ -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": {}}
|
||||
|
||||
|
|
|
@ -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": {}}
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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"}}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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": {}}
|
||||
|
||||
|
|
|
@ -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', {})
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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": {}}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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": {}}
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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() }
|
||||
|
||||
################################################################################
|
||||
|
||||
|
|
|
@ -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)],
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#
|
||||
|
||||
import atexit
|
||||
import bz2
|
||||
from collections import OrderedDict
|
||||
import faulthandler
|
||||
import io
|
||||
|
@ -24,6 +25,7 @@
|
|||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import signal
|
||||
import struct
|
||||
import subprocess
|
||||
|
@ -96,6 +98,14 @@
|
|||
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]:
|
||||
|
|
Binary file not shown.
|
@ -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
|
|
@ -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)
|
|
@ -0,0 +1,6 @@
|
|||
Start NBD server
|
||||
dirty clusters (cluster size is 64K):
|
||||
5-6
|
||||
10-12
|
||||
30
|
||||
Kill NBD server
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue