Merge remote-tracking branch 'kwolf/for-anthony' into staging

* kwolf/for-anthony: (22 commits)
  scsi: Guard against buflen exceeding req->cmd.xfer in scsi_disk_emulate_command
  qcow: Use bdrv functions to replace file operation
  qcow: Return real error code in qcow_open
  block/vdi: Zero unused parts when allocating a new block (fix #919242)
  virtio-blk: add virtio_blk_handle_read trace event
  docs: describe live block operations
  block: add support for partial streaming
  add QERR_BASE_NOT_FOUND
  block: add bdrv_find_backing_image
  blockdev: make image streaming safe across hotplug
  qmp: add query-block-jobs
  qmp: add block_job_cancel command
  qmp: add block_job_set_speed command
  qmp: add block_stream command
  block: rate-limit streaming operations
  block: add image streaming block job
  block: add BlockJob interface for long-running operations
  block: make copy-on-read a per-request flag
  block: check bdrv_in_use() before blockdev operations
  coroutine: add co_sleep_ns() coroutine sleep function
  ...
This commit is contained in:
Anthony Liguori 2012-01-27 08:58:52 -06:00
commit 21fe5bc678
28 changed files with 1209 additions and 91 deletions

View File

@ -13,6 +13,7 @@ oslib-obj-$(CONFIG_POSIX) += oslib-posix.o qemu-thread-posix.o
#######################################################################
# coroutines
coroutine-obj-y = qemu-coroutine.o qemu-coroutine-lock.o qemu-coroutine-io.o
coroutine-obj-y += qemu-coroutine-sleep.o
ifeq ($(CONFIG_UCONTEXT_COROUTINE),y)
coroutine-obj-$(CONFIG_POSIX) += coroutine-ucontext.o
else
@ -34,6 +35,7 @@ block-nested-y += qcow2.o qcow2-refcount.o qcow2-cluster.o qcow2-snapshot.o qcow
block-nested-y += qed.o qed-gencb.o qed-l2-cache.o qed-table.o qed-cluster.o
block-nested-y += qed-check.o
block-nested-y += parallels.o nbd.o blkdebug.o sheepdog.o blkverify.o
block-nested-y += stream.o
block-nested-$(CONFIG_WIN32) += raw-win32.o
block-nested-$(CONFIG_POSIX) += raw-posix.o
block-nested-$(CONFIG_LIBISCSI) += iscsi.o

View File

@ -264,3 +264,56 @@ Example:
Note: If action is "reset", "shutdown", or "pause" the WATCHDOG event is
followed respectively by the RESET, SHUTDOWN, or STOP events.
BLOCK_JOB_COMPLETED
-------------------
Emitted when a block job has completed.
Data:
- "type": Job type ("stream" for image streaming, json-string)
- "device": Device name (json-string)
- "len": Maximum progress value (json-int)
- "offset": Current progress value (json-int)
On success this is equal to len.
On failure this is less than len.
- "speed": Rate limit, bytes per second (json-int)
- "error": Error message (json-string, optional)
Only present on failure. This field contains a human-readable
error message. There are no semantics other than that streaming
has failed and clients should not try to interpret the error
string.
Example:
{ "event": "BLOCK_JOB_COMPLETED",
"data": { "type": "stream", "device": "virtio-disk0",
"len": 10737418240, "offset": 10737418240,
"speed": 0 },
"timestamp": { "seconds": 1267061043, "microseconds": 959568 } }
BLOCK_JOB_CANCELLED
-------------------
Emitted when a block job has been cancelled.
Data:
- "type": Job type ("stream" for image streaming, json-string)
- "device": Device name (json-string)
- "len": Maximum progress value (json-int)
- "offset": Current progress value (json-int)
On success this is equal to len.
On failure this is less than len.
- "speed": Rate limit, bytes per second (json-int)
Example:
{ "event": "BLOCK_JOB_CANCELLED",
"data": { "type": "stream", "device": "virtio-disk0",
"len": 10737418240, "offset": 134217728,
"speed": 0 },
"timestamp": { "seconds": 1267061043, "microseconds": 959568 } }

119
block.c
View File

@ -48,6 +48,10 @@
#define NOT_DONE 0x7fffffff /* used while emulated sync operation in progress */
typedef enum {
BDRV_REQ_COPY_ON_READ = 0x1,
} BdrvRequestFlags;
static void bdrv_dev_change_media_cb(BlockDriverState *bs, bool load);
static BlockDriverAIOCB *bdrv_aio_readv_em(BlockDriverState *bs,
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
@ -62,7 +66,8 @@ static int coroutine_fn bdrv_co_writev_em(BlockDriverState *bs,
int64_t sector_num, int nb_sectors,
QEMUIOVector *iov);
static int coroutine_fn bdrv_co_do_readv(BlockDriverState *bs,
int64_t sector_num, int nb_sectors, QEMUIOVector *qiov);
int64_t sector_num, int nb_sectors, QEMUIOVector *qiov,
BdrvRequestFlags flags);
static int coroutine_fn bdrv_co_do_writev(BlockDriverState *bs,
int64_t sector_num, int nb_sectors, QEMUIOVector *qiov);
static BlockDriverAIOCB *bdrv_co_aio_rw_vector(BlockDriverState *bs,
@ -1020,6 +1025,10 @@ int bdrv_commit(BlockDriverState *bs)
return -EACCES;
}
if (bdrv_in_use(bs) || bdrv_in_use(bs->backing_hd)) {
return -EBUSY;
}
backing_drv = bs->backing_hd->drv;
ro = bs->backing_hd->read_only;
strncpy(filename, bs->backing_hd->filename, sizeof(filename));
@ -1288,7 +1297,7 @@ static void coroutine_fn bdrv_rw_co_entry(void *opaque)
if (!rwco->is_write) {
rwco->ret = bdrv_co_do_readv(rwco->bs, rwco->sector_num,
rwco->nb_sectors, rwco->qiov);
rwco->nb_sectors, rwco->qiov, 0);
} else {
rwco->ret = bdrv_co_do_writev(rwco->bs, rwco->sector_num,
rwco->nb_sectors, rwco->qiov);
@ -1496,7 +1505,7 @@ int bdrv_pwrite_sync(BlockDriverState *bs, int64_t offset,
return 0;
}
static int coroutine_fn bdrv_co_copy_on_readv(BlockDriverState *bs,
static int coroutine_fn bdrv_co_do_copy_on_readv(BlockDriverState *bs,
int64_t sector_num, int nb_sectors, QEMUIOVector *qiov)
{
/* Perform I/O through a temporary buffer so that users who scribble over
@ -1519,8 +1528,8 @@ static int coroutine_fn bdrv_co_copy_on_readv(BlockDriverState *bs,
round_to_clusters(bs, sector_num, nb_sectors,
&cluster_sector_num, &cluster_nb_sectors);
trace_bdrv_co_copy_on_readv(bs, sector_num, nb_sectors,
cluster_sector_num, cluster_nb_sectors);
trace_bdrv_co_do_copy_on_readv(bs, sector_num, nb_sectors,
cluster_sector_num, cluster_nb_sectors);
iov.iov_len = cluster_nb_sectors * BDRV_SECTOR_SIZE;
iov.iov_base = bounce_buffer = qemu_blockalign(bs, iov.iov_len);
@ -1555,7 +1564,8 @@ err:
* Handle a read request in coroutine context
*/
static int coroutine_fn bdrv_co_do_readv(BlockDriverState *bs,
int64_t sector_num, int nb_sectors, QEMUIOVector *qiov)
int64_t sector_num, int nb_sectors, QEMUIOVector *qiov,
BdrvRequestFlags flags)
{
BlockDriver *drv = bs->drv;
BdrvTrackedRequest req;
@ -1574,12 +1584,19 @@ static int coroutine_fn bdrv_co_do_readv(BlockDriverState *bs,
}
if (bs->copy_on_read) {
flags |= BDRV_REQ_COPY_ON_READ;
}
if (flags & BDRV_REQ_COPY_ON_READ) {
bs->copy_on_read_in_flight++;
}
if (bs->copy_on_read_in_flight) {
wait_for_overlapping_requests(bs, sector_num, nb_sectors);
}
tracked_request_begin(&req, bs, sector_num, nb_sectors, false);
if (bs->copy_on_read) {
if (flags & BDRV_REQ_COPY_ON_READ) {
int pnum;
ret = bdrv_co_is_allocated(bs, sector_num, nb_sectors, &pnum);
@ -1588,7 +1605,7 @@ static int coroutine_fn bdrv_co_do_readv(BlockDriverState *bs,
}
if (!ret || pnum != nb_sectors) {
ret = bdrv_co_copy_on_readv(bs, sector_num, nb_sectors, qiov);
ret = bdrv_co_do_copy_on_readv(bs, sector_num, nb_sectors, qiov);
goto out;
}
}
@ -1597,6 +1614,11 @@ static int coroutine_fn bdrv_co_do_readv(BlockDriverState *bs,
out:
tracked_request_end(&req);
if (flags & BDRV_REQ_COPY_ON_READ) {
bs->copy_on_read_in_flight--;
}
return ret;
}
@ -1605,7 +1627,16 @@ int coroutine_fn bdrv_co_readv(BlockDriverState *bs, int64_t sector_num,
{
trace_bdrv_co_readv(bs, sector_num, nb_sectors);
return bdrv_co_do_readv(bs, sector_num, nb_sectors, qiov);
return bdrv_co_do_readv(bs, sector_num, nb_sectors, qiov, 0);
}
int coroutine_fn bdrv_co_copy_on_readv(BlockDriverState *bs,
int64_t sector_num, int nb_sectors, QEMUIOVector *qiov)
{
trace_bdrv_co_copy_on_readv(bs, sector_num, nb_sectors);
return bdrv_co_do_readv(bs, sector_num, nb_sectors, qiov,
BDRV_REQ_COPY_ON_READ);
}
/*
@ -1633,7 +1664,7 @@ static int coroutine_fn bdrv_co_do_writev(BlockDriverState *bs,
bdrv_io_limits_intercept(bs, true, nb_sectors);
}
if (bs->copy_on_read) {
if (bs->copy_on_read_in_flight) {
wait_for_overlapping_requests(bs, sector_num, nb_sectors);
}
@ -2564,6 +2595,24 @@ int bdrv_snapshot_load_tmp(BlockDriverState *bs,
return -ENOTSUP;
}
BlockDriverState *bdrv_find_backing_image(BlockDriverState *bs,
const char *backing_file)
{
if (!bs->drv) {
return NULL;
}
if (bs->backing_hd) {
if (strcmp(bs->backing_file, backing_file) == 0) {
return bs->backing_hd;
} else {
return bdrv_find_backing_image(bs->backing_hd, backing_file);
}
}
return NULL;
}
#define NB_SUFFIXES 4
char *get_human_readable_size(char *buf, int buf_size, int64_t size)
@ -3140,7 +3189,7 @@ static void coroutine_fn bdrv_co_do_rw(void *opaque)
if (!acb->is_write) {
acb->req.error = bdrv_co_do_readv(bs, acb->req.sector,
acb->req.nb_sectors, acb->req.qiov);
acb->req.nb_sectors, acb->req.qiov, 0);
} else {
acb->req.error = bdrv_co_do_writev(bs, acb->req.sector,
acb->req.nb_sectors, acb->req.qiov);
@ -3827,3 +3876,51 @@ out:
return ret;
}
void *block_job_create(const BlockJobType *job_type, BlockDriverState *bs,
BlockDriverCompletionFunc *cb, void *opaque)
{
BlockJob *job;
if (bs->job || bdrv_in_use(bs)) {
return NULL;
}
bdrv_set_in_use(bs, 1);
job = g_malloc0(job_type->instance_size);
job->job_type = job_type;
job->bs = bs;
job->cb = cb;
job->opaque = opaque;
bs->job = job;
return job;
}
void block_job_complete(BlockJob *job, int ret)
{
BlockDriverState *bs = job->bs;
assert(bs->job == job);
job->cb(job->opaque, ret);
bs->job = NULL;
g_free(job);
bdrv_set_in_use(bs, 0);
}
int block_job_set_speed(BlockJob *job, int64_t value)
{
if (!job->job_type->set_speed) {
return -ENOTSUP;
}
return job->job_type->set_speed(job, value);
}
void block_job_cancel(BlockJob *job)
{
job->cancelled = true;
}
bool block_job_is_cancelled(BlockJob *job)
{
return job->cancelled;
}

View File

@ -142,10 +142,14 @@ int bdrv_pwrite_sync(BlockDriverState *bs, int64_t offset,
const void *buf, int count);
int coroutine_fn bdrv_co_readv(BlockDriverState *bs, int64_t sector_num,
int nb_sectors, QEMUIOVector *qiov);
int coroutine_fn bdrv_co_copy_on_readv(BlockDriverState *bs,
int64_t sector_num, int nb_sectors, QEMUIOVector *qiov);
int coroutine_fn bdrv_co_writev(BlockDriverState *bs, int64_t sector_num,
int nb_sectors, QEMUIOVector *qiov);
int coroutine_fn bdrv_co_is_allocated(BlockDriverState *bs, int64_t sector_num,
int nb_sectors, int *pnum);
BlockDriverState *bdrv_find_backing_image(BlockDriverState *bs,
const char *backing_file);
int bdrv_truncate(BlockDriverState *bs, int64_t offset);
int64_t bdrv_getlength(BlockDriverState *bs);
int64_t bdrv_get_allocated_file_size(BlockDriverState *bs);

View File

@ -292,10 +292,10 @@ static int blkdebug_open(BlockDriverState *bs, const char *filename, int flags)
return -EINVAL;
}
config = strdup(filename);
config = g_strdup(filename);
config[c - filename] = '\0';
ret = read_config(s, config);
free(config);
g_free(config);
if (ret < 0) {
return ret;
}

View File

@ -87,10 +87,10 @@ static int blkverify_open(BlockDriverState *bs, const char *filename, int flags)
return -EINVAL;
}
raw = strdup(filename);
raw = g_strdup(filename);
raw[c - filename] = '\0';
ret = bdrv_file_open(&bs->file, raw, flags);
free(raw);
g_free(raw);
if (ret < 0) {
return ret;
}

View File

@ -95,11 +95,13 @@ static int qcow_probe(const uint8_t *buf, int buf_size, const char *filename)
static int qcow_open(BlockDriverState *bs, int flags)
{
BDRVQcowState *s = bs->opaque;
int len, i, shift;
int len, i, shift, ret;
QCowHeader header;
if (bdrv_pread(bs->file, 0, &header, sizeof(header)) != sizeof(header))
ret = bdrv_pread(bs->file, 0, &header, sizeof(header));
if (ret < 0) {
goto fail;
}
be32_to_cpus(&header.magic);
be32_to_cpus(&header.version);
be64_to_cpus(&header.backing_file_offset);
@ -109,15 +111,31 @@ static int qcow_open(BlockDriverState *bs, int flags)
be32_to_cpus(&header.crypt_method);
be64_to_cpus(&header.l1_table_offset);
if (header.magic != QCOW_MAGIC || header.version != QCOW_VERSION)
if (header.magic != QCOW_MAGIC) {
ret = -EINVAL;
goto fail;
if (header.size <= 1 || header.cluster_bits < 9)
}
if (header.version != QCOW_VERSION) {
char version[64];
snprintf(version, sizeof(version), "QCOW version %d", header.version);
qerror_report(QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,
bs->device_name, "qcow", version);
ret = -ENOTSUP;
goto fail;
if (header.crypt_method > QCOW_CRYPT_AES)
}
if (header.size <= 1 || header.cluster_bits < 9) {
ret = -EINVAL;
goto fail;
}
if (header.crypt_method > QCOW_CRYPT_AES) {
ret = -EINVAL;
goto fail;
}
s->crypt_method_header = header.crypt_method;
if (s->crypt_method_header)
if (s->crypt_method_header) {
bs->encrypted = 1;
}
s->cluster_bits = header.cluster_bits;
s->cluster_size = 1 << s->cluster_bits;
s->cluster_sectors = 1 << (s->cluster_bits - 9);
@ -132,33 +150,33 @@ static int qcow_open(BlockDriverState *bs, int flags)
s->l1_table_offset = header.l1_table_offset;
s->l1_table = g_malloc(s->l1_size * sizeof(uint64_t));
if (!s->l1_table)
goto fail;
if (bdrv_pread(bs->file, s->l1_table_offset, s->l1_table, s->l1_size * sizeof(uint64_t)) !=
s->l1_size * sizeof(uint64_t))
ret = bdrv_pread(bs->file, s->l1_table_offset, s->l1_table,
s->l1_size * sizeof(uint64_t));
if (ret < 0) {
goto fail;
}
for(i = 0;i < s->l1_size; i++) {
be64_to_cpus(&s->l1_table[i]);
}
/* alloc L2 cache */
s->l2_cache = g_malloc(s->l2_size * L2_CACHE_SIZE * sizeof(uint64_t));
if (!s->l2_cache)
goto fail;
s->cluster_cache = g_malloc(s->cluster_size);
if (!s->cluster_cache)
goto fail;
s->cluster_data = g_malloc(s->cluster_size);
if (!s->cluster_data)
goto fail;
s->cluster_cache_offset = -1;
/* read the backing file name */
if (header.backing_file_offset != 0) {
len = header.backing_file_size;
if (len > 1023)
if (len > 1023) {
len = 1023;
if (bdrv_pread(bs->file, header.backing_file_offset, bs->backing_file, len) != len)
}
ret = bdrv_pread(bs->file, header.backing_file_offset,
bs->backing_file, len);
if (ret < 0) {
goto fail;
}
bs->backing_file[len] = '\0';
}
@ -176,7 +194,7 @@ static int qcow_open(BlockDriverState *bs, int flags)
g_free(s->l2_cache);
g_free(s->cluster_cache);
g_free(s->cluster_data);
return -1;
return ret;
}
static int qcow_set_key(BlockDriverState *bs, const char *key)
@ -626,13 +644,14 @@ static void qcow_close(BlockDriverState *bs)
static int qcow_create(const char *filename, QEMUOptionParameter *options)
{
int fd, header_size, backing_filename_len, l1_size, i, shift;
int header_size, backing_filename_len, l1_size, shift, i;
QCowHeader header;
uint64_t tmp;
uint8_t *tmp;
int64_t total_size = 0;
const char *backing_file = NULL;
int flags = 0;
int ret;
BlockDriverState *qcow_bs;
/* Read out options */
while (options && options->name) {
@ -646,9 +665,21 @@ static int qcow_create(const char *filename, QEMUOptionParameter *options)
options++;
}
fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
if (fd < 0)
return -errno;
ret = bdrv_create_file(filename, options);
if (ret < 0) {
return ret;
}
ret = bdrv_file_open(&qcow_bs, filename, BDRV_O_RDWR);
if (ret < 0) {
return ret;
}
ret = bdrv_truncate(qcow_bs, 0);
if (ret < 0) {
goto exit;
}
memset(&header, 0, sizeof(header));
header.magic = cpu_to_be32(QCOW_MAGIC);
header.version = cpu_to_be32(QCOW_VERSION);
@ -684,33 +715,34 @@ static int qcow_create(const char *filename, QEMUOptionParameter *options)
}
/* write all the data */
ret = qemu_write_full(fd, &header, sizeof(header));
ret = bdrv_pwrite(qcow_bs, 0, &header, sizeof(header));
if (ret != sizeof(header)) {
ret = -errno;
goto exit;
}
if (backing_file) {
ret = qemu_write_full(fd, backing_file, backing_filename_len);
ret = bdrv_pwrite(qcow_bs, sizeof(header),
backing_file, backing_filename_len);
if (ret != backing_filename_len) {
ret = -errno;
goto exit;
}
}
lseek(fd, header_size, SEEK_SET);
tmp = 0;
for(i = 0;i < l1_size; i++) {
ret = qemu_write_full(fd, &tmp, sizeof(tmp));
if (ret != sizeof(tmp)) {
ret = -errno;
goto exit;
}
}
tmp = g_malloc0(BDRV_SECTOR_SIZE);
for (i = 0; i < ((sizeof(uint64_t)*l1_size + BDRV_SECTOR_SIZE - 1)/
BDRV_SECTOR_SIZE); i++) {
ret = bdrv_pwrite(qcow_bs, header_size +
BDRV_SECTOR_SIZE*i, tmp, BDRV_SECTOR_SIZE);
if (ret != BDRV_SECTOR_SIZE) {
g_free(tmp);
goto exit;
}
}
g_free(tmp);
ret = 0;
exit:
close(fd);
bdrv_delete(qcow_bs);
return ret;
}

View File

@ -789,6 +789,26 @@ static int qemu_rbd_snap_create(BlockDriverState *bs,
return 0;
}
static int qemu_rbd_snap_remove(BlockDriverState *bs,
const char *snapshot_name)
{
BDRVRBDState *s = bs->opaque;
int r;
r = rbd_snap_remove(s->image, snapshot_name);
return r;
}
static int qemu_rbd_snap_rollback(BlockDriverState *bs,
const char *snapshot_name)
{
BDRVRBDState *s = bs->opaque;
int r;
r = rbd_snap_rollback(s->image, snapshot_name);
return r;
}
static int qemu_rbd_snap_list(BlockDriverState *bs,
QEMUSnapshotInfo **psn_tab)
{
@ -862,7 +882,9 @@ static BlockDriver bdrv_rbd = {
.bdrv_co_flush_to_disk = qemu_rbd_co_flush,
.bdrv_snapshot_create = qemu_rbd_snap_create,
.bdrv_snapshot_delete = qemu_rbd_snap_remove,
.bdrv_snapshot_list = qemu_rbd_snap_list,
.bdrv_snapshot_goto = qemu_rbd_snap_rollback,
};
static void bdrv_rbd_init(void)

269
block/stream.c Normal file
View File

@ -0,0 +1,269 @@
/*
* Image streaming
*
* Copyright IBM, Corp. 2011
*
* Authors:
* Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
* See the COPYING.LIB file in the top-level directory.
*
*/
#include "trace.h"
#include "block_int.h"
enum {
/*
* Size of data buffer for populating the image file. This should be large
* enough to process multiple clusters in a single call, so that populating
* contiguous regions of the image is efficient.
*/
STREAM_BUFFER_SIZE = 512 * 1024, /* in bytes */
};
#define SLICE_TIME 100000000ULL /* ns */
typedef struct {
int64_t next_slice_time;
uint64_t slice_quota;
uint64_t dispatched;
} RateLimit;
static int64_t ratelimit_calculate_delay(RateLimit *limit, uint64_t n)
{
int64_t delay_ns = 0;
int64_t now = qemu_get_clock_ns(rt_clock);
if (limit->next_slice_time < now) {
limit->next_slice_time = now + SLICE_TIME;
limit->dispatched = 0;
}
if (limit->dispatched + n > limit->slice_quota) {
delay_ns = limit->next_slice_time - now;
} else {
limit->dispatched += n;
}
return delay_ns;
}
static void ratelimit_set_speed(RateLimit *limit, uint64_t speed)
{
limit->slice_quota = speed / (1000000000ULL / SLICE_TIME);
}
typedef struct StreamBlockJob {
BlockJob common;
RateLimit limit;
BlockDriverState *base;
char backing_file_id[1024];
} StreamBlockJob;
static int coroutine_fn stream_populate(BlockDriverState *bs,
int64_t sector_num, int nb_sectors,
void *buf)
{
struct iovec iov = {
.iov_base = buf,
.iov_len = nb_sectors * BDRV_SECTOR_SIZE,
};
QEMUIOVector qiov;
qemu_iovec_init_external(&qiov, &iov, 1);
/* Copy-on-read the unallocated clusters */
return bdrv_co_copy_on_readv(bs, sector_num, nb_sectors, &qiov);
}
/*
* Given an image chain: [BASE] -> [INTER1] -> [INTER2] -> [TOP]
*
* Return true if the given sector is allocated in top.
* Return false if the given sector is allocated in intermediate images.
* Return true otherwise.
*
* 'pnum' is set to the number of sectors (including and immediately following
* the specified sector) that are known to be in the same
* allocated/unallocated state.
*
*/
static int coroutine_fn is_allocated_base(BlockDriverState *top,
BlockDriverState *base,
int64_t sector_num,
int nb_sectors, int *pnum)
{
BlockDriverState *intermediate;
int ret, n;
ret = bdrv_co_is_allocated(top, sector_num, nb_sectors, &n);
if (ret) {
*pnum = n;
return ret;
}
/*
* Is the unallocated chunk [sector_num, n] also
* unallocated between base and top?
*/
intermediate = top->backing_hd;
while (intermediate) {
int pnum_inter;
/* reached base */
if (intermediate == base) {
*pnum = n;
return 1;
}
ret = bdrv_co_is_allocated(intermediate, sector_num, nb_sectors,
&pnum_inter);
if (ret < 0) {
return ret;
} else if (ret) {
*pnum = pnum_inter;
return 0;
}
/*
* [sector_num, nb_sectors] is unallocated on top but intermediate
* might have
*
* [sector_num+x, nr_sectors] allocated.
*/
if (n > pnum_inter) {
n = pnum_inter;
}
intermediate = intermediate->backing_hd;
}
return 1;
}
static void coroutine_fn stream_run(void *opaque)
{
StreamBlockJob *s = opaque;
BlockDriverState *bs = s->common.bs;
BlockDriverState *base = s->base;
int64_t sector_num, end;
int ret = 0;
int n;
void *buf;
s->common.len = bdrv_getlength(bs);
if (s->common.len < 0) {
block_job_complete(&s->common, s->common.len);
return;
}
end = s->common.len >> BDRV_SECTOR_BITS;
buf = qemu_blockalign(bs, STREAM_BUFFER_SIZE);
/* Turn on copy-on-read for the whole block device so that guest read
* requests help us make progress. Only do this when copying the entire
* backing chain since the copy-on-read operation does not take base into
* account.
*/
if (!base) {
bdrv_enable_copy_on_read(bs);
}
for (sector_num = 0; sector_num < end; sector_num += n) {
retry:
if (block_job_is_cancelled(&s->common)) {
break;
}
if (base) {
ret = is_allocated_base(bs, base, sector_num,
STREAM_BUFFER_SIZE / BDRV_SECTOR_SIZE, &n);
} else {
ret = bdrv_co_is_allocated(bs, sector_num,
STREAM_BUFFER_SIZE / BDRV_SECTOR_SIZE,
&n);
}
trace_stream_one_iteration(s, sector_num, n, ret);
if (ret == 0) {
if (s->common.speed) {
uint64_t delay_ns = ratelimit_calculate_delay(&s->limit, n);
if (delay_ns > 0) {
co_sleep_ns(rt_clock, delay_ns);
/* Recheck cancellation and that sectors are unallocated */
goto retry;
}
}
ret = stream_populate(bs, sector_num, n, buf);
}
if (ret < 0) {
break;
}
ret = 0;
/* Publish progress */
s->common.offset += n * BDRV_SECTOR_SIZE;
/* Note that even when no rate limit is applied we need to yield
* with no pending I/O here so that qemu_aio_flush() returns.
*/
co_sleep_ns(rt_clock, 0);
}
if (!base) {
bdrv_disable_copy_on_read(bs);
}
if (sector_num == end && ret == 0) {
const char *base_id = NULL;
if (base) {
base_id = s->backing_file_id;
}
ret = bdrv_change_backing_file(bs, base_id, NULL);
}
qemu_vfree(buf);
block_job_complete(&s->common, ret);
}
static int stream_set_speed(BlockJob *job, int64_t value)
{
StreamBlockJob *s = container_of(job, StreamBlockJob, common);
if (value < 0) {
return -EINVAL;
}
job->speed = value;
ratelimit_set_speed(&s->limit, value / BDRV_SECTOR_SIZE);
return 0;
}
static BlockJobType stream_job_type = {
.instance_size = sizeof(StreamBlockJob),
.job_type = "stream",
.set_speed = stream_set_speed,
};
int stream_start(BlockDriverState *bs, BlockDriverState *base,
const char *base_id, BlockDriverCompletionFunc *cb,
void *opaque)
{
StreamBlockJob *s;
Coroutine *co;
s = block_job_create(&stream_job_type, bs, cb, opaque);
if (!s) {
return -EBUSY; /* bs must already be in use */
}
s->base = base;
if (base_id) {
pstrcpy(s->backing_file_id, sizeof(s->backing_file_id), base_id);
}
co = qemu_coroutine_create(stream_run);
trace_stream_start(bs, base, s, co, opaque);
qemu_coroutine_enter(co, s);
return 0;
}

View File

@ -1,7 +1,7 @@
/*
* Block driver for the Virtual Disk Image (VDI) format
*
* Copyright (c) 2009 Stefan Weil
* Copyright (c) 2009, 2012 Stefan Weil
*
* 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
@ -756,15 +756,19 @@ static void vdi_aio_write_cb(void *opaque, int ret)
(uint64_t)bmap_entry * s->block_sectors;
block = acb->block_buffer;
if (block == NULL) {
block = g_malloc0(s->block_size);
block = g_malloc(s->block_size);
acb->block_buffer = block;
acb->bmap_first = block_index;
assert(!acb->header_modified);
acb->header_modified = 1;
}
acb->bmap_last = block_index;
/* Copy data to be written to new block and zero unused parts. */
memset(block, 0, sector_in_block * SECTOR_SIZE);
memcpy(block + sector_in_block * SECTOR_SIZE,
acb->buf, n_sectors * SECTOR_SIZE);
memset(block + (sector_in_block + n_sectors) * SECTOR_SIZE, 0,
(s->block_sectors - n_sectors - sector_in_block) * SECTOR_SIZE);
acb->hd_iov.iov_base = (void *)block;
acb->hd_iov.iov_len = s->block_size;
qemu_iovec_init_external(&acb->hd_qiov, &acb->hd_iov, 1);

View File

@ -69,6 +69,36 @@ typedef struct BlockIOBaseValue {
uint64_t ios[2];
} BlockIOBaseValue;
typedef void BlockJobCancelFunc(void *opaque);
typedef struct BlockJob BlockJob;
typedef struct BlockJobType {
/** Derived BlockJob struct size */
size_t instance_size;
/** String describing the operation, part of query-block-jobs QMP API */
const char *job_type;
/** Optional callback for job types that support setting a speed limit */
int (*set_speed)(BlockJob *job, int64_t value);
} BlockJobType;
/**
* Long-running operation on a BlockDriverState
*/
struct BlockJob {
const BlockJobType *job_type;
BlockDriverState *bs;
bool cancelled;
/* These fields are published by the query-block-jobs QMP API */
int64_t offset;
int64_t len;
int64_t speed;
BlockDriverCompletionFunc *cb;
void *opaque;
};
struct BlockDriver {
const char *format_name;
int instance_size;
@ -218,6 +248,9 @@ struct BlockDriverState {
BlockDriverState *backing_hd;
BlockDriverState *file;
/* number of in-flight copy-on-read requests */
unsigned int copy_on_read_in_flight;
/* async read/write emulation */
void *sync_aiocb;
@ -261,6 +294,9 @@ struct BlockDriverState {
void *private;
QLIST_HEAD(, BdrvTrackedRequest) tracked_requests;
/* long-running background operation */
BlockJob *job;
};
struct BlockDriverAIOCB {
@ -284,4 +320,15 @@ void bdrv_set_io_limits(BlockDriverState *bs,
int is_windows_drive(const char *filename);
#endif
void *block_job_create(const BlockJobType *job_type, BlockDriverState *bs,
BlockDriverCompletionFunc *cb, void *opaque);
void block_job_complete(BlockJob *job, int ret);
int block_job_set_speed(BlockJob *job, int64_t value);
void block_job_cancel(BlockJob *job);
bool block_job_is_cancelled(BlockJob *job);
int stream_start(BlockDriverState *bs, BlockDriverState *base,
const char *base_id, BlockDriverCompletionFunc *cb,
void *opaque);
#endif /* BLOCK_INT_H */

View File

@ -13,9 +13,11 @@
#include "qerror.h"
#include "qemu-option.h"
#include "qemu-config.h"
#include "qemu-objects.h"
#include "sysemu.h"
#include "block_int.h"
#include "qmp-commands.h"
#include "trace.h"
static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives);
@ -200,6 +202,37 @@ void drive_get_ref(DriveInfo *dinfo)
dinfo->refcount++;
}
typedef struct {
QEMUBH *bh;
DriveInfo *dinfo;
} DrivePutRefBH;
static void drive_put_ref_bh(void *opaque)
{
DrivePutRefBH *s = opaque;
drive_put_ref(s->dinfo);
qemu_bh_delete(s->bh);
g_free(s);
}
/*
* Release a drive reference in a BH
*
* It is not possible to use drive_put_ref() from a callback function when the
* callers still need the drive. In such cases we schedule a BH to release the
* reference.
*/
static void drive_put_ref_bh_schedule(DriveInfo *dinfo)
{
DrivePutRefBH *s;
s = g_new(DrivePutRefBH, 1);
s->bh = qemu_bh_new(drive_put_ref_bh, s);
s->dinfo = dinfo;
qemu_bh_schedule(s->bh);
}
static int parse_block_error_action(const char *buf, int is_read)
{
if (!strcmp(buf, "ignore")) {
@ -592,12 +625,18 @@ void do_commit(Monitor *mon, const QDict *qdict)
if (!strcmp(device, "all")) {
bdrv_commit_all();
} else {
int ret;
bs = bdrv_find(device);
if (!bs) {
qerror_report(QERR_DEVICE_NOT_FOUND, device);
return;
}
bdrv_commit(bs);
ret = bdrv_commit(bs);
if (ret == -EBUSY) {
qerror_report(QERR_DEVICE_IN_USE, device);
return;
}
}
}
@ -616,6 +655,10 @@ void qmp_blockdev_snapshot_sync(const char *device, const char *snapshot_file,
error_set(errp, QERR_DEVICE_NOT_FOUND, device);
return;
}
if (bdrv_in_use(bs)) {
error_set(errp, QERR_DEVICE_IN_USE, device);
return;
}
pstrcpy(old_filename, sizeof(old_filename), bs->filename);
@ -667,6 +710,10 @@ void qmp_blockdev_snapshot_sync(const char *device, const char *snapshot_file,
static void eject_device(BlockDriverState *bs, int force, Error **errp)
{
if (bdrv_in_use(bs)) {
error_set(errp, QERR_DEVICE_IN_USE, bdrv_get_device_name(bs));
return;
}
if (!bdrv_dev_has_removable_media(bs)) {
error_set(errp, QERR_DEVICE_NOT_REMOVABLE, bdrv_get_device_name(bs));
return;
@ -883,3 +930,153 @@ void qmp_block_resize(const char *device, int64_t size, Error **errp)
break;
}
}
static QObject *qobject_from_block_job(BlockJob *job)
{
return qobject_from_jsonf("{ 'type': %s,"
"'device': %s,"
"'len': %" PRId64 ","
"'offset': %" PRId64 ","
"'speed': %" PRId64 " }",
job->job_type->job_type,
bdrv_get_device_name(job->bs),
job->len,
job->offset,
job->speed);
}
static void block_stream_cb(void *opaque, int ret)
{
BlockDriverState *bs = opaque;
QObject *obj;
trace_block_stream_cb(bs, bs->job, ret);
assert(bs->job);
obj = qobject_from_block_job(bs->job);
if (ret < 0) {
QDict *dict = qobject_to_qdict(obj);
qdict_put(dict, "error", qstring_from_str(strerror(-ret)));
}
if (block_job_is_cancelled(bs->job)) {
monitor_protocol_event(QEVENT_BLOCK_JOB_CANCELLED, obj);
} else {
monitor_protocol_event(QEVENT_BLOCK_JOB_COMPLETED, obj);
}
qobject_decref(obj);
drive_put_ref_bh_schedule(drive_get_by_blockdev(bs));
}
void qmp_block_stream(const char *device, bool has_base,
const char *base, Error **errp)
{
BlockDriverState *bs;
BlockDriverState *base_bs = NULL;
int ret;
bs = bdrv_find(device);
if (!bs) {
error_set(errp, QERR_DEVICE_NOT_FOUND, device);
return;
}
if (base) {
base_bs = bdrv_find_backing_image(bs, base);
if (base_bs == NULL) {
error_set(errp, QERR_BASE_NOT_FOUND, base);
return;
}
}
ret = stream_start(bs, base_bs, base, block_stream_cb, bs);
if (ret < 0) {
switch (ret) {
case -EBUSY:
error_set(errp, QERR_DEVICE_IN_USE, device);
return;
default:
error_set(errp, QERR_NOT_SUPPORTED);
return;
}
}
/* Grab a reference so hotplug does not delete the BlockDriverState from
* underneath us.
*/
drive_get_ref(drive_get_by_blockdev(bs));
trace_qmp_block_stream(bs, bs->job);
}
static BlockJob *find_block_job(const char *device)
{
BlockDriverState *bs;
bs = bdrv_find(device);
if (!bs || !bs->job) {
return NULL;
}
return bs->job;
}
void qmp_block_job_set_speed(const char *device, int64_t value, Error **errp)
{
BlockJob *job = find_block_job(device);
if (!job) {
error_set(errp, QERR_DEVICE_NOT_ACTIVE, device);
return;
}
if (block_job_set_speed(job, value) < 0) {
error_set(errp, QERR_NOT_SUPPORTED);
}
}
void qmp_block_job_cancel(const char *device, Error **errp)
{
BlockJob *job = find_block_job(device);
if (!job) {
error_set(errp, QERR_DEVICE_NOT_ACTIVE, device);
return;
}
trace_qmp_block_job_cancel(job);
block_job_cancel(job);
}
static void do_qmp_query_block_jobs_one(void *opaque, BlockDriverState *bs)
{
BlockJobInfoList **prev = opaque;
BlockJob *job = bs->job;
if (job) {
BlockJobInfoList *elem;
BlockJobInfo *info = g_new(BlockJobInfo, 1);
*info = (BlockJobInfo){
.type = g_strdup(job->job_type->job_type),
.device = g_strdup(bdrv_get_device_name(bs)),
.len = job->len,
.offset = job->offset,
.speed = job->speed,
};
elem = g_new0(BlockJobInfoList, 1);
elem->value = info;
(*prev)->next = elem;
*prev = elem;
}
}
BlockJobInfoList *qmp_query_block_jobs(Error **errp)
{
/* Dummy is a fake list element for holding the head pointer */
BlockJobInfoList dummy = {};
BlockJobInfoList *prev = &dummy;
bdrv_iterate(do_qmp_query_block_jobs_one, &prev);
return dummy.next;
}

58
docs/live-block-ops.txt Normal file
View File

@ -0,0 +1,58 @@
LIVE BLOCK OPERATIONS
=====================
High level description of live block operations. Note these are not
supported for use with the raw format at the moment.
Snapshot live merge
===================
Given a snapshot chain, described in this document in the following
format:
[A] -> [B] -> [C] -> [D]
Where the rightmost object ([D] in the example) described is the current
image which the guest OS has write access to. To the left of it is its base
image, and so on accordingly until the leftmost image, which has no
base.
The snapshot live merge operation transforms such a chain into a
smaller one with fewer elements, such as this transformation relative
to the first example:
[A] -> [D]
Currently only forward merge with target being the active image is
supported, that is, data copy is performed in the right direction with
destination being the rightmost image.
The operation is implemented in QEMU through image streaming facilities.
The basic idea is to execute 'block_stream virtio0' while the guest is
running. Progress can be monitored using 'info block-jobs'. When the
streaming operation completes it raises a QMP event. 'block_stream'
copies data from the backing file(s) into the active image. When finished,
it adjusts the backing file pointer.
The 'base' parameter specifies an image which data need not be streamed from.
This image will be used as the backing file for the active image when the
operation is finished.
In the example above, the command would be:
(qemu) block_stream virtio0 A
Live block copy
===============
To copy an in use image to another destination in the filesystem, one
should create a live snapshot in the desired destination, then stream
into that image. Example:
(qemu) snapshot_blkdev ide0-hd0 /new-path/disk.img qcow2
(qemu) block_stream ide0-hd0

View File

@ -69,6 +69,47 @@ but should be used with extreme caution. Note that this command only
resizes image files, it can not resize block devices like LVM volumes.
ETEXI
{
.name = "block_stream",
.args_type = "device:B,base:s?",
.params = "device [base]",
.help = "copy data from a backing file into a block device",
.mhandler.cmd = hmp_block_stream,
},
STEXI
@item block_stream
@findex block_stream
Copy data from a backing file into a block device.
ETEXI
{
.name = "block_job_set_speed",
.args_type = "device:B,value:o",
.params = "device value",
.help = "set maximum speed for a background block operation",
.mhandler.cmd = hmp_block_job_set_speed,
},
STEXI
@item block_job_set_stream
@findex block_job_set_stream
Set maximum speed for a background block operation.
ETEXI
{
.name = "block_job_cancel",
.args_type = "device:B",
.params = "device",
.help = "stop an active block streaming operation",
.mhandler.cmd = hmp_block_job_cancel,
},
STEXI
@item block_job_cancel
@findex block_job_cancel
Stop an active block streaming operation.
ETEXI
{
.name = "eject",

68
hmp.c
View File

@ -509,6 +509,42 @@ void hmp_info_pci(Monitor *mon)
qapi_free_PciInfoList(info_list);
}
void hmp_info_block_jobs(Monitor *mon)
{
BlockJobInfoList *list;
Error *err = NULL;
list = qmp_query_block_jobs(&err);
assert(!err);
if (!list) {
monitor_printf(mon, "No active jobs\n");
return;
}
while (list) {
if (strcmp(list->value->type, "stream") == 0) {
monitor_printf(mon, "Streaming device %s: Completed %" PRId64
" of %" PRId64 " bytes, speed limit %" PRId64
" bytes/s\n",
list->value->device,
list->value->offset,
list->value->len,
list->value->speed);
} else {
monitor_printf(mon, "Type %s, device %s: Completed %" PRId64
" of %" PRId64 " bytes, speed limit %" PRId64
" bytes/s\n",
list->value->type,
list->value->device,
list->value->offset,
list->value->len,
list->value->speed);
}
list = list->next;
}
}
void hmp_quit(Monitor *mon, const QDict *qdict)
{
monitor_suspend(mon);
@ -783,3 +819,35 @@ void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict)
qdict_get_int(qdict, "iops_wr"), &err);
hmp_handle_error(mon, &err);
}
void hmp_block_stream(Monitor *mon, const QDict *qdict)
{
Error *error = NULL;
const char *device = qdict_get_str(qdict, "device");
const char *base = qdict_get_try_str(qdict, "base");
qmp_block_stream(device, base != NULL, base, &error);
hmp_handle_error(mon, &error);
}
void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict)
{
Error *error = NULL;
const char *device = qdict_get_str(qdict, "device");
int64_t value = qdict_get_int(qdict, "value");
qmp_block_job_set_speed(device, value, &error);
hmp_handle_error(mon, &error);
}
void hmp_block_job_cancel(Monitor *mon, const QDict *qdict)
{
Error *error = NULL;
const char *device = qdict_get_str(qdict, "device");
qmp_block_job_cancel(device, &error);
hmp_handle_error(mon, &error);
}

4
hmp.h
View File

@ -32,6 +32,7 @@ void hmp_info_vnc(Monitor *mon);
void hmp_info_spice(Monitor *mon);
void hmp_info_balloon(Monitor *mon);
void hmp_info_pci(Monitor *mon);
void hmp_info_block_jobs(Monitor *mon);
void hmp_quit(Monitor *mon, const QDict *qdict);
void hmp_stop(Monitor *mon, const QDict *qdict);
void hmp_system_reset(Monitor *mon, const QDict *qdict);
@ -54,5 +55,8 @@ void hmp_expire_password(Monitor *mon, const QDict *qdict);
void hmp_eject(Monitor *mon, const QDict *qdict);
void hmp_change(Monitor *mon, const QDict *qdict);
void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict);
void hmp_block_stream(Monitor *mon, const QDict *qdict);
void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict);
void hmp_block_job_cancel(Monitor *mon, const QDict *qdict);
#endif

View File

@ -391,9 +391,6 @@ static int scsi_disk_emulate_inquiry(SCSIRequest *req, uint8_t *outbuf)
}
l = strlen(s->serial);
if (l > req->cmd.xfer) {
l = req->cmd.xfer;
}
if (l > 20) {
l = 20;
}
@ -1002,9 +999,6 @@ static int scsi_disk_emulate_mode_sense(SCSIDiskReq *r, uint8_t *outbuf)
outbuf[0] = ((buflen - 2) >> 8) & 0xff;
outbuf[1] = (buflen - 2) & 0xff;
}
if (buflen > r->req.cmd.xfer) {
buflen = r->req.cmd.xfer;
}
return buflen;
}
@ -1038,9 +1032,6 @@ static int scsi_disk_emulate_read_toc(SCSIRequest *req, uint8_t *outbuf)
default:
return -1;
}
if (toclen > req->cmd.xfer) {
toclen = req->cmd.xfer;
}
return toclen;
}
@ -1251,6 +1242,7 @@ static int scsi_disk_emulate_command(SCSIDiskReq *r)
scsi_check_condition(r, SENSE_CODE(INVALID_OPCODE));
return -1;
}
buflen = MIN(buflen, req->cmd.xfer);
return buflen;
not_ready:

View File

@ -346,6 +346,8 @@ static void virtio_blk_handle_read(VirtIOBlockReq *req)
bdrv_acct_start(req->dev->bs, &req->acct, req->qiov.size, BDRV_ACCT_READ);
trace_virtio_blk_handle_read(req, sector, req->qiov.size / 512);
if (sector & req->dev->sector_mask) {
virtio_blk_rw_complete(req, -EIO);
return;

View File

@ -479,6 +479,12 @@ void monitor_protocol_event(MonitorEvent event, QObject *data)
case QEVENT_SPICE_DISCONNECTED:
event_name = "SPICE_DISCONNECTED";
break;
case QEVENT_BLOCK_JOB_COMPLETED:
event_name = "BLOCK_JOB_COMPLETED";
break;
case QEVENT_BLOCK_JOB_CANCELLED:
event_name = "BLOCK_JOB_CANCELLED";
break;
default:
abort();
break;
@ -2311,6 +2317,13 @@ static mon_cmd_t info_cmds[] = {
.help = "show block device statistics",
.mhandler.info = hmp_info_blockstats,
},
{
.name = "block-jobs",
.args_type = "",
.params = "",
.help = "show progress of ongoing block device operations",
.mhandler.info = hmp_info_block_jobs,
},
{
.name = "registers",
.args_type = "",

View File

@ -36,6 +36,8 @@ typedef enum MonitorEvent {
QEVENT_SPICE_CONNECTED,
QEVENT_SPICE_INITIALIZED,
QEVENT_SPICE_DISCONNECTED,
QEVENT_BLOCK_JOB_COMPLETED,
QEVENT_BLOCK_JOB_CANCELLED,
QEVENT_MAX,
} MonitorEvent;

View File

@ -844,6 +844,38 @@
##
{ 'command': 'query-pci', 'returns': ['PciInfo'] }
##
# @BlockJobInfo:
#
# Information about a long-running block device operation.
#
# @type: the job type ('stream' for image streaming)
#
# @device: the block device name
#
# @len: the maximum progress value
#
# @offset: the current progress value
#
# @speed: the rate limit, bytes per second
#
# Since: 1.1
##
{ 'type': 'BlockJobInfo',
'data': {'type': 'str', 'device': 'str', 'len': 'int',
'offset': 'int', 'speed': 'int'} }
##
# @query-block-jobs:
#
# Return information about long-running block device operations.
#
# Returns: a list of @BlockJobInfo for each active block job
#
# Since: 1.1
##
{ 'command': 'query-block-jobs', 'returns': ['BlockJobInfo'] }
##
# @quit:
#
@ -1434,3 +1466,86 @@
{ 'command': 'block_set_io_throttle',
'data': { 'device': 'str', 'bps': 'int', 'bps_rd': 'int', 'bps_wr': 'int',
'iops': 'int', 'iops_rd': 'int', 'iops_wr': 'int' } }
# @block_stream:
#
# Copy data from a backing file into a block device.
#
# The block streaming operation is performed in the background until the entire
# backing file has been copied. This command returns immediately once streaming
# has started. The status of ongoing block streaming operations can be checked
# with query-block-jobs. The operation can be stopped before it has completed
# using the block_job_cancel command.
#
# If a base file is specified then sectors are not copied from that base file and
# its backing chain. When streaming completes the image file will have the base
# file as its backing file. This can be used to stream a subset of the backing
# file chain instead of flattening the entire image.
#
# On successful completion the image file is updated to drop the backing file
# and the BLOCK_JOB_COMPLETED event is emitted.
#
# @device: the device name
#
# @base: #optional the common backing file name
#
# Returns: Nothing on success
# If streaming is already active on this device, DeviceInUse
# If @device does not exist, DeviceNotFound
# If image streaming is not supported by this device, NotSupported
# If @base does not exist, BaseNotFound
#
# Since: 1.1
##
{ 'command': 'block_stream', 'data': { 'device': 'str', '*base': 'str' } }
##
# @block_job_set_speed:
#
# Set maximum speed for a background block operation.
#
# This command can only be issued when there is an active block job.
#
# Throttling can be disabled by setting the speed to 0.
#
# @device: the device name
#
# @value: the maximum speed, in bytes per second
#
# Returns: Nothing on success
# If the job type does not support throttling, NotSupported
# If streaming is not active on this device, DeviceNotActive
#
# Since: 1.1
##
{ 'command': 'block_job_set_speed',
'data': { 'device': 'str', 'value': 'int' } }
##
# @block_job_cancel:
#
# Stop an active block streaming operation.
#
# This command returns immediately after marking the active block streaming
# operation for cancellation. It is an error to call this command if no
# operation is in progress.
#
# The operation will cancel as soon as possible and then emit the
# BLOCK_JOB_CANCELLED event. Before that happens the job is still visible when
# enumerated using query-block-jobs.
#
# The image file retains its backing file unless the streaming operation happens
# to complete just as it is being cancelled.
#
# A new block streaming operation can be started at a later time to finish
# copying all data from the backing file.
#
# @device: the device name
#
# Returns: Nothing on success
# If streaming is not active on this device, DeviceNotActive
# If cancellation already in progress, DeviceInUse
#
# Since: 1.1
##
{ 'command': 'block_job_cancel', 'data': { 'device': 'str' } }

38
qemu-coroutine-sleep.c Normal file
View File

@ -0,0 +1,38 @@
/*
* QEMU coroutine sleep
*
* Copyright IBM, Corp. 2011
*
* Authors:
* Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
* See the COPYING.LIB file in the top-level directory.
*
*/
#include "qemu-coroutine.h"
#include "qemu-timer.h"
typedef struct CoSleepCB {
QEMUTimer *ts;
Coroutine *co;
} CoSleepCB;
static void co_sleep_cb(void *opaque)
{
CoSleepCB *sleep_cb = opaque;
qemu_free_timer(sleep_cb->ts);
qemu_coroutine_enter(sleep_cb->co, NULL);
}
void coroutine_fn co_sleep_ns(QEMUClock *clock, int64_t ns)
{
CoSleepCB sleep_cb = {
.co = qemu_coroutine_self(),
};
sleep_cb.ts = qemu_new_timer(clock, SCALE_NS, co_sleep_cb, &sleep_cb);
qemu_mod_timer(sleep_cb.ts, qemu_get_clock_ns(clock) + ns);
qemu_coroutine_yield();
}

View File

@ -17,6 +17,7 @@
#include <stdbool.h>
#include "qemu-queue.h"
#include "qemu-timer.h"
/**
* Coroutines are a mechanism for stack switching and can be used for
@ -199,4 +200,12 @@ void qemu_co_rwlock_wrlock(CoRwlock *lock);
*/
void qemu_co_rwlock_unlock(CoRwlock *lock);
/**
* Yield the coroutine for a given duration
*
* Note this function uses timers and hence only works when a main loop is in
* use. See main-loop.h and do not use from qemu-tool programs.
*/
void coroutine_fn co_sleep_ns(QEMUClock *clock, int64_t ns);
#endif /* QEMU_COROUTINE_H */

View File

@ -130,7 +130,7 @@ static void print_report(const char *op, struct timeval *t, int64_t offset,
static void *
create_iovec(QEMUIOVector *qiov, char **argv, int nr_iov, int pattern)
{
size_t *sizes = calloc(nr_iov, sizeof(size_t));
size_t *sizes = g_new0(size_t, nr_iov);
size_t count = 0;
void *buf = NULL;
void *p;
@ -172,7 +172,7 @@ create_iovec(QEMUIOVector *qiov, char **argv, int nr_iov, int pattern)
}
fail:
free(sizes);
g_free(sizes);
return buf;
}
@ -471,14 +471,14 @@ static int read_f(int argc, char **argv)
}
if (Pflag) {
void *cmp_buf = malloc(pattern_count);
void *cmp_buf = g_malloc(pattern_count);
memset(cmp_buf, pattern, pattern_count);
if (memcmp(buf + pattern_offset, cmp_buf, pattern_count)) {
printf("Pattern verification failed at offset %"
PRId64 ", %d bytes\n",
offset + pattern_offset, pattern_count);
}
free(cmp_buf);
g_free(cmp_buf);
}
if (qflag) {
@ -601,13 +601,13 @@ static int readv_f(int argc, char **argv)
}
if (Pflag) {
void *cmp_buf = malloc(qiov.size);
void *cmp_buf = g_malloc(qiov.size);
memset(cmp_buf, pattern, qiov.size);
if (memcmp(buf, cmp_buf, qiov.size)) {
printf("Pattern verification failed at offset %"
PRId64 ", %zd bytes\n", offset, qiov.size);
}
free(cmp_buf);
g_free(cmp_buf);
}
if (qflag) {
@ -1063,7 +1063,7 @@ static void aio_write_done(void *opaque, int ret)
ctx->qiov.size, 1, ctx->Cflag);
out:
qemu_io_free(ctx->buf);
free(ctx);
g_free(ctx);
}
static void aio_read_done(void *opaque, int ret)
@ -1079,14 +1079,14 @@ static void aio_read_done(void *opaque, int ret)
}
if (ctx->Pflag) {
void *cmp_buf = malloc(ctx->qiov.size);
void *cmp_buf = g_malloc(ctx->qiov.size);
memset(cmp_buf, ctx->pattern, ctx->qiov.size);
if (memcmp(ctx->buf, cmp_buf, ctx->qiov.size)) {
printf("Pattern verification failed at offset %"
PRId64 ", %zd bytes\n", ctx->offset, ctx->qiov.size);
}
free(cmp_buf);
g_free(cmp_buf);
}
if (ctx->qflag) {
@ -1103,7 +1103,7 @@ static void aio_read_done(void *opaque, int ret)
ctx->qiov.size, 1, ctx->Cflag);
out:
qemu_io_free(ctx->buf);
free(ctx);
g_free(ctx);
}
static void aio_read_help(void)
@ -1141,7 +1141,7 @@ static const cmdinfo_t aio_read_cmd = {
static int aio_read_f(int argc, char **argv)
{
int nr_iov, c;
struct aio_ctx *ctx = calloc(1, sizeof(struct aio_ctx));
struct aio_ctx *ctx = g_new0(struct aio_ctx, 1);
while ((c = getopt(argc, argv, "CP:qv")) != EOF) {
switch (c) {
@ -1152,7 +1152,7 @@ static int aio_read_f(int argc, char **argv)
ctx->Pflag = 1;
ctx->pattern = parse_pattern(optarg);
if (ctx->pattern < 0) {
free(ctx);
g_free(ctx);
return 0;
}
break;
@ -1163,20 +1163,20 @@ static int aio_read_f(int argc, char **argv)
ctx->vflag = 1;
break;
default:
free(ctx);
g_free(ctx);
return command_usage(&aio_read_cmd);
}
}
if (optind > argc - 2) {
free(ctx);
g_free(ctx);
return command_usage(&aio_read_cmd);
}
ctx->offset = cvtnum(argv[optind]);
if (ctx->offset < 0) {
printf("non-numeric length argument -- %s\n", argv[optind]);
free(ctx);
g_free(ctx);
return 0;
}
optind++;
@ -1184,14 +1184,14 @@ static int aio_read_f(int argc, char **argv)
if (ctx->offset & 0x1ff) {
printf("offset %" PRId64 " is not sector aligned\n",
ctx->offset);
free(ctx);
g_free(ctx);
return 0;
}
nr_iov = argc - optind;
ctx->buf = create_iovec(&ctx->qiov, &argv[optind], nr_iov, 0xab);
if (ctx->buf == NULL) {
free(ctx);
g_free(ctx);
return 0;
}
@ -1237,7 +1237,7 @@ static int aio_write_f(int argc, char **argv)
{
int nr_iov, c;
int pattern = 0xcd;
struct aio_ctx *ctx = calloc(1, sizeof(struct aio_ctx));
struct aio_ctx *ctx = g_new0(struct aio_ctx, 1);
while ((c = getopt(argc, argv, "CqP:")) != EOF) {
switch (c) {
@ -1250,25 +1250,25 @@ static int aio_write_f(int argc, char **argv)
case 'P':
pattern = parse_pattern(optarg);
if (pattern < 0) {
free(ctx);
g_free(ctx);
return 0;
}
break;
default:
free(ctx);
g_free(ctx);
return command_usage(&aio_write_cmd);
}
}
if (optind > argc - 2) {
free(ctx);
g_free(ctx);
return command_usage(&aio_write_cmd);
}
ctx->offset = cvtnum(argv[optind]);
if (ctx->offset < 0) {
printf("non-numeric length argument -- %s\n", argv[optind]);
free(ctx);
g_free(ctx);
return 0;
}
optind++;
@ -1276,14 +1276,14 @@ static int aio_write_f(int argc, char **argv)
if (ctx->offset & 0x1ff) {
printf("offset %" PRId64 " is not sector aligned\n",
ctx->offset);
free(ctx);
g_free(ctx);
return 0;
}
nr_iov = argc - optind;
ctx->buf = create_iovec(&ctx->qiov, &argv[optind], nr_iov, pattern);
if (ctx->buf == NULL) {
free(ctx);
g_free(ctx);
return 0;
}

View File

@ -51,6 +51,10 @@ static const QErrorStringTable qerror_table[] = {
.error_fmt = QERR_BAD_BUS_FOR_DEVICE,
.desc = "Device '%(device)' can't go on a %(bad_bus_type) bus",
},
{
.error_fmt = QERR_BASE_NOT_FOUND,
.desc = "Base '%(base)' not found",
},
{
.error_fmt = QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
.desc = "Block format '%(format)' used by device '%(name)' does not support feature '%(feature)'",
@ -196,6 +200,10 @@ static const QErrorStringTable qerror_table[] = {
.error_fmt = QERR_NO_BUS_FOR_DEVICE,
.desc = "No '%(bus)' bus found for device '%(device)'",
},
{
.error_fmt = QERR_NOT_SUPPORTED,
.desc = "Not supported",
},
{
.error_fmt = QERR_OPEN_FILE_FAILED,
.desc = "Could not open '%(filename)'",

View File

@ -57,6 +57,9 @@ QError *qobject_to_qerror(const QObject *obj);
#define QERR_BAD_BUS_FOR_DEVICE \
"{ 'class': 'BadBusForDevice', 'data': { 'device': %s, 'bad_bus_type': %s } }"
#define QERR_BASE_NOT_FOUND \
"{ 'class': 'BaseNotFound', 'data': { 'base': %s } }"
#define QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED \
"{ 'class': 'BlockFormatFeatureNotSupported', 'data': { 'format': %s, 'name': %s, 'feature': %s } }"
@ -168,6 +171,9 @@ QError *qobject_to_qerror(const QObject *obj);
#define QERR_NO_BUS_FOR_DEVICE \
"{ 'class': 'NoBusForDevice', 'data': { 'device': %s, 'bus': %s } }"
#define QERR_NOT_SUPPORTED \
"{ 'class': 'NotSupported', 'data': {} }"
#define QERR_OPEN_FILE_FAILED \
"{ 'class': 'OpenFileFailed', 'data': { 'filename': %s } }"

View File

@ -648,6 +648,24 @@ Example:
EQMP
{
.name = "block_stream",
.args_type = "device:B,base:s?",
.mhandler.cmd_new = qmp_marshal_input_block_stream,
},
{
.name = "block_job_set_speed",
.args_type = "device:B,value:o",
.mhandler.cmd_new = qmp_marshal_input_block_job_set_speed,
},
{
.name = "block_job_cancel",
.args_type = "device:B",
.mhandler.cmd_new = qmp_marshal_input_block_job_cancel,
},
{
.name = "blockdev-snapshot-sync",
.args_type = "device:B,snapshot-file:s,format:s?",
@ -1995,6 +2013,12 @@ EQMP
.mhandler.cmd_new = qmp_marshal_input_query_balloon,
},
{
.name = "query-block-jobs",
.args_type = "",
.mhandler.cmd_new = qmp_marshal_input_query_block_jobs,
},
{
.name = "qom-list",
.args_type = "path:s",

View File

@ -65,14 +65,25 @@ bdrv_aio_readv(void *bs, int64_t sector_num, int nb_sectors, void *opaque) "bs %
bdrv_aio_writev(void *bs, int64_t sector_num, int nb_sectors, void *opaque) "bs %p sector_num %"PRId64" nb_sectors %d opaque %p"
bdrv_lock_medium(void *bs, bool locked) "bs %p locked %d"
bdrv_co_readv(void *bs, int64_t sector_num, int nb_sector) "bs %p sector_num %"PRId64" nb_sectors %d"
bdrv_co_copy_on_readv(void *bs, int64_t sector_num, int nb_sector) "bs %p sector_num %"PRId64" nb_sectors %d"
bdrv_co_writev(void *bs, int64_t sector_num, int nb_sector) "bs %p sector_num %"PRId64" nb_sectors %d"
bdrv_co_io_em(void *bs, int64_t sector_num, int nb_sectors, int is_write, void *acb) "bs %p sector_num %"PRId64" nb_sectors %d is_write %d acb %p"
bdrv_co_copy_on_readv(void *bs, int64_t sector_num, int nb_sectors, int64_t cluster_sector_num, int cluster_nb_sectors) "bs %p sector_num %"PRId64" nb_sectors %d cluster_sector_num %"PRId64" cluster_nb_sectors %d"
bdrv_co_do_copy_on_readv(void *bs, int64_t sector_num, int nb_sectors, int64_t cluster_sector_num, int cluster_nb_sectors) "bs %p sector_num %"PRId64" nb_sectors %d cluster_sector_num %"PRId64" cluster_nb_sectors %d"
# block/stream.c
stream_one_iteration(void *s, int64_t sector_num, int nb_sectors, int is_allocated) "s %p sector_num %"PRId64" nb_sectors %d is_allocated %d"
stream_start(void *bs, void *base, void *s, void *co, void *opaque) "bs %p base %p s %p co %p opaque %p"
# blockdev.c
qmp_block_job_cancel(void *job) "job %p"
block_stream_cb(void *bs, void *job, int ret) "bs %p job %p ret %d"
qmp_block_stream(void *bs, void *job) "bs %p job %p"
# hw/virtio-blk.c
virtio_blk_req_complete(void *req, int status) "req %p status %d"
virtio_blk_rw_complete(void *req, int ret) "req %p ret %d"
virtio_blk_handle_write(void *req, uint64_t sector, size_t nsectors) "req %p sector %"PRIu64" nsectors %zu"
virtio_blk_handle_read(void *req, uint64_t sector, size_t nsectors) "req %p sector %"PRIu64" nsectors %zu"
# posix-aio-compat.c
paio_submit(void *acb, void *opaque, int64_t sector_num, int nb_sectors, int type) "acb %p opaque %p sector_num %"PRId64" nb_sectors %d type %d"