mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
Merge remote-tracking branch 'kwolf/for-anthony' into staging
* kwolf/for-anthony: (30 commits) declare ECANCELED on all machines tests/Makefile: Add missing $(EXESUF) stream: do not copy unallocated sectors from the base stream: fix ratelimiting corner case stream: fix HMP block_job_set_speed stream: pass new base image format to bdrv_change_backing_file stream: add testcase for partial streaming stream: fix sectors not allocated test qemu-io: fix the alloc command qemu-io: correctly print non-integer values as decimals qemu-img: make "info" backing file output correct and easier to use block: move field reset from bdrv_open_common to bdrv_close block: protect path_has_protocol from filenames with colons block: simplify path_is_absolute block: wait for job callback in block_job_cancel_sync block: add block_job_sleep_ns block: fully delete bs->file when closing block: do not reuse the backing file across bdrv_close/bdrv_open block: another bdrv_append fix block: fix snapshot on QED ...
This commit is contained in:
@@ -198,33 +198,31 @@ static void bdrv_io_limits_intercept(BlockDriverState *bs,
|
||||
/* check if the path starts with "<protocol>:" */
|
||||
static int path_has_protocol(const char *path)
|
||||
{
|
||||
const char *p;
|
||||
|
||||
#ifdef _WIN32
|
||||
if (is_windows_drive(path) ||
|
||||
is_windows_drive_prefix(path)) {
|
||||
return 0;
|
||||
}
|
||||
p = path + strcspn(path, ":/\\");
|
||||
#else
|
||||
p = path + strcspn(path, ":/");
|
||||
#endif
|
||||
|
||||
return strchr(path, ':') != NULL;
|
||||
return *p == ':';
|
||||
}
|
||||
|
||||
int path_is_absolute(const char *path)
|
||||
{
|
||||
const char *p;
|
||||
#ifdef _WIN32
|
||||
/* specific case for names like: "\\.\d:" */
|
||||
if (*path == '/' || *path == '\\')
|
||||
if (is_windows_drive(path) || is_windows_drive_prefix(path)) {
|
||||
return 1;
|
||||
#endif
|
||||
p = strchr(path, ':');
|
||||
if (p)
|
||||
p++;
|
||||
else
|
||||
p = path;
|
||||
#ifdef _WIN32
|
||||
return (*p == '/' || *p == '\\');
|
||||
}
|
||||
return (*path == '/' || *path == '\\');
|
||||
#else
|
||||
return (*p == '/');
|
||||
return (*path == '/');
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -272,6 +270,15 @@ void path_combine(char *dest, int dest_size,
|
||||
}
|
||||
}
|
||||
|
||||
void bdrv_get_full_backing_filename(BlockDriverState *bs, char *dest, size_t sz)
|
||||
{
|
||||
if (bs->backing_file[0] == '\0' || path_has_protocol(bs->backing_file)) {
|
||||
pstrcpy(dest, sz, bs->backing_file);
|
||||
} else {
|
||||
path_combine(dest, sz, bs->filename, bs->backing_file);
|
||||
}
|
||||
}
|
||||
|
||||
void bdrv_register(BlockDriver *bdrv)
|
||||
{
|
||||
/* Block drivers without coroutine functions need emulation */
|
||||
@@ -612,16 +619,11 @@ static int bdrv_open_common(BlockDriverState *bs, const char *filename,
|
||||
int ret, open_flags;
|
||||
|
||||
assert(drv != NULL);
|
||||
assert(bs->file == NULL);
|
||||
|
||||
trace_bdrv_open_common(bs, filename, flags, drv->format_name);
|
||||
|
||||
bs->file = NULL;
|
||||
bs->total_sectors = 0;
|
||||
bs->encrypted = 0;
|
||||
bs->valid_key = 0;
|
||||
bs->sg = 0;
|
||||
bs->open_flags = flags;
|
||||
bs->growable = 0;
|
||||
bs->buffer_alignment = 512;
|
||||
|
||||
assert(bs->copy_on_read == 0); /* bdrv_new() and bdrv_close() make it so */
|
||||
@@ -630,7 +632,6 @@ static int bdrv_open_common(BlockDriverState *bs, const char *filename,
|
||||
}
|
||||
|
||||
pstrcpy(bs->filename, sizeof(bs->filename), filename);
|
||||
bs->backing_file[0] = '\0';
|
||||
|
||||
if (use_bdrv_whitelist && !bdrv_is_whitelisted(drv)) {
|
||||
return -ENOTSUP;
|
||||
@@ -804,14 +805,8 @@ int bdrv_open(BlockDriverState *bs, const char *filename, int flags,
|
||||
BlockDriver *back_drv = NULL;
|
||||
|
||||
bs->backing_hd = bdrv_new("");
|
||||
|
||||
if (path_has_protocol(bs->backing_file)) {
|
||||
pstrcpy(backing_filename, sizeof(backing_filename),
|
||||
bs->backing_file);
|
||||
} else {
|
||||
path_combine(backing_filename, sizeof(backing_filename),
|
||||
filename, bs->backing_file);
|
||||
}
|
||||
bdrv_get_full_backing_filename(bs, backing_filename,
|
||||
sizeof(backing_filename));
|
||||
|
||||
if (bs->backing_format[0] != '\0') {
|
||||
back_drv = bdrv_find_format(bs->backing_format);
|
||||
@@ -878,9 +873,17 @@ void bdrv_close(BlockDriverState *bs)
|
||||
bs->opaque = NULL;
|
||||
bs->drv = NULL;
|
||||
bs->copy_on_read = 0;
|
||||
bs->backing_file[0] = '\0';
|
||||
bs->backing_format[0] = '\0';
|
||||
bs->total_sectors = 0;
|
||||
bs->encrypted = 0;
|
||||
bs->valid_key = 0;
|
||||
bs->sg = 0;
|
||||
bs->growable = 0;
|
||||
|
||||
if (bs->file != NULL) {
|
||||
bdrv_close(bs->file);
|
||||
bdrv_delete(bs->file);
|
||||
bs->file = NULL;
|
||||
}
|
||||
|
||||
bdrv_dev_change_media_cb(bs, false);
|
||||
@@ -906,12 +909,31 @@ void bdrv_close_all(void)
|
||||
*
|
||||
* This function does not flush data to disk, use bdrv_flush_all() for that
|
||||
* after calling this function.
|
||||
*
|
||||
* Note that completion of an asynchronous I/O operation can trigger any
|
||||
* number of other I/O operations on other devices---for example a coroutine
|
||||
* can be arbitrarily complex and a constant flow of I/O can come until the
|
||||
* coroutine is complete. Because of this, it is not possible to have a
|
||||
* function to drain a single device's I/O queue.
|
||||
*/
|
||||
void bdrv_drain_all(void)
|
||||
{
|
||||
BlockDriverState *bs;
|
||||
bool busy;
|
||||
|
||||
qemu_aio_flush();
|
||||
do {
|
||||
busy = qemu_aio_wait();
|
||||
|
||||
/* FIXME: We do not have timer support here, so this is effectively
|
||||
* a busy wait.
|
||||
*/
|
||||
QTAILQ_FOREACH(bs, &bdrv_states, list) {
|
||||
if (!qemu_co_queue_empty(&bs->throttled_reqs)) {
|
||||
qemu_co_queue_restart_all(&bs->throttled_reqs);
|
||||
busy = true;
|
||||
}
|
||||
}
|
||||
} while (busy);
|
||||
|
||||
/* If requests are still pending there is a bug somewhere */
|
||||
QTAILQ_FOREACH(bs, &bdrv_states, list) {
|
||||
@@ -930,6 +952,13 @@ void bdrv_make_anon(BlockDriverState *bs)
|
||||
bs->device_name[0] = '\0';
|
||||
}
|
||||
|
||||
static void bdrv_rebind(BlockDriverState *bs)
|
||||
{
|
||||
if (bs->drv && bs->drv->bdrv_rebind) {
|
||||
bs->drv->bdrv_rebind(bs);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Add new bs contents at the top of an image chain while the chain is
|
||||
* live, while keeping required fields on the top layer.
|
||||
@@ -951,6 +980,7 @@ void bdrv_append(BlockDriverState *bs_new, BlockDriverState *bs_top)
|
||||
tmp = *bs_new;
|
||||
|
||||
/* there are some fields that need to stay on the top layer: */
|
||||
tmp.open_flags = bs_top->open_flags;
|
||||
|
||||
/* dev info */
|
||||
tmp.dev_ops = bs_top->dev_ops;
|
||||
@@ -1018,6 +1048,9 @@ void bdrv_append(BlockDriverState *bs_new, BlockDriverState *bs_top)
|
||||
bs_new->slice_time = 0;
|
||||
bs_new->slice_start = 0;
|
||||
bs_new->slice_end = 0;
|
||||
|
||||
bdrv_rebind(bs_new);
|
||||
bdrv_rebind(bs_top);
|
||||
}
|
||||
|
||||
void bdrv_delete(BlockDriverState *bs)
|
||||
@@ -1030,9 +1063,6 @@ void bdrv_delete(BlockDriverState *bs)
|
||||
bdrv_make_anon(bs);
|
||||
|
||||
bdrv_close(bs);
|
||||
if (bs->file != NULL) {
|
||||
bdrv_delete(bs->file);
|
||||
}
|
||||
|
||||
assert(bs != bs_snapshots);
|
||||
g_free(bs);
|
||||
@@ -1440,12 +1470,24 @@ int bdrv_change_backing_file(BlockDriverState *bs,
|
||||
const char *backing_file, const char *backing_fmt)
|
||||
{
|
||||
BlockDriver *drv = bs->drv;
|
||||
int ret;
|
||||
|
||||
/* Backing file format doesn't make sense without a backing file */
|
||||
if (backing_fmt && !backing_file) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (drv->bdrv_change_backing_file != NULL) {
|
||||
return drv->bdrv_change_backing_file(bs, backing_file, backing_fmt);
|
||||
ret = drv->bdrv_change_backing_file(bs, backing_file, backing_fmt);
|
||||
} else {
|
||||
return -ENOTSUP;
|
||||
ret = -ENOTSUP;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
pstrcpy(bs->backing_file, sizeof(bs->backing_file), backing_file ?: "");
|
||||
pstrcpy(bs->backing_format, sizeof(bs->backing_format), backing_fmt ?: "");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bdrv_check_byte_request(BlockDriverState *bs, int64_t offset,
|
||||
@@ -1553,6 +1595,8 @@ int bdrv_read(BlockDriverState *bs, int64_t sector_num,
|
||||
return bdrv_rw_co(bs, sector_num, buf, nb_sectors, false);
|
||||
}
|
||||
|
||||
#define BITS_PER_LONG (sizeof(unsigned long) * 8)
|
||||
|
||||
static void set_dirty_bitmap(BlockDriverState *bs, int64_t sector_num,
|
||||
int nb_sectors, int dirty)
|
||||
{
|
||||
@@ -1563,8 +1607,8 @@ static void set_dirty_bitmap(BlockDriverState *bs, int64_t sector_num,
|
||||
end = (sector_num + nb_sectors - 1) / BDRV_SECTORS_PER_DIRTY_CHUNK;
|
||||
|
||||
for (; start <= end; start++) {
|
||||
idx = start / (sizeof(unsigned long) * 8);
|
||||
bit = start % (sizeof(unsigned long) * 8);
|
||||
idx = start / BITS_PER_LONG;
|
||||
bit = start % BITS_PER_LONG;
|
||||
val = bs->dirty_bitmap[idx];
|
||||
if (dirty) {
|
||||
if (!(val & (1UL << bit))) {
|
||||
@@ -3869,10 +3913,10 @@ void bdrv_set_dirty_tracking(BlockDriverState *bs, int enable)
|
||||
if (enable) {
|
||||
if (!bs->dirty_bitmap) {
|
||||
bitmap_size = (bdrv_getlength(bs) >> BDRV_SECTOR_BITS) +
|
||||
BDRV_SECTORS_PER_DIRTY_CHUNK * 8 - 1;
|
||||
bitmap_size /= BDRV_SECTORS_PER_DIRTY_CHUNK * 8;
|
||||
BDRV_SECTORS_PER_DIRTY_CHUNK * BITS_PER_LONG - 1;
|
||||
bitmap_size /= BDRV_SECTORS_PER_DIRTY_CHUNK * BITS_PER_LONG;
|
||||
|
||||
bs->dirty_bitmap = g_malloc0(bitmap_size);
|
||||
bs->dirty_bitmap = g_new0(unsigned long, bitmap_size);
|
||||
}
|
||||
} else {
|
||||
if (bs->dirty_bitmap) {
|
||||
@@ -4072,10 +4116,15 @@ int bdrv_img_create(const char *filename, const char *fmt,
|
||||
if (backing_file && backing_file->value.s) {
|
||||
uint64_t size;
|
||||
char buf[32];
|
||||
int back_flags;
|
||||
|
||||
/* backing files always opened read-only */
|
||||
back_flags =
|
||||
flags & ~(BDRV_O_RDWR | BDRV_O_SNAPSHOT | BDRV_O_NO_BACKING);
|
||||
|
||||
bs = bdrv_new("");
|
||||
|
||||
ret = bdrv_open(bs, backing_file->value.s, flags, backing_drv);
|
||||
ret = bdrv_open(bs, backing_file->value.s, back_flags, backing_drv);
|
||||
if (ret < 0) {
|
||||
error_report("Could not open '%s'", backing_file->value.s);
|
||||
goto out;
|
||||
@@ -4139,6 +4188,7 @@ void *block_job_create(const BlockJobType *job_type, BlockDriverState *bs,
|
||||
job->bs = bs;
|
||||
job->cb = cb;
|
||||
job->opaque = opaque;
|
||||
job->busy = true;
|
||||
bs->job = job;
|
||||
|
||||
/* Only set speed when necessary to avoid NotSupported error */
|
||||
@@ -4188,6 +4238,9 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
||||
void block_job_cancel(BlockJob *job)
|
||||
{
|
||||
job->cancelled = true;
|
||||
if (job->co && !job->busy) {
|
||||
qemu_coroutine_enter(job->co, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
bool block_job_is_cancelled(BlockJob *job)
|
||||
@@ -4195,13 +4248,52 @@ bool block_job_is_cancelled(BlockJob *job)
|
||||
return job->cancelled;
|
||||
}
|
||||
|
||||
void block_job_cancel_sync(BlockJob *job)
|
||||
struct BlockCancelData {
|
||||
BlockJob *job;
|
||||
BlockDriverCompletionFunc *cb;
|
||||
void *opaque;
|
||||
bool cancelled;
|
||||
int ret;
|
||||
};
|
||||
|
||||
static void block_job_cancel_cb(void *opaque, int ret)
|
||||
{
|
||||
struct BlockCancelData *data = opaque;
|
||||
|
||||
data->cancelled = block_job_is_cancelled(data->job);
|
||||
data->ret = ret;
|
||||
data->cb(data->opaque, ret);
|
||||
}
|
||||
|
||||
int block_job_cancel_sync(BlockJob *job)
|
||||
{
|
||||
struct BlockCancelData data;
|
||||
BlockDriverState *bs = job->bs;
|
||||
|
||||
assert(bs->job == job);
|
||||
|
||||
/* Set up our own callback to store the result and chain to
|
||||
* the original callback.
|
||||
*/
|
||||
data.job = job;
|
||||
data.cb = job->cb;
|
||||
data.opaque = job->opaque;
|
||||
data.ret = -EINPROGRESS;
|
||||
job->cb = block_job_cancel_cb;
|
||||
job->opaque = &data;
|
||||
block_job_cancel(job);
|
||||
while (bs->job != NULL && bs->job->busy) {
|
||||
while (data.ret == -EINPROGRESS) {
|
||||
qemu_aio_wait();
|
||||
}
|
||||
return (data.cancelled && data.ret == 0) ? -ECANCELED : data.ret;
|
||||
}
|
||||
|
||||
void block_job_sleep_ns(BlockJob *job, QEMUClock *clock, int64_t ns)
|
||||
{
|
||||
/* Check cancellation *before* setting busy = false, too! */
|
||||
if (!block_job_is_cancelled(job)) {
|
||||
job->busy = false;
|
||||
co_sleep_ns(clock, ns);
|
||||
job->busy = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,6 +303,8 @@ int bdrv_get_info(BlockDriverState *bs, BlockDriverInfo *bdi);
|
||||
const char *bdrv_get_encrypted_filename(BlockDriverState *bs);
|
||||
void bdrv_get_backing_filename(BlockDriverState *bs,
|
||||
char *filename, int filename_size);
|
||||
void bdrv_get_full_backing_filename(BlockDriverState *bs,
|
||||
char *dest, size_t sz);
|
||||
int bdrv_can_snapshot(BlockDriverState *bs);
|
||||
int bdrv_is_snapshot(BlockDriverState *bs);
|
||||
BlockDriverState *bdrv_snapshots(void);
|
||||
|
||||
@@ -1011,11 +1011,6 @@ fail:
|
||||
static int qcow2_change_backing_file(BlockDriverState *bs,
|
||||
const char *backing_file, const char *backing_fmt)
|
||||
{
|
||||
/* Backing file format doesn't make sense without a backing file */
|
||||
if (backing_fmt && !backing_file) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pstrcpy(bs->backing_file, sizeof(bs->backing_file), backing_file ?: "");
|
||||
pstrcpy(bs->backing_format, sizeof(bs->backing_format), backing_fmt ?: "");
|
||||
|
||||
|
||||
@@ -367,6 +367,12 @@ static void qed_cancel_need_check_timer(BDRVQEDState *s)
|
||||
qemu_del_timer(s->need_check_timer);
|
||||
}
|
||||
|
||||
static void bdrv_qed_rebind(BlockDriverState *bs)
|
||||
{
|
||||
BDRVQEDState *s = bs->opaque;
|
||||
s->bs = bs;
|
||||
}
|
||||
|
||||
static int bdrv_qed_open(BlockDriverState *bs, int flags)
|
||||
{
|
||||
BDRVQEDState *s = bs->opaque;
|
||||
@@ -1550,6 +1556,7 @@ static BlockDriver bdrv_qed = {
|
||||
.create_options = qed_create_options,
|
||||
|
||||
.bdrv_probe = bdrv_qed_probe,
|
||||
.bdrv_rebind = bdrv_qed_rebind,
|
||||
.bdrv_open = bdrv_qed_open,
|
||||
.bdrv_close = bdrv_qed_close,
|
||||
.bdrv_create = bdrv_qed_create,
|
||||
|
||||
+26
-50
@@ -33,19 +33,19 @@ typedef struct {
|
||||
|
||||
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 {
|
||||
if (limit->dispatched == 0 || limit->dispatched + n <= limit->slice_quota) {
|
||||
limit->dispatched += n;
|
||||
return 0;
|
||||
} else {
|
||||
limit->dispatched = n;
|
||||
return limit->next_slice_time - now;
|
||||
}
|
||||
return delay_ns;
|
||||
}
|
||||
|
||||
static void ratelimit_set_speed(RateLimit *limit, uint64_t speed)
|
||||
@@ -96,17 +96,6 @@ static void close_unused_images(BlockDriverState *top, BlockDriverState *base,
|
||||
bdrv_delete(unused);
|
||||
}
|
||||
top->backing_hd = base;
|
||||
|
||||
pstrcpy(top->backing_file, sizeof(top->backing_file), "");
|
||||
pstrcpy(top->backing_format, sizeof(top->backing_format), "");
|
||||
if (base_id) {
|
||||
pstrcpy(top->backing_file, sizeof(top->backing_file), base_id);
|
||||
if (base->drv) {
|
||||
pstrcpy(top->backing_format, sizeof(top->backing_format),
|
||||
base->drv->format_name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -141,14 +130,9 @@ static int coroutine_fn is_allocated_base(BlockDriverState *top,
|
||||
*/
|
||||
intermediate = top->backing_hd;
|
||||
|
||||
while (intermediate) {
|
||||
while (intermediate != base) {
|
||||
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) {
|
||||
@@ -171,6 +155,7 @@ static int coroutine_fn is_allocated_base(BlockDriverState *top,
|
||||
intermediate = intermediate->backing_hd;
|
||||
}
|
||||
|
||||
*pnum = n;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -203,30 +188,25 @@ static void coroutine_fn stream_run(void *opaque)
|
||||
}
|
||||
|
||||
for (sector_num = 0; sector_num < end; sector_num += n) {
|
||||
retry:
|
||||
uint64_t delay_ns = 0;
|
||||
|
||||
wait:
|
||||
/* 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.
|
||||
*/
|
||||
block_job_sleep_ns(&s->common, rt_clock, delay_ns);
|
||||
if (block_job_is_cancelled(&s->common)) {
|
||||
break;
|
||||
}
|
||||
|
||||
s->common.busy = true;
|
||||
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);
|
||||
}
|
||||
ret = is_allocated_base(bs, base, 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);
|
||||
delay_ns = ratelimit_calculate_delay(&s->limit, n);
|
||||
if (delay_ns > 0) {
|
||||
s->common.busy = false;
|
||||
co_sleep_ns(rt_clock, delay_ns);
|
||||
|
||||
/* Recheck cancellation and that sectors are unallocated */
|
||||
goto retry;
|
||||
goto wait;
|
||||
}
|
||||
}
|
||||
ret = stream_populate(bs, sector_num, n, buf);
|
||||
@@ -238,12 +218,6 @@ retry:
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
s->common.busy = false;
|
||||
co_sleep_ns(rt_clock, 0);
|
||||
}
|
||||
|
||||
if (!base) {
|
||||
@@ -251,11 +225,14 @@ retry:
|
||||
}
|
||||
|
||||
if (!block_job_is_cancelled(&s->common) && sector_num == end && ret == 0) {
|
||||
const char *base_id = NULL;
|
||||
const char *base_id = NULL, *base_fmt = NULL;
|
||||
if (base) {
|
||||
base_id = s->backing_file_id;
|
||||
if (base->drv) {
|
||||
base_fmt = base->drv->format_name;
|
||||
}
|
||||
}
|
||||
ret = bdrv_change_backing_file(bs, base_id, NULL);
|
||||
ret = bdrv_change_backing_file(bs, base_id, base_fmt);
|
||||
close_unused_images(bs, base, base_id);
|
||||
}
|
||||
|
||||
@@ -286,7 +263,6 @@ void stream_start(BlockDriverState *bs, BlockDriverState *base,
|
||||
void *opaque, Error **errp)
|
||||
{
|
||||
StreamBlockJob *s;
|
||||
Coroutine *co;
|
||||
|
||||
s = block_job_create(&stream_job_type, bs, speed, cb, opaque, errp);
|
||||
if (!s) {
|
||||
@@ -298,7 +274,7 @@ void stream_start(BlockDriverState *bs, BlockDriverState *base,
|
||||
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);
|
||||
s->common.co = qemu_coroutine_create(stream_run);
|
||||
trace_stream_start(bs, base, s, s->common.co, opaque);
|
||||
qemu_coroutine_enter(s->common.co, s);
|
||||
}
|
||||
|
||||
@@ -982,6 +982,12 @@ static BDRVVVFATState *vvv = NULL;
|
||||
static int enable_write_target(BDRVVVFATState *s);
|
||||
static int is_consistent(BDRVVVFATState *s);
|
||||
|
||||
static void vvfat_rebind(BlockDriverState *bs)
|
||||
{
|
||||
BDRVVVFATState *s = bs->opaque;
|
||||
s->bs = bs;
|
||||
}
|
||||
|
||||
static int vvfat_open(BlockDriverState *bs, const char* dirname, int flags)
|
||||
{
|
||||
BDRVVVFATState *s = bs->opaque;
|
||||
@@ -2855,6 +2861,7 @@ static BlockDriver bdrv_vvfat = {
|
||||
.format_name = "vvfat",
|
||||
.instance_size = sizeof(BDRVVVFATState),
|
||||
.bdrv_file_open = vvfat_open,
|
||||
.bdrv_rebind = vvfat_rebind,
|
||||
.bdrv_read = vvfat_co_read,
|
||||
.bdrv_write = vvfat_co_write,
|
||||
.bdrv_close = vvfat_close,
|
||||
|
||||
+26
-8
@@ -94,21 +94,24 @@ struct BlockJob {
|
||||
/** The block device on which the job is operating. */
|
||||
BlockDriverState *bs;
|
||||
|
||||
/**
|
||||
* The coroutine that executes the job. If not NULL, it is
|
||||
* reentered when busy is false and the job is cancelled.
|
||||
*/
|
||||
Coroutine *co;
|
||||
|
||||
/**
|
||||
* Set to true if the job should cancel itself. The flag must
|
||||
* always be tested just before toggling the busy flag from false
|
||||
* to true. After a job has detected that the cancelled flag is
|
||||
* true, it should not anymore issue any I/O operation to the
|
||||
* block device.
|
||||
* to true. After a job has been cancelled, it should only yield
|
||||
* if #qemu_aio_wait will ("sooner or later") reenter the coroutine.
|
||||
*/
|
||||
bool cancelled;
|
||||
|
||||
/**
|
||||
* Set to false by the job while it is in a quiescent state, where
|
||||
* no I/O is pending and cancellation can be processed without
|
||||
* issuing new I/O. The busy flag must be set to false when the
|
||||
* job goes to sleep on any condition that is not detected by
|
||||
* #qemu_aio_wait, such as a timer.
|
||||
* no I/O is pending and the job has yielded on any condition
|
||||
* that is not detected by #qemu_aio_wait, such as a timer.
|
||||
*/
|
||||
bool busy;
|
||||
|
||||
@@ -140,6 +143,7 @@ struct BlockDriver {
|
||||
int (*bdrv_write)(BlockDriverState *bs, int64_t sector_num,
|
||||
const uint8_t *buf, int nb_sectors);
|
||||
void (*bdrv_close)(BlockDriverState *bs);
|
||||
void (*bdrv_rebind)(BlockDriverState *bs);
|
||||
int (*bdrv_create)(const char *filename, QEMUOptionParameter *options);
|
||||
int (*bdrv_set_key)(BlockDriverState *bs, const char *key);
|
||||
int (*bdrv_make_empty)(BlockDriverState *bs);
|
||||
@@ -362,6 +366,17 @@ void *block_job_create(const BlockJobType *job_type, BlockDriverState *bs,
|
||||
int64_t speed, BlockDriverCompletionFunc *cb,
|
||||
void *opaque, Error **errp);
|
||||
|
||||
/**
|
||||
* block_job_sleep_ns:
|
||||
* @job: The job that calls the function.
|
||||
* @clock: The clock to sleep on.
|
||||
* @ns: How many nanoseconds to stop for.
|
||||
*
|
||||
* Put the job to sleep (assuming that it wasn't canceled) for @ns
|
||||
* nanoseconds. Canceling the job will interrupt the wait immediately.
|
||||
*/
|
||||
void block_job_sleep_ns(BlockJob *job, QEMUClock *clock, int64_t ns);
|
||||
|
||||
/**
|
||||
* block_job_complete:
|
||||
* @job: The job being completed.
|
||||
@@ -409,8 +424,11 @@ bool block_job_is_cancelled(BlockJob *job);
|
||||
* immediately after #block_job_cancel_sync. Users of block jobs
|
||||
* will usually protect the BlockDriverState objects with a reference
|
||||
* count, should this be a concern.
|
||||
*
|
||||
* Returns the return value from the job if the job actually completed
|
||||
* during the call, or -ECANCELED if it was canceled.
|
||||
*/
|
||||
void block_job_cancel_sync(BlockJob *job);
|
||||
int block_job_cancel_sync(BlockJob *job);
|
||||
|
||||
/**
|
||||
* stream_start:
|
||||
|
||||
+6
-3
@@ -756,14 +756,17 @@ void qmp_transaction(BlockdevActionList *dev_list, Error **errp)
|
||||
goto delete_and_fail;
|
||||
}
|
||||
|
||||
if (!bdrv_is_inserted(states->old_bs)) {
|
||||
error_set(errp, QERR_DEVICE_HAS_NO_MEDIUM, device);
|
||||
goto delete_and_fail;
|
||||
}
|
||||
|
||||
if (bdrv_in_use(states->old_bs)) {
|
||||
error_set(errp, QERR_DEVICE_IN_USE, device);
|
||||
goto delete_and_fail;
|
||||
}
|
||||
|
||||
if (!bdrv_is_read_only(states->old_bs) &&
|
||||
bdrv_is_inserted(states->old_bs)) {
|
||||
|
||||
if (!bdrv_is_read_only(states->old_bs)) {
|
||||
if (bdrv_flush(states->old_bs)) {
|
||||
error_set(errp, QERR_IO_ERROR);
|
||||
goto delete_and_fail;
|
||||
|
||||
@@ -418,31 +418,37 @@ cvtstr(
|
||||
char *str,
|
||||
size_t size)
|
||||
{
|
||||
const char *fmt;
|
||||
int precise;
|
||||
|
||||
precise = ((double)value * 1000 == (double)(int)value * 1000);
|
||||
char *trim;
|
||||
const char *suffix;
|
||||
|
||||
if (value >= EXABYTES(1)) {
|
||||
fmt = precise ? "%.f EiB" : "%.3f EiB";
|
||||
snprintf(str, size, fmt, TO_EXABYTES(value));
|
||||
suffix = " EiB";
|
||||
snprintf(str, size - 4, "%.3f", TO_EXABYTES(value));
|
||||
} else if (value >= PETABYTES(1)) {
|
||||
fmt = precise ? "%.f PiB" : "%.3f PiB";
|
||||
snprintf(str, size, fmt, TO_PETABYTES(value));
|
||||
suffix = " PiB";
|
||||
snprintf(str, size - 4, "%.3f", TO_PETABYTES(value));
|
||||
} else if (value >= TERABYTES(1)) {
|
||||
fmt = precise ? "%.f TiB" : "%.3f TiB";
|
||||
snprintf(str, size, fmt, TO_TERABYTES(value));
|
||||
suffix = " TiB";
|
||||
snprintf(str, size - 4, "%.3f", TO_TERABYTES(value));
|
||||
} else if (value >= GIGABYTES(1)) {
|
||||
fmt = precise ? "%.f GiB" : "%.3f GiB";
|
||||
snprintf(str, size, fmt, TO_GIGABYTES(value));
|
||||
suffix = " GiB";
|
||||
snprintf(str, size - 4, "%.3f", TO_GIGABYTES(value));
|
||||
} else if (value >= MEGABYTES(1)) {
|
||||
fmt = precise ? "%.f MiB" : "%.3f MiB";
|
||||
snprintf(str, size, fmt, TO_MEGABYTES(value));
|
||||
suffix = " MiB";
|
||||
snprintf(str, size - 4, "%.3f", TO_MEGABYTES(value));
|
||||
} else if (value >= KILOBYTES(1)) {
|
||||
fmt = precise ? "%.f KiB" : "%.3f KiB";
|
||||
snprintf(str, size, fmt, TO_KILOBYTES(value));
|
||||
suffix = " KiB";
|
||||
snprintf(str, size - 4, "%.3f", TO_KILOBYTES(value));
|
||||
} else {
|
||||
snprintf(str, size, "%f bytes", value);
|
||||
suffix = " bytes";
|
||||
snprintf(str, size - 6, "%f", value);
|
||||
}
|
||||
|
||||
trim = strstr(str, ".000");
|
||||
if (trim) {
|
||||
strcpy(trim, suffix);
|
||||
} else {
|
||||
strcat(str, suffix);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -849,7 +849,7 @@ 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");
|
||||
int64_t value = qdict_get_int(qdict, "speed");
|
||||
|
||||
qmp_block_job_set_speed(device, value, &error);
|
||||
|
||||
|
||||
@@ -61,6 +61,9 @@ typedef struct Monitor Monitor;
|
||||
#if !defined(ENOTSUP)
|
||||
#define ENOTSUP 4096
|
||||
#endif
|
||||
#if !defined(ECANCELED)
|
||||
#define ECANCELED 4097
|
||||
#endif
|
||||
#ifndef TIME_MAX
|
||||
#define TIME_MAX LONG_MAX
|
||||
#endif
|
||||
|
||||
+7
-5
@@ -1138,11 +1138,13 @@ static int img_info(int argc, char **argv)
|
||||
}
|
||||
bdrv_get_backing_filename(bs, backing_filename, sizeof(backing_filename));
|
||||
if (backing_filename[0] != '\0') {
|
||||
path_combine(backing_filename2, sizeof(backing_filename2),
|
||||
filename, backing_filename);
|
||||
printf("backing file: %s (actual path: %s)\n",
|
||||
backing_filename,
|
||||
backing_filename2);
|
||||
bdrv_get_full_backing_filename(bs, backing_filename2,
|
||||
sizeof(backing_filename2));
|
||||
printf("backing file: %s", backing_filename);
|
||||
if (strcmp(backing_filename, backing_filename2) != 0) {
|
||||
printf(" (actual path: %s)", backing_filename2);
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
dump_snapshots(bs);
|
||||
bdrv_delete(bs);
|
||||
|
||||
@@ -1560,7 +1560,7 @@ out:
|
||||
|
||||
static int alloc_f(int argc, char **argv)
|
||||
{
|
||||
int64_t offset;
|
||||
int64_t offset, sector_num;
|
||||
int nb_sectors, remaining;
|
||||
char s1[64];
|
||||
int num, sum_alloc;
|
||||
@@ -1581,12 +1581,18 @@ static int alloc_f(int argc, char **argv)
|
||||
|
||||
remaining = nb_sectors;
|
||||
sum_alloc = 0;
|
||||
sector_num = offset >> 9;
|
||||
while (remaining) {
|
||||
ret = bdrv_is_allocated(bs, offset >> 9, nb_sectors, &num);
|
||||
ret = bdrv_is_allocated(bs, sector_num, remaining, &num);
|
||||
sector_num += num;
|
||||
remaining -= num;
|
||||
if (ret) {
|
||||
sum_alloc += num;
|
||||
}
|
||||
if (num == 0) {
|
||||
nb_sectors -= remaining;
|
||||
remaining = 0;
|
||||
}
|
||||
}
|
||||
|
||||
cvtstr(offset, s1, sizeof(s1));
|
||||
|
||||
+1
-1
@@ -759,7 +759,7 @@ EQMP
|
||||
|
||||
{
|
||||
.name = "blockdev-snapshot-sync",
|
||||
.args_type = "device:B,snapshot-file:s,format:s?",
|
||||
.args_type = "device:B,snapshot-file:s,format:s?,mode:s?",
|
||||
.mhandler.cmd_new = qmp_marshal_input_blockdev_snapshot_sync,
|
||||
},
|
||||
|
||||
|
||||
+3
-1
@@ -18,7 +18,8 @@ check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
|
||||
|
||||
# All QTests for now are POSIX-only, but the dependencies are
|
||||
# really in libqtest, not in the testcases themselves.
|
||||
check-qtest-i386-y = tests/rtc-test
|
||||
check-qtest-i386-y = tests/rtc-test$(EXESUF)
|
||||
check-qtest-i386-y = tests/fdc-test$(EXESUF)
|
||||
check-qtest-x86_64-y = $(check-qtest-i386-y)
|
||||
check-qtest-sparc-y = tests/m48t59-test$(EXESUF)
|
||||
check-qtest-sparc64-y = tests/m48t59-test$(EXESUF)
|
||||
@@ -67,6 +68,7 @@ tests/test-qmp-commands$(EXESUF): tests/test-qmp-commands.o tests/test-qmp-marsh
|
||||
|
||||
tests/rtc-test$(EXESUF): tests/rtc-test.o $(trace-obj-y)
|
||||
tests/m48t59-test$(EXESUF): tests/m48t59-test.o $(trace-obj-y)
|
||||
tests/fdc-test$(EXESUF): tests/fdc-test.o tests/libqtest.o
|
||||
|
||||
# QTest rules
|
||||
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Floppy test cases.
|
||||
*
|
||||
* Copyright (c) 2012 Kevin Wolf <kwolf@redhat.com>
|
||||
*
|
||||
* 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 <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include "libqtest.h"
|
||||
#include "qemu-common.h"
|
||||
|
||||
#define TEST_IMAGE_SIZE 1440 * 1024
|
||||
|
||||
#define FLOPPY_BASE 0x3f0
|
||||
#define FLOPPY_IRQ 6
|
||||
|
||||
enum {
|
||||
reg_sra = 0x0,
|
||||
reg_srb = 0x1,
|
||||
reg_dor = 0x2,
|
||||
reg_msr = 0x4,
|
||||
reg_dsr = 0x4,
|
||||
reg_fifo = 0x5,
|
||||
reg_dir = 0x7,
|
||||
};
|
||||
|
||||
enum {
|
||||
CMD_SENSE_INT = 0x08,
|
||||
CMD_SEEK = 0x0f,
|
||||
};
|
||||
|
||||
enum {
|
||||
RQM = 0x80,
|
||||
DIO = 0x40,
|
||||
|
||||
DSKCHG = 0x80,
|
||||
};
|
||||
|
||||
char test_image[] = "/tmp/qtest.XXXXXX";
|
||||
|
||||
#define assert_bit_set(data, mask) g_assert_cmphex((data) & (mask), ==, (mask))
|
||||
#define assert_bit_clear(data, mask) g_assert_cmphex((data) & (mask), ==, 0)
|
||||
|
||||
static void floppy_send(uint8_t byte)
|
||||
{
|
||||
uint8_t msr;
|
||||
|
||||
msr = inb(FLOPPY_BASE + reg_msr);
|
||||
assert_bit_set(msr, RQM);
|
||||
assert_bit_clear(msr, DIO);
|
||||
|
||||
outb(FLOPPY_BASE + reg_fifo, byte);
|
||||
}
|
||||
|
||||
static uint8_t floppy_recv(void)
|
||||
{
|
||||
uint8_t msr;
|
||||
|
||||
msr = inb(FLOPPY_BASE + reg_msr);
|
||||
assert_bit_set(msr, RQM | DIO);
|
||||
|
||||
return inb(FLOPPY_BASE + reg_fifo);
|
||||
}
|
||||
|
||||
static void ack_irq(void)
|
||||
{
|
||||
g_assert(get_irq(FLOPPY_IRQ));
|
||||
floppy_send(CMD_SENSE_INT);
|
||||
floppy_recv();
|
||||
floppy_recv();
|
||||
g_assert(!get_irq(FLOPPY_IRQ));
|
||||
}
|
||||
|
||||
static void send_step_pulse(void)
|
||||
{
|
||||
int drive = 0;
|
||||
int head = 0;
|
||||
static int cyl = 0;
|
||||
|
||||
floppy_send(CMD_SEEK);
|
||||
floppy_send(head << 2 | drive);
|
||||
g_assert(!get_irq(FLOPPY_IRQ));
|
||||
floppy_send(cyl);
|
||||
ack_irq();
|
||||
|
||||
cyl = (cyl + 1) % 4;
|
||||
}
|
||||
|
||||
static void test_media_change(void)
|
||||
{
|
||||
uint8_t dir;
|
||||
|
||||
/* Media changed bit must be up-to-date after step pulse. Do two SEEKs
|
||||
* because we may already happen to be on the right cylinder initially. */
|
||||
send_step_pulse();
|
||||
send_step_pulse();
|
||||
dir = inb(FLOPPY_BASE + reg_dir);
|
||||
assert_bit_clear(dir, DSKCHG);
|
||||
|
||||
/* Eject the floppy and check that DSKCHG is set. Reading it out doesn't
|
||||
* reset the bit. */
|
||||
qmp("{'execute':'eject', 'arguments':{ 'device':'floppy0' }}");
|
||||
qmp(""); /* ignore event */
|
||||
|
||||
dir = inb(FLOPPY_BASE + reg_dir);
|
||||
assert_bit_set(dir, DSKCHG);
|
||||
dir = inb(FLOPPY_BASE + reg_dir);
|
||||
assert_bit_set(dir, DSKCHG);
|
||||
|
||||
send_step_pulse();
|
||||
dir = inb(FLOPPY_BASE + reg_dir);
|
||||
assert_bit_set(dir, DSKCHG);
|
||||
dir = inb(FLOPPY_BASE + reg_dir);
|
||||
assert_bit_set(dir, DSKCHG);
|
||||
|
||||
/* And then insert it again. DSKCHK should not be reset until a step pulse
|
||||
* is sent. */
|
||||
qmp("{'execute':'change', 'arguments':{ 'device':'floppy0', "
|
||||
"'target': '%s' }}", test_image);
|
||||
qmp(""); /* ignore event (FIXME open -> open transition?!) */
|
||||
qmp(""); /* ignore event */
|
||||
|
||||
dir = inb(FLOPPY_BASE + reg_dir);
|
||||
assert_bit_set(dir, DSKCHG);
|
||||
dir = inb(FLOPPY_BASE + reg_dir);
|
||||
assert_bit_set(dir, DSKCHG);
|
||||
|
||||
send_step_pulse();
|
||||
dir = inb(FLOPPY_BASE + reg_dir);
|
||||
assert_bit_clear(dir, DSKCHG);
|
||||
dir = inb(FLOPPY_BASE + reg_dir);
|
||||
assert_bit_clear(dir, DSKCHG);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *arch = qtest_get_arch();
|
||||
char *cmdline;
|
||||
int fd;
|
||||
int ret;
|
||||
|
||||
/* Check architecture */
|
||||
if (strcmp(arch, "i386") && strcmp(arch, "x86_64")) {
|
||||
g_test_message("Skipping test for non-x86\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Create a temporary raw image */
|
||||
fd = mkstemp(test_image);
|
||||
g_assert(fd >= 0);
|
||||
ret = ftruncate(fd, TEST_IMAGE_SIZE);
|
||||
g_assert(ret == 0);
|
||||
close(fd);
|
||||
|
||||
/* Run the tests */
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
cmdline = g_strdup_printf("-vnc none "
|
||||
"-drive file=%s,if=floppy,cache=writeback ",
|
||||
test_image);
|
||||
|
||||
qtest_start(cmdline);
|
||||
qtest_irq_intercept_in(global_qtest, "ioapic");
|
||||
qtest_add_func("/fdc/media_change", test_media_change);
|
||||
|
||||
ret = g_test_run();
|
||||
|
||||
/* Cleanup */
|
||||
qtest_quit(global_qtest);
|
||||
unlink(test_image);
|
||||
|
||||
return ret;
|
||||
}
|
||||
+106
-37
@@ -36,6 +36,7 @@ QTestState *global_qtest;
|
||||
struct QTestState
|
||||
{
|
||||
int fd;
|
||||
int qmp_fd;
|
||||
bool irq_level[MAX_IRQ];
|
||||
GString *rx;
|
||||
gchar *pid_file;
|
||||
@@ -45,25 +46,11 @@ struct QTestState
|
||||
g_assert_cmpint(ret, !=, -1); \
|
||||
} while (0)
|
||||
|
||||
QTestState *qtest_init(const char *extra_args)
|
||||
static int init_socket(const char *socket_path)
|
||||
{
|
||||
QTestState *s;
|
||||
struct sockaddr_un addr;
|
||||
int sock, ret, i;
|
||||
gchar *socket_path;
|
||||
gchar *pid_file;
|
||||
gchar *command;
|
||||
const char *qemu_binary;
|
||||
pid_t pid;
|
||||
socklen_t addrlen;
|
||||
|
||||
qemu_binary = getenv("QTEST_QEMU_BINARY");
|
||||
g_assert(qemu_binary != NULL);
|
||||
|
||||
socket_path = g_strdup_printf("/tmp/qtest-%d.sock", getpid());
|
||||
pid_file = g_strdup_printf("/tmp/qtest-%d.pid", getpid());
|
||||
|
||||
s = g_malloc(sizeof(*s));
|
||||
int sock;
|
||||
int ret;
|
||||
|
||||
sock = socket(PF_UNIX, SOCK_STREAM, 0);
|
||||
g_assert_no_errno(sock);
|
||||
@@ -78,21 +65,14 @@ QTestState *qtest_init(const char *extra_args)
|
||||
g_assert_no_errno(ret);
|
||||
listen(sock, 1);
|
||||
|
||||
pid = fork();
|
||||
if (pid == 0) {
|
||||
command = g_strdup_printf("%s "
|
||||
"-qtest unix:%s,nowait "
|
||||
"-qtest-log /dev/null "
|
||||
"-pidfile %s "
|
||||
"-machine accel=qtest "
|
||||
"%s", qemu_binary, socket_path,
|
||||
pid_file,
|
||||
extra_args ?: "");
|
||||
return sock;
|
||||
}
|
||||
|
||||
ret = system(command);
|
||||
exit(ret);
|
||||
g_free(command);
|
||||
}
|
||||
static int socket_accept(int sock)
|
||||
{
|
||||
struct sockaddr_un addr;
|
||||
socklen_t addrlen;
|
||||
int ret;
|
||||
|
||||
do {
|
||||
ret = accept(sock, (struct sockaddr *)&addr, &addrlen);
|
||||
@@ -100,7 +80,52 @@ QTestState *qtest_init(const char *extra_args)
|
||||
g_assert_no_errno(ret);
|
||||
close(sock);
|
||||
|
||||
s->fd = ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
QTestState *qtest_init(const char *extra_args)
|
||||
{
|
||||
QTestState *s;
|
||||
int sock, qmpsock, ret, i;
|
||||
gchar *socket_path;
|
||||
gchar *qmp_socket_path;
|
||||
gchar *pid_file;
|
||||
gchar *command;
|
||||
const char *qemu_binary;
|
||||
pid_t pid;
|
||||
|
||||
qemu_binary = getenv("QTEST_QEMU_BINARY");
|
||||
g_assert(qemu_binary != NULL);
|
||||
|
||||
socket_path = g_strdup_printf("/tmp/qtest-%d.sock", getpid());
|
||||
qmp_socket_path = g_strdup_printf("/tmp/qtest-%d.qmp", getpid());
|
||||
pid_file = g_strdup_printf("/tmp/qtest-%d.pid", getpid());
|
||||
|
||||
s = g_malloc(sizeof(*s));
|
||||
|
||||
sock = init_socket(socket_path);
|
||||
qmpsock = init_socket(qmp_socket_path);
|
||||
|
||||
pid = fork();
|
||||
if (pid == 0) {
|
||||
command = g_strdup_printf("%s "
|
||||
"-qtest unix:%s,nowait "
|
||||
"-qtest-log /dev/null "
|
||||
"-qmp unix:%s,nowait "
|
||||
"-pidfile %s "
|
||||
"-machine accel=qtest "
|
||||
"%s", qemu_binary, socket_path,
|
||||
qmp_socket_path, pid_file,
|
||||
extra_args ?: "");
|
||||
|
||||
ret = system(command);
|
||||
exit(ret);
|
||||
g_free(command);
|
||||
}
|
||||
|
||||
s->fd = socket_accept(sock);
|
||||
s->qmp_fd = socket_accept(qmpsock);
|
||||
|
||||
s->rx = g_string_new("");
|
||||
s->pid_file = pid_file;
|
||||
for (i = 0; i < MAX_IRQ; i++) {
|
||||
@@ -108,6 +133,11 @@ QTestState *qtest_init(const char *extra_args)
|
||||
}
|
||||
|
||||
g_free(socket_path);
|
||||
g_free(qmp_socket_path);
|
||||
|
||||
/* Read the QMP greeting and then do the handshake */
|
||||
qtest_qmp(s, "");
|
||||
qtest_qmp(s, "{ 'execute': 'qmp_capabilities' }");
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -131,22 +161,19 @@ void qtest_quit(QTestState *s)
|
||||
}
|
||||
}
|
||||
|
||||
static void GCC_FMT_ATTR(2, 3) qtest_sendf(QTestState *s, const char *fmt, ...)
|
||||
static void socket_sendf(int fd, const char *fmt, va_list ap)
|
||||
{
|
||||
va_list ap;
|
||||
gchar *str;
|
||||
size_t size, offset;
|
||||
|
||||
va_start(ap, fmt);
|
||||
str = g_strdup_vprintf(fmt, ap);
|
||||
va_end(ap);
|
||||
size = strlen(str);
|
||||
|
||||
offset = 0;
|
||||
while (offset < size) {
|
||||
ssize_t len;
|
||||
|
||||
len = write(s->fd, str + offset, size - offset);
|
||||
len = write(fd, str + offset, size - offset);
|
||||
if (len == -1 && errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
@@ -158,6 +185,15 @@ static void GCC_FMT_ATTR(2, 3) qtest_sendf(QTestState *s, const char *fmt, ...)
|
||||
}
|
||||
}
|
||||
|
||||
static void GCC_FMT_ATTR(2, 3) qtest_sendf(QTestState *s, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
socket_sendf(s->fd, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
static GString *qtest_recv_line(QTestState *s)
|
||||
{
|
||||
GString *line;
|
||||
@@ -233,6 +269,39 @@ redo:
|
||||
return words;
|
||||
}
|
||||
|
||||
void qtest_qmp(QTestState *s, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
bool has_reply = false;
|
||||
int nesting = 0;
|
||||
|
||||
/* Send QMP request */
|
||||
va_start(ap, fmt);
|
||||
socket_sendf(s->qmp_fd, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
/* Receive reply */
|
||||
while (!has_reply || nesting > 0) {
|
||||
ssize_t len;
|
||||
char c;
|
||||
|
||||
len = read(s->qmp_fd, &c, 1);
|
||||
if (len == -1 && errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
case '{':
|
||||
nesting++;
|
||||
has_reply = true;
|
||||
break;
|
||||
case '}':
|
||||
nesting--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char *qtest_get_arch(void)
|
||||
{
|
||||
const char *qemu = getenv("QTEST_QEMU_BINARY");
|
||||
|
||||
@@ -37,6 +37,15 @@ QTestState *qtest_init(const char *extra_args);
|
||||
*/
|
||||
void qtest_quit(QTestState *s);
|
||||
|
||||
/**
|
||||
* qtest_qmp:
|
||||
* @s: QTestState instance to operate on.
|
||||
* @fmt...: QMP message to send to qemu
|
||||
*
|
||||
* Sends a QMP message to QEMU
|
||||
*/
|
||||
void qtest_qmp(QTestState *s, const char *fmt, ...);
|
||||
|
||||
/**
|
||||
* qtest_get_irq:
|
||||
* @s: QTestState instance to operate on.
|
||||
@@ -206,6 +215,14 @@ void qtest_add_func(const char *str, void (*fn));
|
||||
global_qtest = qtest_init((args)) \
|
||||
)
|
||||
|
||||
/**
|
||||
* qmp:
|
||||
* @fmt...: QMP message to send to qemu
|
||||
*
|
||||
* Sends a QMP message to QEMU
|
||||
*/
|
||||
#define qmp(fmt, ...) qtest_qmp(global_qtest, fmt, ## __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* get_irq:
|
||||
* @num: Interrupt to observe.
|
||||
|
||||
@@ -15,9 +15,9 @@ read 134217728/134217728 bytes at offset 0
|
||||
|
||||
unaligned pwrite
|
||||
wrote 42/42 bytes at offset 66
|
||||
42.000000 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
42 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
|
||||
verify pattern
|
||||
read 42/42 bytes at offset 66
|
||||
42.000000 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
42 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
*** done
|
||||
|
||||
@@ -5,5 +5,5 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728
|
||||
|
||||
== read from read-only image
|
||||
read 512/512 bytes at offset 0
|
||||
512.000000 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
*** done
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user