From cf8074b3825f7229a20c60e679511592bde41340 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Fri, 5 Apr 2013 21:27:53 +0200 Subject: block: Introduce bdrv_writev_vmstate Signed-off-by: Kevin Wolf Signed-off-by: Stefan Hajnoczi --- block.c | 25 ++++++++++++++++++++----- block/qcow2.c | 12 +++++++++--- block/sheepdog.c | 13 ++++++++++--- include/block/block.h | 1 + include/block/block_int.h | 4 ++-- 5 files changed, 42 insertions(+), 13 deletions(-) diff --git a/block.c b/block.c index 602d8a443e..175497e678 100644 --- a/block.c +++ b/block.c @@ -3183,14 +3183,29 @@ int bdrv_get_info(BlockDriverState *bs, BlockDriverInfo *bdi) int bdrv_save_vmstate(BlockDriverState *bs, const uint8_t *buf, int64_t pos, int size) +{ + QEMUIOVector qiov; + struct iovec iov = { + .iov_base = (void *) buf, + .iov_len = size, + }; + + qemu_iovec_init_external(&qiov, &iov, 1); + return bdrv_writev_vmstate(bs, &qiov, pos); +} + +int bdrv_writev_vmstate(BlockDriverState *bs, QEMUIOVector *qiov, int64_t pos) { BlockDriver *drv = bs->drv; - if (!drv) + + if (!drv) { return -ENOMEDIUM; - if (drv->bdrv_save_vmstate) - return drv->bdrv_save_vmstate(bs, buf, pos, size); - if (bs->file) - return bdrv_save_vmstate(bs->file, buf, pos, size); + } else if (drv->bdrv_save_vmstate) { + return drv->bdrv_save_vmstate(bs, qiov, pos); + } else if (bs->file) { + return bdrv_writev_vmstate(bs->file, qiov, pos); + } + return -ENOTSUP; } diff --git a/block/qcow2.c b/block/qcow2.c index 1d180732e9..1d856af80f 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -1652,18 +1652,24 @@ static void dump_refcounts(BlockDriverState *bs) } #endif -static int qcow2_save_vmstate(BlockDriverState *bs, const uint8_t *buf, - int64_t pos, int size) +static int qcow2_save_vmstate(BlockDriverState *bs, QEMUIOVector *qiov, + int64_t pos) { BDRVQcowState *s = bs->opaque; int growable = bs->growable; int ret; + void *buf; + + buf = qemu_blockalign(bs, qiov->size); + qemu_iovec_to_buf(qiov, 0, buf, qiov->size); BLKDBG_EVENT(bs->file, BLKDBG_VMSTATE_SAVE); bs->growable = 1; - ret = bdrv_pwrite(bs, qcow2_vm_state_offset(s) + pos, buf, size); + ret = bdrv_pwrite(bs, qcow2_vm_state_offset(s) + pos, buf, qiov->size); bs->growable = growable; + qemu_vfree(buf); + return ret; } diff --git a/block/sheepdog.c b/block/sheepdog.c index 987018e20c..1c5b53220b 100644 --- a/block/sheepdog.c +++ b/block/sheepdog.c @@ -2054,12 +2054,19 @@ cleanup: return ret; } -static int sd_save_vmstate(BlockDriverState *bs, const uint8_t *data, - int64_t pos, int size) +static int sd_save_vmstate(BlockDriverState *bs, QEMUIOVector *qiov, + int64_t pos) { BDRVSheepdogState *s = bs->opaque; + void *buf; + int ret; - return do_load_save_vmstate(s, (uint8_t *)data, pos, size, 0); + buf = qemu_blockalign(bs, qiov->size); + qemu_iovec_to_buf(qiov, 0, buf, qiov->size); + ret = do_load_save_vmstate(s, (uint8_t *) buf, pos, qiov->size, 0); + qemu_vfree(buf); + + return ret; } static int sd_load_vmstate(BlockDriverState *bs, uint8_t *data, diff --git a/include/block/block.h b/include/block/block.h index 9dc6aada99..e5fc566fbc 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -348,6 +348,7 @@ void path_combine(char *dest, int dest_size, const char *base_path, const char *filename); +int bdrv_writev_vmstate(BlockDriverState *bs, QEMUIOVector *qiov, int64_t pos); int bdrv_save_vmstate(BlockDriverState *bs, const uint8_t *buf, int64_t pos, int size); diff --git a/include/block/block_int.h b/include/block/block_int.h index 9aa98b5d12..458cde3766 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -164,8 +164,8 @@ struct BlockDriver { const char *snapshot_name); int (*bdrv_get_info)(BlockDriverState *bs, BlockDriverInfo *bdi); - int (*bdrv_save_vmstate)(BlockDriverState *bs, const uint8_t *buf, - int64_t pos, int size); + int (*bdrv_save_vmstate)(BlockDriverState *bs, QEMUIOVector *qiov, + int64_t pos); int (*bdrv_load_vmstate)(BlockDriverState *bs, uint8_t *buf, int64_t pos, int size); -- cgit v1.2.3 From 05fcc84888cfabec2267851415b66395a73a0f5f Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Fri, 5 Apr 2013 21:27:54 +0200 Subject: savevm: Implement block_writev_buffer() Instead of breaking up RAM state into many small chunks, pass the iovec to the block layer for better performance. Signed-off-by: Kevin Wolf Signed-off-by: Stefan Hajnoczi --- include/migration/qemu-file.h | 2 +- savevm.c | 25 +++++++++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/include/migration/qemu-file.h b/include/migration/qemu-file.h index 623c434b15..7519464192 100644 --- a/include/migration/qemu-file.h +++ b/include/migration/qemu-file.h @@ -55,7 +55,7 @@ typedef int (QEMUFileGetFD)(void *opaque); * This function writes an iovec to file. */ typedef ssize_t (QEMUFileWritevBufferFunc)(void *opaque, struct iovec *iov, - int iovcnt); + int iovcnt, int64_t pos); typedef struct QEMUFileOps { QEMUFilePutBufferFunc *put_buffer; diff --git a/savevm.c b/savevm.c index b1d8988c78..88fe985f93 100644 --- a/savevm.c +++ b/savevm.c @@ -176,7 +176,8 @@ static void coroutine_fn yield_until_fd_readable(int fd) qemu_coroutine_yield(); } -static ssize_t socket_writev_buffer(void *opaque, struct iovec *iov, int iovcnt) +static ssize_t socket_writev_buffer(void *opaque, struct iovec *iov, int iovcnt, + int64_t pos) { QEMUFileSocket *s = opaque; ssize_t len; @@ -458,6 +459,21 @@ fail: return NULL; } +static ssize_t block_writev_buffer(void *opaque, struct iovec *iov, int iovcnt, + int64_t pos) +{ + int ret; + QEMUIOVector qiov; + + qemu_iovec_init_external(&qiov, iov, iovcnt); + ret = bdrv_writev_vmstate(opaque, &qiov, pos); + if (ret < 0) { + return ret; + } + + return qiov.size; +} + static int block_put_buffer(void *opaque, const uint8_t *buf, int64_t pos, int size) { @@ -481,8 +497,9 @@ static const QEMUFileOps bdrv_read_ops = { }; static const QEMUFileOps bdrv_write_ops = { - .put_buffer = block_put_buffer, - .close = bdrv_fclose + .put_buffer = block_put_buffer, + .writev_buffer = block_writev_buffer, + .close = bdrv_fclose }; static QEMUFile *qemu_fopen_bdrv(BlockDriverState *bs, int is_writable) @@ -533,7 +550,7 @@ static void qemu_fflush(QEMUFile *f) if (f->is_write && f->iovcnt > 0) { if (f->ops->writev_buffer) { - ret = f->ops->writev_buffer(f->opaque, f->iov, f->iovcnt); + ret = f->ops->writev_buffer(f->opaque, f->iov, f->iovcnt, f->pos); if (ret >= 0) { f->pos += ret; } -- cgit v1.2.3 From 8d3b1a2d0b34a95800c482e1414c63f469ac4973 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Fri, 5 Apr 2013 21:27:55 +0200 Subject: block: Introduce bdrv_pwritev() for qcow2_save_vmstate Directly pass the QEMUIOVector on instead of linearising it. Signed-off-by: Kevin Wolf Signed-off-by: Stefan Hajnoczi --- block.c | 80 +++++++++++++++++++++++++++++++++++++-------------- block/qcow2.c | 8 +----- include/block/block.h | 2 ++ 3 files changed, 61 insertions(+), 29 deletions(-) diff --git a/block.c b/block.c index 175497e678..4ad663d3fa 100644 --- a/block.c +++ b/block.c @@ -2131,27 +2131,21 @@ static void coroutine_fn bdrv_rw_co_entry(void *opaque) } /* - * Process a synchronous request using coroutines + * Process a vectored synchronous request using coroutines */ -static int bdrv_rw_co(BlockDriverState *bs, int64_t sector_num, uint8_t *buf, - int nb_sectors, bool is_write) +static int bdrv_rwv_co(BlockDriverState *bs, int64_t sector_num, + QEMUIOVector *qiov, bool is_write) { - QEMUIOVector qiov; - struct iovec iov = { - .iov_base = (void *)buf, - .iov_len = nb_sectors * BDRV_SECTOR_SIZE, - }; Coroutine *co; RwCo rwco = { .bs = bs, .sector_num = sector_num, - .nb_sectors = nb_sectors, - .qiov = &qiov, + .nb_sectors = qiov->size >> BDRV_SECTOR_BITS, + .qiov = qiov, .is_write = is_write, .ret = NOT_DONE, }; - - qemu_iovec_init_external(&qiov, &iov, 1); + assert((qiov->size & (BDRV_SECTOR_SIZE - 1)) == 0); /** * In sync call context, when the vcpu is blocked, this throttling timer @@ -2177,6 +2171,22 @@ static int bdrv_rw_co(BlockDriverState *bs, int64_t sector_num, uint8_t *buf, return rwco.ret; } +/* + * Process a synchronous request using coroutines + */ +static int bdrv_rw_co(BlockDriverState *bs, int64_t sector_num, uint8_t *buf, + int nb_sectors, bool is_write) +{ + QEMUIOVector qiov; + struct iovec iov = { + .iov_base = (void *)buf, + .iov_len = nb_sectors * BDRV_SECTOR_SIZE, + }; + + qemu_iovec_init_external(&qiov, &iov, 1); + return bdrv_rwv_co(bs, sector_num, &qiov, is_write); +} + /* return < 0 if error. See bdrv_write() for the return codes */ int bdrv_read(BlockDriverState *bs, int64_t sector_num, uint8_t *buf, int nb_sectors) @@ -2210,6 +2220,11 @@ int bdrv_write(BlockDriverState *bs, int64_t sector_num, return bdrv_rw_co(bs, sector_num, (uint8_t *)buf, nb_sectors, true); } +int bdrv_writev(BlockDriverState *bs, int64_t sector_num, QEMUIOVector *qiov) +{ + return bdrv_rwv_co(bs, sector_num, qiov, true); +} + int bdrv_pread(BlockDriverState *bs, int64_t offset, void *buf, int count1) { @@ -2255,15 +2270,15 @@ int bdrv_pread(BlockDriverState *bs, int64_t offset, return count1; } -int bdrv_pwrite(BlockDriverState *bs, int64_t offset, - const void *buf, int count1) +int bdrv_pwritev(BlockDriverState *bs, int64_t offset, QEMUIOVector *qiov) { uint8_t tmp_buf[BDRV_SECTOR_SIZE]; int len, nb_sectors, count; int64_t sector_num; int ret; - count = count1; + count = qiov->size; + /* first write to align to sector start */ len = (BDRV_SECTOR_SIZE - offset) & (BDRV_SECTOR_SIZE - 1); if (len > count) @@ -2272,24 +2287,32 @@ int bdrv_pwrite(BlockDriverState *bs, int64_t offset, if (len > 0) { if ((ret = bdrv_read(bs, sector_num, tmp_buf, 1)) < 0) return ret; - memcpy(tmp_buf + (offset & (BDRV_SECTOR_SIZE - 1)), buf, len); + qemu_iovec_to_buf(qiov, 0, tmp_buf + (offset & (BDRV_SECTOR_SIZE - 1)), + len); if ((ret = bdrv_write(bs, sector_num, tmp_buf, 1)) < 0) return ret; count -= len; if (count == 0) - return count1; + return qiov->size; sector_num++; - buf += len; } /* write the sectors "in place" */ nb_sectors = count >> BDRV_SECTOR_BITS; if (nb_sectors > 0) { - if ((ret = bdrv_write(bs, sector_num, buf, nb_sectors)) < 0) + QEMUIOVector qiov_inplace; + + qemu_iovec_init(&qiov_inplace, qiov->niov); + qemu_iovec_concat(&qiov_inplace, qiov, len, + nb_sectors << BDRV_SECTOR_BITS); + ret = bdrv_writev(bs, sector_num, &qiov_inplace); + qemu_iovec_destroy(&qiov_inplace); + if (ret < 0) { return ret; + } + sector_num += nb_sectors; len = nb_sectors << BDRV_SECTOR_BITS; - buf += len; count -= len; } @@ -2297,11 +2320,24 @@ int bdrv_pwrite(BlockDriverState *bs, int64_t offset, if (count > 0) { if ((ret = bdrv_read(bs, sector_num, tmp_buf, 1)) < 0) return ret; - memcpy(tmp_buf, buf, count); + qemu_iovec_to_buf(qiov, qiov->size - count, tmp_buf, count); if ((ret = bdrv_write(bs, sector_num, tmp_buf, 1)) < 0) return ret; } - return count1; + return qiov->size; +} + +int bdrv_pwrite(BlockDriverState *bs, int64_t offset, + const void *buf, int count1) +{ + QEMUIOVector qiov; + struct iovec iov = { + .iov_base = (void *) buf, + .iov_len = count1, + }; + + qemu_iovec_init_external(&qiov, &iov, 1); + return bdrv_pwritev(bs, offset, &qiov); } /* diff --git a/block/qcow2.c b/block/qcow2.c index 1d856af80f..e8934de185 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -1658,18 +1658,12 @@ static int qcow2_save_vmstate(BlockDriverState *bs, QEMUIOVector *qiov, BDRVQcowState *s = bs->opaque; int growable = bs->growable; int ret; - void *buf; - - buf = qemu_blockalign(bs, qiov->size); - qemu_iovec_to_buf(qiov, 0, buf, qiov->size); BLKDBG_EVENT(bs->file, BLKDBG_VMSTATE_SAVE); bs->growable = 1; - ret = bdrv_pwrite(bs, qcow2_vm_state_offset(s) + pos, buf, qiov->size); + ret = bdrv_pwritev(bs, qcow2_vm_state_offset(s) + pos, qiov); bs->growable = growable; - qemu_vfree(buf); - return ret; } diff --git a/include/block/block.h b/include/block/block.h index e5fc566fbc..ebd95127a1 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -166,10 +166,12 @@ int bdrv_read_unthrottled(BlockDriverState *bs, int64_t sector_num, uint8_t *buf, int nb_sectors); int bdrv_write(BlockDriverState *bs, int64_t sector_num, const uint8_t *buf, int nb_sectors); +int bdrv_writev(BlockDriverState *bs, int64_t sector_num, QEMUIOVector *qiov); int bdrv_pread(BlockDriverState *bs, int64_t offset, void *buf, int count); int bdrv_pwrite(BlockDriverState *bs, int64_t offset, const void *buf, int count); +int bdrv_pwritev(BlockDriverState *bs, int64_t offset, QEMUIOVector *qiov); 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, -- cgit v1.2.3 From cf07aecf955453de7570c66fa0e582c83f2aac4c Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Fri, 5 Apr 2013 21:27:56 +0200 Subject: qemu-iotests: A few more bdrv_pread/pwrite tests Signed-off-by: Kevin Wolf Signed-off-by: Stefan Hajnoczi --- tests/qemu-iotests/002 | 13 +++++++++++++ tests/qemu-iotests/002.out | 26 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/tests/qemu-iotests/002 b/tests/qemu-iotests/002 index bebed84fd9..51d0a8f4ad 100755 --- a/tests/qemu-iotests/002 +++ b/tests/qemu-iotests/002 @@ -61,10 +61,23 @@ $QEMU_IO -c "read -pP 0xa 0 $size" $TEST_IMG | _filter_qemu_io echo echo "unaligned pwrite" $QEMU_IO -c 'write -pP 0xab 66 42' $TEST_IMG | _filter_qemu_io +$QEMU_IO -c 'write -pP 0xac 512 288' $TEST_IMG | _filter_qemu_io +$QEMU_IO -c 'write -pP 0xad 800 224' $TEST_IMG | _filter_qemu_io +$QEMU_IO -c 'write -pP 0xae 66000 128k' $TEST_IMG | _filter_qemu_io +$QEMU_IO -c 'write -pP 0xaf 256k 42' $TEST_IMG | _filter_qemu_io echo echo "verify pattern" +$QEMU_IO -c 'read -pP 0xa 0 66' $TEST_IMG | _filter_qemu_io $QEMU_IO -c 'read -pP 0xab 66 42' $TEST_IMG | _filter_qemu_io +$QEMU_IO -c 'read -pP 0xa 108 404' $TEST_IMG | _filter_qemu_io +$QEMU_IO -c 'read -pP 0xac 512 288' $TEST_IMG | _filter_qemu_io +$QEMU_IO -c 'read -pP 0xad 800 224' $TEST_IMG | _filter_qemu_io +$QEMU_IO -c 'read -pP 0xa 1k 64976' $TEST_IMG | _filter_qemu_io +$QEMU_IO -c 'read -pP 0xae 66000 128k' $TEST_IMG | _filter_qemu_io +$QEMU_IO -c 'read -pP 0xa 197072 65072' $TEST_IMG | _filter_qemu_io +$QEMU_IO -c 'read -pP 0xaf 256k 42' $TEST_IMG | _filter_qemu_io +$QEMU_IO -c 'read -pP 0xa 262186 470' $TEST_IMG | _filter_qemu_io # success, all done echo "*** done" diff --git a/tests/qemu-iotests/002.out b/tests/qemu-iotests/002.out index 75f5876e88..cd6aa0f9fa 100644 --- a/tests/qemu-iotests/002.out +++ b/tests/qemu-iotests/002.out @@ -16,8 +16,34 @@ read 134217728/134217728 bytes at offset 0 unaligned pwrite wrote 42/42 bytes at offset 66 42 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 288/288 bytes at offset 512 +288 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 224/224 bytes at offset 800 +224 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 131072/131072 bytes at offset 66000 +128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 42/42 bytes at offset 262144 +42 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) verify pattern +read 66/66 bytes at offset 0 +66 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) read 42/42 bytes at offset 66 42 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 404/404 bytes at offset 108 +404 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 288/288 bytes at offset 512 +288 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 224/224 bytes at offset 800 +224 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 64976/64976 bytes at offset 1024 +63.453 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 131072/131072 bytes at offset 66000 +128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65072/65072 bytes at offset 197072 +63.547 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 42/42 bytes at offset 262144 +42 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 470/470 bytes at offset 262186 +470 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) *** done -- cgit v1.2.3 From 47e5df2146e8b6cd1c093720461928e66f824222 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Tue, 9 Apr 2013 15:56:13 +0200 Subject: qemu-iotests: Add test for -drive options Signed-off-by: Kevin Wolf Signed-off-by: Stefan Hajnoczi --- tests/qemu-iotests/051 | 148 +++++++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/051.out | 162 +++++++++++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/group | 1 + 3 files changed, 311 insertions(+) create mode 100755 tests/qemu-iotests/051 create mode 100644 tests/qemu-iotests/051.out diff --git a/tests/qemu-iotests/051 b/tests/qemu-iotests/051 new file mode 100755 index 0000000000..17a1d92a10 --- /dev/null +++ b/tests/qemu-iotests/051 @@ -0,0 +1,148 @@ +#!/bin/bash +# +# Test command line configuration of block devices and driver-specific options +# +# Copyright (C) 2013 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +# creator +owner=kwolf@redhat.com + +seq=`basename $0` +echo "QA output created by $seq" + +here=`pwd` +tmp=/tmp/$$ +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +_supported_fmt qcow2 +_supported_proto file +_supported_os Linux + +function do_run_qemu() +{ + echo Testing: "$@" + echo quit | $QEMU -nographic -monitor stdio -serial none "$@" + echo +} + +function run_qemu() +{ + do_run_qemu "$@" 2>&1 | _filter_testdir +} + +size=128M + +_make_test_img $size + +echo +echo === Unknown option === +echo + +run_qemu -drive file=$TEST_IMG,format=qcow2,unknown_opt= +run_qemu -drive file=$TEST_IMG,format=qcow2,unknown_opt=on +run_qemu -drive file=$TEST_IMG,format=qcow2,unknown_opt=1234 +run_qemu -drive file=$TEST_IMG,format=qcow2,unknown_opt=foo + + +echo +echo === Enable and disable lazy refcounting on the command line, plus some invalid values === +echo + +run_qemu -drive file=$TEST_IMG,format=qcow2,lazy_refcounts=on +run_qemu -drive file=$TEST_IMG,format=qcow2,lazy_refcounts=off +run_qemu -drive file=$TEST_IMG,format=qcow2,lazy_refcounts= +run_qemu -drive file=$TEST_IMG,format=qcow2,lazy_refcounts=42 +run_qemu -drive file=$TEST_IMG,format=qcow2,lazy_refcounts=foo + + +echo +echo === With version 2 images enabling lazy refcounts must fail === +echo + +_make_test_img -ocompat=0.10 $size + +run_qemu -drive file=$TEST_IMG,format=qcow2,lazy_refcounts=on +run_qemu -drive file=$TEST_IMG,format=qcow2,lazy_refcounts=off + +echo +echo === No medium === +echo + +run_qemu -drive if=floppy +run_qemu -drive if=ide,media=cdrom +run_qemu -drive if=scsi,media=cdrom + +run_qemu -drive if=ide +run_qemu -drive if=virtio +run_qemu -drive if=scsi + +run_qemu -drive if=none,id=disk -device ide-cd,drive=disk +run_qemu -drive if=none,id=disk -device lsi53c895a -device scsi-cd,drive=disk + +run_qemu -drive if=none,id=disk -device ide-drive,drive=disk +run_qemu -drive if=none,id=disk -device ide-hd,drive=disk +run_qemu -drive if=none,id=disk -device lsi53c895a -device scsi-disk,drive=disk +run_qemu -drive if=none,id=disk -device lsi53c895a -device scsi-hd,drive=disk + +echo +echo === Read-only === +echo + +run_qemu -drive file=$TEST_IMG,if=floppy,readonly=on +run_qemu -drive file=$TEST_IMG,if=ide,media=cdrom,readonly=on +run_qemu -drive file=$TEST_IMG,if=scsi,media=cdrom,readonly=on + +run_qemu -drive file=$TEST_IMG,if=ide,readonly=on +run_qemu -drive file=$TEST_IMG,if=virtio,readonly=on +run_qemu -drive file=$TEST_IMG,if=scsi,readonly=on + +run_qemu -drive file=$TEST_IMG,if=none,id=disk,readonly=on -device ide-cd,drive=disk +run_qemu -drive file=$TEST_IMG,if=none,id=disk,readonly=on -device lsi53c895a -device scsi-cd,drive=disk + +run_qemu -drive file=$TEST_IMG,if=none,id=disk,readonly=on -device ide-drive,drive=disk +run_qemu -drive file=$TEST_IMG,if=none,id=disk,readonly=on -device ide-hd,drive=disk +run_qemu -drive file=$TEST_IMG,if=none,id=disk,readonly=on -device lsi53c895a -device scsi-disk,drive=disk +run_qemu -drive file=$TEST_IMG,if=none,id=disk,readonly=on -device lsi53c895a -device scsi-hd,drive=disk + +echo +echo === Cache modes === +echo + +# Cannot use the test image because cache=none might not work on the host FS +# Use cdrom so that we won't get errors about missing media + +run_qemu -drive media=cdrom,cache=none +run_qemu -drive media=cdrom,cache=directsync +run_qemu -drive media=cdrom,cache=writeback +run_qemu -drive media=cdrom,cache=writethrough +run_qemu -drive media=cdrom,cache=unsafe +run_qemu -drive media=cdrom,cache=invalid_value + +# success, all done +echo "*** done" +rm -f $seq.full +status=0 diff --git a/tests/qemu-iotests/051.out b/tests/qemu-iotests/051.out new file mode 100644 index 0000000000..c20618615d --- /dev/null +++ b/tests/qemu-iotests/051.out @@ -0,0 +1,162 @@ +QA output created by 051 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 + +=== Unknown option === + +Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt= +qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=: Block format 'qcow2' used by device 'ide0-hd0' doesn't support the option 'unknown_opt' +qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=: could not open disk image TEST_DIR/t.qcow2: Invalid argument + +Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=on +qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=on: Block format 'qcow2' used by device 'ide0-hd0' doesn't support the option 'unknown_opt' +qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=on: could not open disk image TEST_DIR/t.qcow2: Invalid argument + +Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=1234 +qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=1234: Block format 'qcow2' used by device 'ide0-hd0' doesn't support the option 'unknown_opt' +qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=1234: could not open disk image TEST_DIR/t.qcow2: Invalid argument + +Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=foo +qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=foo: Block format 'qcow2' used by device 'ide0-hd0' doesn't support the option 'unknown_opt' +qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=foo: could not open disk image TEST_DIR/t.qcow2: Invalid argument + + +=== Enable and disable lazy refcounting on the command line, plus some invalid values === + +Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=on +qququiquit + +Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=off +qququiquit + +Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts= +qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=: Parameter 'lazy_refcounts' expects 'on' or 'off' +qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=: could not open disk image TEST_DIR/t.qcow2: Invalid argument + +Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=42 +qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=42: Parameter 'lazy_refcounts' expects 'on' or 'off' +qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=42: could not open disk image TEST_DIR/t.qcow2: Invalid argument + +Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=foo +qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=foo: Parameter 'lazy_refcounts' expects 'on' or 'off' +qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=foo: could not open disk image TEST_DIR/t.qcow2: Invalid argument + + +=== With version 2 images enabling lazy refcounts must fail === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 +Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=on +qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=on: Lazy refcounts require a qcow2 image with at least qemu 1.1 compatibility level +qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=on: could not open disk image TEST_DIR/t.qcow2: Invalid argument + +Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=off +qququiquit + + +=== No medium === + +Testing: -drive if=floppy +qququiquit + +Testing: -drive if=ide,media=cdrom +qququiquit + +Testing: -drive if=scsi,media=cdrom +qququiquit + +Testing: -drive if=ide +qemu: Device needs media, but drive is empty +qemu: Initialization of device ide-hd failed + +Testing: -drive if=virtio +qemu: -drive if=virtio: Device needs media, but drive is empty +qemu: -drive if=virtio: Device 'virtio-blk-pci' could not be initialized + +Testing: -drive if=scsi +qemu: -drive if=scsi: Device needs media, but drive is empty +qemu: Initialization of device lsi53c895a failed + +Testing: -drive if=none,id=disk -device ide-cd,drive=disk +qququiquit + +Testing: -drive if=none,id=disk -device lsi53c895a -device scsi-cd,drive=disk +qququiquit + +Testing: -drive if=none,id=disk -device ide-drive,drive=disk +qemu: -device ide-drive,drive=disk: Device needs media, but drive is empty +qemu: -device ide-drive,drive=disk: Device 'ide-drive' could not be initialized + +Testing: -drive if=none,id=disk -device ide-hd,drive=disk +qemu: -device ide-hd,drive=disk: Device needs media, but drive is empty +qemu: -device ide-hd,drive=disk: Device 'ide-hd' could not be initialized + +Testing: -drive if=none,id=disk -device lsi53c895a -device scsi-disk,drive=disk +qemu: -device scsi-disk,drive=disk: Device needs media, but drive is empty +qemu: -device scsi-disk,drive=disk: Device 'scsi-disk' could not be initialized + +Testing: -drive if=none,id=disk -device lsi53c895a -device scsi-hd,drive=disk +qemu: -device scsi-hd,drive=disk: Device needs media, but drive is empty +qemu: -device scsi-hd,drive=disk: Device 'scsi-hd' could not be initialized + + +=== Read-only === + +Testing: -drive file=TEST_DIR/t.qcow2,if=floppy,readonly=on +qququiquit + +Testing: -drive file=TEST_DIR/t.qcow2,if=ide,media=cdrom,readonly=on +qququiquit + +Testing: -drive file=TEST_DIR/t.qcow2,if=scsi,media=cdrom,readonly=on +qququiquit + +Testing: -drive file=TEST_DIR/t.qcow2,if=ide,readonly=on +qemu: -drive file=TEST_DIR/t.qcow2,if=ide,readonly=on: readonly not supported by this bus type + +Testing: -drive file=TEST_DIR/t.qcow2,if=virtio,readonly=on +qququiquit + +Testing: -drive file=TEST_DIR/t.qcow2,if=scsi,readonly=on +qququiquit + +Testing: -drive file=TEST_DIR/t.qcow2,if=none,id=disk,readonly=on -device ide-cd,drive=disk +qququiquit + +Testing: -drive file=TEST_DIR/t.qcow2,if=none,id=disk,readonly=on -device lsi53c895a -device scsi-cd,drive=disk +qququiquit + +Testing: -drive file=TEST_DIR/t.qcow2,if=none,id=disk,readonly=on -device ide-drive,drive=disk +qemu: -device ide-drive,drive=disk: Can't use a read-only drive +qemu: -device ide-drive,drive=disk: Device 'ide-drive' could not be initialized + +Testing: -drive file=TEST_DIR/t.qcow2,if=none,id=disk,readonly=on -device ide-hd,drive=disk +qemu: -device ide-hd,drive=disk: Can't use a read-only drive +qemu: -device ide-hd,drive=disk: Device 'ide-hd' could not be initialized + +Testing: -drive file=TEST_DIR/t.qcow2,if=none,id=disk,readonly=on -device lsi53c895a -device scsi-disk,drive=disk +qququiquit + +Testing: -drive file=TEST_DIR/t.qcow2,if=none,id=disk,readonly=on -device lsi53c895a -device scsi-hd,drive=disk +qququiquit + + +=== Cache modes === + +Testing: -drive media=cdrom,cache=none +qququiquit + +Testing: -drive media=cdrom,cache=directsync +qququiquit + +Testing: -drive media=cdrom,cache=writeback +qququiquit + +Testing: -drive media=cdrom,cache=writethrough +qququiquit + +Testing: -drive media=cdrom,cache=unsafe +qququiquit + +Testing: -drive media=cdrom,cache=invalid_value +qemu: -drive media=cdrom,cache=invalid_value: invalid cache option + +*** done diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group index 73c5966e10..324bacbcd4 100644 --- a/tests/qemu-iotests/group +++ b/tests/qemu-iotests/group @@ -57,4 +57,5 @@ 048 img auto quick 049 rw auto 050 rw auto backing quick +051 rw auto 052 rw auto backing -- cgit v1.2.3 From c09b437b5fab487edd924e09d1732062d7dcf093 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Mon, 15 Apr 2013 10:15:17 +0200 Subject: qemu-iotests: filter QEMU_PROG in 051.out Filter the name of the QEMU executable so the output can be diffed no matter what QEMU_PROG is (e.g. qemu-system-x86_64). Signed-off-by: Stefan Hajnoczi --- tests/qemu-iotests/051 | 2 +- tests/qemu-iotests/051.out | 72 ++++++++++++++++++++-------------------- tests/qemu-iotests/common.filter | 6 ++++ 3 files changed, 43 insertions(+), 37 deletions(-) diff --git a/tests/qemu-iotests/051 b/tests/qemu-iotests/051 index 17a1d92a10..8b51de3f31 100755 --- a/tests/qemu-iotests/051 +++ b/tests/qemu-iotests/051 @@ -51,7 +51,7 @@ function do_run_qemu() function run_qemu() { - do_run_qemu "$@" 2>&1 | _filter_testdir + do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qemu } size=128M diff --git a/tests/qemu-iotests/051.out b/tests/qemu-iotests/051.out index c20618615d..48456d5b07 100644 --- a/tests/qemu-iotests/051.out +++ b/tests/qemu-iotests/051.out @@ -4,20 +4,20 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 === Unknown option === Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt= -qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=: Block format 'qcow2' used by device 'ide0-hd0' doesn't support the option 'unknown_opt' -qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=: could not open disk image TEST_DIR/t.qcow2: Invalid argument +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=: Block format 'qcow2' used by device 'ide0-hd0' doesn't support the option 'unknown_opt' +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=: could not open disk image TEST_DIR/t.qcow2: Invalid argument Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=on -qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=on: Block format 'qcow2' used by device 'ide0-hd0' doesn't support the option 'unknown_opt' -qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=on: could not open disk image TEST_DIR/t.qcow2: Invalid argument +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=on: Block format 'qcow2' used by device 'ide0-hd0' doesn't support the option 'unknown_opt' +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=on: could not open disk image TEST_DIR/t.qcow2: Invalid argument Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=1234 -qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=1234: Block format 'qcow2' used by device 'ide0-hd0' doesn't support the option 'unknown_opt' -qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=1234: could not open disk image TEST_DIR/t.qcow2: Invalid argument +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=1234: Block format 'qcow2' used by device 'ide0-hd0' doesn't support the option 'unknown_opt' +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=1234: could not open disk image TEST_DIR/t.qcow2: Invalid argument Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=foo -qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=foo: Block format 'qcow2' used by device 'ide0-hd0' doesn't support the option 'unknown_opt' -qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=foo: could not open disk image TEST_DIR/t.qcow2: Invalid argument +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=foo: Block format 'qcow2' used by device 'ide0-hd0' doesn't support the option 'unknown_opt' +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,unknown_opt=foo: could not open disk image TEST_DIR/t.qcow2: Invalid argument === Enable and disable lazy refcounting on the command line, plus some invalid values === @@ -29,24 +29,24 @@ Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=off qququiquit Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts= -qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=: Parameter 'lazy_refcounts' expects 'on' or 'off' -qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=: could not open disk image TEST_DIR/t.qcow2: Invalid argument +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=: Parameter 'lazy_refcounts' expects 'on' or 'off' +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=: could not open disk image TEST_DIR/t.qcow2: Invalid argument Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=42 -qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=42: Parameter 'lazy_refcounts' expects 'on' or 'off' -qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=42: could not open disk image TEST_DIR/t.qcow2: Invalid argument +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=42: Parameter 'lazy_refcounts' expects 'on' or 'off' +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=42: could not open disk image TEST_DIR/t.qcow2: Invalid argument Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=foo -qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=foo: Parameter 'lazy_refcounts' expects 'on' or 'off' -qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=foo: could not open disk image TEST_DIR/t.qcow2: Invalid argument +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=foo: Parameter 'lazy_refcounts' expects 'on' or 'off' +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=foo: could not open disk image TEST_DIR/t.qcow2: Invalid argument === With version 2 images enabling lazy refcounts must fail === Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=on -qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=on: Lazy refcounts require a qcow2 image with at least qemu 1.1 compatibility level -qemu: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=on: could not open disk image TEST_DIR/t.qcow2: Invalid argument +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=on: Lazy refcounts require a qcow2 image with at least qemu 1.1 compatibility level +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=on: could not open disk image TEST_DIR/t.qcow2: Invalid argument Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,lazy_refcounts=off qququiquit @@ -64,16 +64,16 @@ Testing: -drive if=scsi,media=cdrom qququiquit Testing: -drive if=ide -qemu: Device needs media, but drive is empty -qemu: Initialization of device ide-hd failed +QEMU_PROG: Device needs media, but drive is empty +QEMU_PROG: Initialization of device ide-hd failed Testing: -drive if=virtio -qemu: -drive if=virtio: Device needs media, but drive is empty -qemu: -drive if=virtio: Device 'virtio-blk-pci' could not be initialized +QEMU_PROG: -drive if=virtio: Device needs media, but drive is empty +QEMU_PROG: -drive if=virtio: Device 'virtio-blk-pci' could not be initialized Testing: -drive if=scsi -qemu: -drive if=scsi: Device needs media, but drive is empty -qemu: Initialization of device lsi53c895a failed +QEMU_PROG: -drive if=scsi: Device needs media, but drive is empty +QEMU_PROG: Initialization of device lsi53c895a failed Testing: -drive if=none,id=disk -device ide-cd,drive=disk qququiquit @@ -82,20 +82,20 @@ Testing: -drive if=none,id=disk -device lsi53c895a -device scsi-cd,drive=disk qququiquit Testing: -drive if=none,id=disk -device ide-drive,drive=disk -qemu: -device ide-drive,drive=disk: Device needs media, but drive is empty -qemu: -device ide-drive,drive=disk: Device 'ide-drive' could not be initialized +QEMU_PROG: -device ide-drive,drive=disk: Device needs media, but drive is empty +QEMU_PROG: -device ide-drive,drive=disk: Device 'ide-drive' could not be initialized Testing: -drive if=none,id=disk -device ide-hd,drive=disk -qemu: -device ide-hd,drive=disk: Device needs media, but drive is empty -qemu: -device ide-hd,drive=disk: Device 'ide-hd' could not be initialized +QEMU_PROG: -device ide-hd,drive=disk: Device needs media, but drive is empty +QEMU_PROG: -device ide-hd,drive=disk: Device 'ide-hd' could not be initialized Testing: -drive if=none,id=disk -device lsi53c895a -device scsi-disk,drive=disk -qemu: -device scsi-disk,drive=disk: Device needs media, but drive is empty -qemu: -device scsi-disk,drive=disk: Device 'scsi-disk' could not be initialized +QEMU_PROG: -device scsi-disk,drive=disk: Device needs media, but drive is empty +QEMU_PROG: -device scsi-disk,drive=disk: Device 'scsi-disk' could not be initialized Testing: -drive if=none,id=disk -device lsi53c895a -device scsi-hd,drive=disk -qemu: -device scsi-hd,drive=disk: Device needs media, but drive is empty -qemu: -device scsi-hd,drive=disk: Device 'scsi-hd' could not be initialized +QEMU_PROG: -device scsi-hd,drive=disk: Device needs media, but drive is empty +QEMU_PROG: -device scsi-hd,drive=disk: Device 'scsi-hd' could not be initialized === Read-only === @@ -110,7 +110,7 @@ Testing: -drive file=TEST_DIR/t.qcow2,if=scsi,media=cdrom,readonly=on qququiquit Testing: -drive file=TEST_DIR/t.qcow2,if=ide,readonly=on -qemu: -drive file=TEST_DIR/t.qcow2,if=ide,readonly=on: readonly not supported by this bus type +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,if=ide,readonly=on: readonly not supported by this bus type Testing: -drive file=TEST_DIR/t.qcow2,if=virtio,readonly=on qququiquit @@ -125,12 +125,12 @@ Testing: -drive file=TEST_DIR/t.qcow2,if=none,id=disk,readonly=on -device lsi53c qququiquit Testing: -drive file=TEST_DIR/t.qcow2,if=none,id=disk,readonly=on -device ide-drive,drive=disk -qemu: -device ide-drive,drive=disk: Can't use a read-only drive -qemu: -device ide-drive,drive=disk: Device 'ide-drive' could not be initialized +QEMU_PROG: -device ide-drive,drive=disk: Can't use a read-only drive +QEMU_PROG: -device ide-drive,drive=disk: Device 'ide-drive' could not be initialized Testing: -drive file=TEST_DIR/t.qcow2,if=none,id=disk,readonly=on -device ide-hd,drive=disk -qemu: -device ide-hd,drive=disk: Can't use a read-only drive -qemu: -device ide-hd,drive=disk: Device 'ide-hd' could not be initialized +QEMU_PROG: -device ide-hd,drive=disk: Can't use a read-only drive +QEMU_PROG: -device ide-hd,drive=disk: Device 'ide-hd' could not be initialized Testing: -drive file=TEST_DIR/t.qcow2,if=none,id=disk,readonly=on -device lsi53c895a -device scsi-disk,drive=disk qququiquit @@ -157,6 +157,6 @@ Testing: -drive media=cdrom,cache=unsafe qququiquit Testing: -drive media=cdrom,cache=invalid_value -qemu: -drive media=cdrom,cache=invalid_value: invalid cache option +QEMU_PROG: -drive media=cdrom,cache=invalid_value: invalid cache option *** done diff --git a/tests/qemu-iotests/common.filter b/tests/qemu-iotests/common.filter index fa26b62dd3..bc5f250cab 100644 --- a/tests/qemu-iotests/common.filter +++ b/tests/qemu-iotests/common.filter @@ -152,5 +152,11 @@ _filter_qemu_io() _filter_win32 | sed -e "s/[0-9]* ops\; [0-9/:. sec]* ([0-9/.inf]* [EPTGMKiBbytes]*\/sec and [0-9/.inf]* ops\/sec)/X ops\; XX:XX:XX.X (XXX YYY\/sec and XXX ops\/sec)/" } +# replace occurrences of QEMU_PROG with "qemu" +_filter_qemu() +{ + sed -e "s#$(basename $QEMU_PROG)#QEMU_PROG#g" +} + # make sure this script returns success /bin/true -- cgit v1.2.3 From aad64f3193cbdc3741d10afa0237b6833e46e94e Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Fri, 5 Apr 2013 11:51:01 +0200 Subject: ide: refuse WIN_READ_NATIVE_MAX on empty device What is the highest addressable sector on an empty CD-ROM? Nothing is addressable so produce an error. This patch prevents a divide-by-zero in ide_set_sector() since s->sectors and s->heads would be 0. Not to mention that a sector=-1 argument would be nonsense. Note that WIN_READ_NATIVE_MAX can be triggered using hdparm -N 1024 /dev/cdrom. The LBA bit will be set to 1 though, so the only easy way to go down the ide_set_sector() CHS code path which divides by zero is to comment out the s->select & 0x40 case for testing. Signed-off-by: Stefan Hajnoczi Reviewed-by: Markus Armbruster --- hw/ide/core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hw/ide/core.c b/hw/ide/core.c index 87d67b7bcb..c7a80413be 100644 --- a/hw/ide/core.c +++ b/hw/ide/core.c @@ -1262,6 +1262,10 @@ void ide_exec_cmd(IDEBus *bus, uint32_t val) lba48 = 1; /* fall through */ case WIN_READ_NATIVE_MAX: + /* Refuse if no sectors are addressable (e.g. medium not inserted) */ + if (s->nb_sectors == 0) { + goto abort_cmd; + } ide_cmd_lba48_transform(s, lba48); ide_set_sector(s, s->nb_sectors - 1); s->status = READY_STAT | SEEK_STAT; -- cgit v1.2.3 From 0a12ec87a513b31eb3b6e035d30649e483322270 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Tue, 9 Apr 2013 15:30:53 +0100 Subject: block: Add support for Secure Shell (ssh) block device. qemu-system-x86_64 -drive file=ssh://hostname/some/image QEMU will ssh into 'hostname' and open '/some/image' which is made available as a standard block device. You can specify a username (ssh://user@host/...) and/or a port number (ssh://host:port/...). You can also use an alternate syntax using properties (file.user, file.host, file.port, file.path). Current limitations: - Authentication must be done without passwords or passphrases, using ssh-agent. Other authentication methods are not supported. - Uses a single connection, instead of concurrent AIO with multiple SSH connections. This is implemented using libssh2 on the client side. The server just requires a regular ssh daemon with sftp-server support. Most ssh daemons on Unix/Linux systems will work out of the box. Signed-off-by: Richard W.M. Jones Cc: Stefan Hajnoczi Cc: Kevin Wolf Signed-off-by: Stefan Hajnoczi --- block/Makefile.objs | 1 + block/ssh.c | 995 ++++++++++++++++++++++++++++++++++++++++++++++++++++ configure | 48 +++ qemu-doc.texi | 49 +++ qemu-options.hx | 12 + 5 files changed, 1105 insertions(+) create mode 100644 block/ssh.c diff --git a/block/Makefile.objs b/block/Makefile.objs index c067f38e1d..6c4b5bc6b0 100644 --- a/block/Makefile.objs +++ b/block/Makefile.objs @@ -13,6 +13,7 @@ block-obj-$(CONFIG_LIBISCSI) += iscsi.o block-obj-$(CONFIG_CURL) += curl.o block-obj-$(CONFIG_RBD) += rbd.o block-obj-$(CONFIG_GLUSTERFS) += gluster.o +block-obj-$(CONFIG_LIBSSH2) += ssh.o endif common-obj-y += stream.o diff --git a/block/ssh.c b/block/ssh.c new file mode 100644 index 0000000000..89a9017b63 --- /dev/null +++ b/block/ssh.c @@ -0,0 +1,995 @@ +/* + * Secure Shell (ssh) backend for QEMU. + * + * Copyright (C) 2013 Red Hat Inc., Richard W.M. Jones + * + * 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 +#include +#include + +#include +#include + +#include "block/block_int.h" +#include "qemu/sockets.h" +#include "qemu/uri.h" +#include "qapi/qmp/qint.h" + +/* DEBUG_SSH=1 enables the DPRINTF (debugging printf) statements in + * this block driver code. + * + * TRACE_LIBSSH2= enables tracing in libssh2 itself. Note + * that this requires that libssh2 was specially compiled with the + * `./configure --enable-debug' option, so most likely you will have + * to compile it yourself. The meaning of is described + * here: http://www.libssh2.org/libssh2_trace.html + */ +#define DEBUG_SSH 0 +#define TRACE_LIBSSH2 0 /* or try: LIBSSH2_TRACE_SFTP */ + +#define DPRINTF(fmt, ...) \ + do { \ + if (DEBUG_SSH) { \ + fprintf(stderr, "ssh: %-15s " fmt "\n", \ + __func__, ##__VA_ARGS__); \ + } \ + } while (0) + +typedef struct BDRVSSHState { + /* Coroutine. */ + CoMutex lock; + + /* SSH connection. */ + int sock; /* socket */ + LIBSSH2_SESSION *session; /* ssh session */ + LIBSSH2_SFTP *sftp; /* sftp session */ + LIBSSH2_SFTP_HANDLE *sftp_handle; /* sftp remote file handle */ + + /* See ssh_seek() function below. */ + int64_t offset; + bool offset_op_read; + + /* File attributes at open. We try to keep the .filesize field + * updated if it changes (eg by writing at the end of the file). + */ + LIBSSH2_SFTP_ATTRIBUTES attrs; +} BDRVSSHState; + +static void ssh_state_init(BDRVSSHState *s) +{ + memset(s, 0, sizeof *s); + s->sock = -1; + s->offset = -1; + qemu_co_mutex_init(&s->lock); +} + +static void ssh_state_free(BDRVSSHState *s) +{ + if (s->sftp_handle) { + libssh2_sftp_close(s->sftp_handle); + } + if (s->sftp) { + libssh2_sftp_shutdown(s->sftp); + } + if (s->session) { + libssh2_session_disconnect(s->session, + "from qemu ssh client: " + "user closed the connection"); + libssh2_session_free(s->session); + } + if (s->sock >= 0) { + close(s->sock); + } +} + +/* Wrappers around error_report which make sure to dump as much + * information from libssh2 as possible. + */ +static void +session_error_report(BDRVSSHState *s, const char *fs, ...) +{ + va_list args; + + va_start(args, fs); + error_vprintf(fs, args); + + if ((s)->session) { + char *ssh_err; + int ssh_err_code; + + libssh2_session_last_error((s)->session, &ssh_err, NULL, 0); + /* This is not an errno. See . */ + ssh_err_code = libssh2_session_last_errno((s)->session); + + error_printf(": %s (libssh2 error code: %d)", ssh_err, ssh_err_code); + } + + va_end(args); + error_printf("\n"); +} + +static void +sftp_error_report(BDRVSSHState *s, const char *fs, ...) +{ + va_list args; + + va_start(args, fs); + error_vprintf(fs, args); + + if ((s)->sftp) { + char *ssh_err; + int ssh_err_code; + unsigned long sftp_err_code; + + libssh2_session_last_error((s)->session, &ssh_err, NULL, 0); + /* This is not an errno. See . */ + ssh_err_code = libssh2_session_last_errno((s)->session); + /* See . */ + sftp_err_code = libssh2_sftp_last_error((s)->sftp); + + error_printf(": %s (libssh2 error code: %d, sftp error code: %lu)", + ssh_err, ssh_err_code, sftp_err_code); + } + + va_end(args); + error_printf("\n"); +} + +static int parse_uri(const char *filename, QDict *options, Error **errp) +{ + URI *uri = NULL; + QueryParams *qp = NULL; + int i; + + uri = uri_parse(filename); + if (!uri) { + return -EINVAL; + } + + if (strcmp(uri->scheme, "ssh") != 0) { + error_setg(errp, "URI scheme must be 'ssh'"); + goto err; + } + + if (!uri->server || strcmp(uri->server, "") == 0) { + error_setg(errp, "missing hostname in URI"); + goto err; + } + + if (!uri->path || strcmp(uri->path, "") == 0) { + error_setg(errp, "missing remote path in URI"); + goto err; + } + + qp = query_params_parse(uri->query); + if (!qp) { + error_setg(errp, "could not parse query parameters"); + goto err; + } + + if(uri->user && strcmp(uri->user, "") != 0) { + qdict_put(options, "user", qstring_from_str(uri->user)); + } + + qdict_put(options, "host", qstring_from_str(uri->server)); + + if (uri->port) { + qdict_put(options, "port", qint_from_int(uri->port)); + } + + qdict_put(options, "path", qstring_from_str(uri->path)); + + /* Pick out any query parameters that we understand, and ignore + * the rest. + */ + for (i = 0; i < qp->n; ++i) { + if (strcmp(qp->p[i].name, "host_key_check") == 0) { + qdict_put(options, "host_key_check", + qstring_from_str(qp->p[i].value)); + } + } + + query_params_free(qp); + uri_free(uri); + return 0; + + err: + if (qp) { + query_params_free(qp); + } + if (uri) { + uri_free(uri); + } + return -EINVAL; +} + +static void ssh_parse_filename(const char *filename, QDict *options, + Error **errp) +{ + if (qdict_haskey(options, "user") || + qdict_haskey(options, "host") || + qdict_haskey(options, "port") || + qdict_haskey(options, "path") || + qdict_haskey(options, "host_key_check")) { + error_setg(errp, "user, host, port, path, host_key_check cannot be used at the same time as a file option"); + return; + } + + parse_uri(filename, options, errp); +} + +static int check_host_key_knownhosts(BDRVSSHState *s, + const char *host, int port) +{ + const char *home; + char *knh_file = NULL; + LIBSSH2_KNOWNHOSTS *knh = NULL; + struct libssh2_knownhost *found; + int ret, r; + const char *hostkey; + size_t len; + int type; + + hostkey = libssh2_session_hostkey(s->session, &len, &type); + if (!hostkey) { + ret = -EINVAL; + session_error_report(s, "failed to read remote host key"); + goto out; + } + + knh = libssh2_knownhost_init(s->session); + if (!knh) { + ret = -EINVAL; + session_error_report(s, "failed to initialize known hosts support"); + goto out; + } + + home = getenv("HOME"); + if (home) { + knh_file = g_strdup_printf("%s/.ssh/known_hosts", home); + } else { + knh_file = g_strdup_printf("/root/.ssh/known_hosts"); + } + + /* Read all known hosts from OpenSSH-style known_hosts file. */ + libssh2_knownhost_readfile(knh, knh_file, LIBSSH2_KNOWNHOST_FILE_OPENSSH); + + r = libssh2_knownhost_checkp(knh, host, port, hostkey, len, + LIBSSH2_KNOWNHOST_TYPE_PLAIN| + LIBSSH2_KNOWNHOST_KEYENC_RAW, + &found); + switch (r) { + case LIBSSH2_KNOWNHOST_CHECK_MATCH: + /* OK */ + DPRINTF("host key OK: %s", found->key); + break; + case LIBSSH2_KNOWNHOST_CHECK_MISMATCH: + ret = -EINVAL; + session_error_report(s, "host key does not match the one in known_hosts (found key %s)", + found->key); + goto out; + case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND: + ret = -EINVAL; + session_error_report(s, "no host key was found in known_hosts"); + goto out; + case LIBSSH2_KNOWNHOST_CHECK_FAILURE: + ret = -EINVAL; + session_error_report(s, "failure matching the host key with known_hosts"); + goto out; + default: + ret = -EINVAL; + session_error_report(s, "unknown error matching the host key with known_hosts (%d)", + r); + goto out; + } + + /* known_hosts checking successful. */ + ret = 0; + + out: + if (knh != NULL) { + libssh2_knownhost_free(knh); + } + g_free(knh_file); + return ret; +} + +static unsigned hex2decimal(char ch) +{ + if (ch >= '0' && ch <= '9') { + return (ch - '0'); + } else if (ch >= 'a' && ch <= 'f') { + return 10 + (ch - 'a'); + } else if (ch >= 'A' && ch <= 'F') { + return 10 + (ch - 'A'); + } + + return -1; +} + +/* Compare the binary fingerprint (hash of host key) with the + * host_key_check parameter. + */ +static int compare_fingerprint(const unsigned char *fingerprint, size_t len, + const char *host_key_check) +{ + unsigned c; + + while (len > 0) { + while (*host_key_check == ':') + host_key_check++; + if (!qemu_isxdigit(host_key_check[0]) || + !qemu_isxdigit(host_key_check[1])) + return 1; + c = hex2decimal(host_key_check[0]) * 16 + + hex2decimal(host_key_check[1]); + if (c - *fingerprint != 0) + return c - *fingerprint; + fingerprint++; + len--; + host_key_check += 2; + } + return *host_key_check - '\0'; +} + +static int +check_host_key_hash(BDRVSSHState *s, const char *hash, + int hash_type, size_t fingerprint_len) +{ + const char *fingerprint; + + fingerprint = libssh2_hostkey_hash(s->session, hash_type); + if (!fingerprint) { + session_error_report(s, "failed to read remote host key"); + return -EINVAL; + } + + if(compare_fingerprint((unsigned char *) fingerprint, fingerprint_len, + hash) != 0) { + error_report("remote host key does not match host_key_check '%s'", + hash); + return -EPERM; + } + + return 0; +} + +static int check_host_key(BDRVSSHState *s, const char *host, int port, + const char *host_key_check) +{ + /* host_key_check=no */ + if (strcmp(host_key_check, "no") == 0) { + return 0; + } + + /* host_key_check=md5:xx:yy:zz:... */ + if (strlen(host_key_check) >= 4 && + strncmp(host_key_check, "md5:", 4) == 0) { + return check_host_key_hash(s, &host_key_check[4], + LIBSSH2_HOSTKEY_HASH_MD5, 16); + } + + /* host_key_check=sha1:xx:yy:zz:... */ + if (strlen(host_key_check) >= 5 && + strncmp(host_key_check, "sha1:", 5) == 0) { + return check_host_key_hash(s, &host_key_check[5], + LIBSSH2_HOSTKEY_HASH_SHA1, 20); + } + + /* host_key_check=yes */ + if (strcmp(host_key_check, "yes") == 0) { + return check_host_key_knownhosts(s, host, port); + } + + error_report("unknown host_key_check setting (%s)", host_key_check); + return -EINVAL; +} + +static int authenticate(BDRVSSHState *s, const char *user) +{ + int r, ret; + const char *userauthlist; + LIBSSH2_AGENT *agent = NULL; + struct libssh2_agent_publickey *identity; + struct libssh2_agent_publickey *prev_identity = NULL; + + userauthlist = libssh2_userauth_list(s->session, user, strlen(user)); + if (strstr(userauthlist, "publickey") == NULL) { + ret = -EPERM; + error_report("remote server does not support \"publickey\" authentication"); + goto out; + } + + /* Connect to ssh-agent and try each identity in turn. */ + agent = libssh2_agent_init(s->session); + if (!agent) { + ret = -EINVAL; + session_error_report(s, "failed to initialize ssh-agent support"); + goto out; + } + if (libssh2_agent_connect(agent)) { + ret = -ECONNREFUSED; + session_error_report(s, "failed to connect to ssh-agent"); + goto out; + } + if (libssh2_agent_list_identities(agent)) { + ret = -EINVAL; + session_error_report(s, "failed requesting identities from ssh-agent"); + goto out; + } + + for(;;) { + r = libssh2_agent_get_identity(agent, &identity, prev_identity); + if (r == 1) { /* end of list */ + break; + } + if (r < 0) { + ret = -EINVAL; + session_error_report(s, "failed to obtain identity from ssh-agent"); + goto out; + } + r = libssh2_agent_userauth(agent, user, identity); + if (r == 0) { + /* Authenticated! */ + ret = 0; + goto out; + } + /* Failed to authenticate with this identity, try the next one. */ + prev_identity = identity; + } + + ret = -EPERM; + error_report("failed to authenticate using publickey authentication " + "and the identities held by your ssh-agent"); + + out: + if (agent != NULL) { + /* Note: libssh2 implementation implicitly calls + * libssh2_agent_disconnect if necessary. + */ + libssh2_agent_free(agent); + } + + return ret; +} + +static int connect_to_ssh(BDRVSSHState *s, QDict *options, + int ssh_flags, int creat_mode) +{ + int r, ret; + Error *err = NULL; + const char *host, *user, *path, *host_key_check; + int port; + char *hostport = NULL; + + host = qdict_get_str(options, "host"); + + if (qdict_haskey(options, "port")) { + port = qdict_get_int(options, "port"); + } else { + port = 22; + } + + path = qdict_get_str(options, "path"); + + if (qdict_haskey(options, "user")) { + user = qdict_get_str(options, "user"); + } else { + user = g_get_user_name(); + if (!user) { + ret = -errno; + goto err; + } + } + + if (qdict_haskey(options, "host_key_check")) { + host_key_check = qdict_get_str(options, "host_key_check"); + } else { + host_key_check = "yes"; + } + + /* Open the socket and connect. */ + hostport = g_strdup_printf("%s:%d", host, port); + s->sock = inet_connect(hostport, &err); + if (err != NULL) { + ret = -errno; + qerror_report_err(err); + error_free(err); + goto err; + } + + /* Create SSH session. */ + s->session = libssh2_session_init(); + if (!s->session) { + ret = -EINVAL; + session_error_report(s, "failed to initialize libssh2 session"); + goto err; + } + +#if TRACE_LIBSSH2 != 0 + libssh2_trace(s->session, TRACE_LIBSSH2); +#endif + + r = libssh2_session_handshake(s->session, s->sock); + if (r != 0) { + ret = -EINVAL; + session_error_report(s, "failed to establish SSH session"); + goto err; + } + + /* Check the remote host's key against known_hosts. */ + ret = check_host_key(s, host, port, host_key_check); + if (ret < 0) { + goto err; + } + + /* Authenticate. */ + ret = authenticate(s, user); + if (ret < 0) { + goto err; + } + + /* Start SFTP. */ + s->sftp = libssh2_sftp_init(s->session); + if (!s->sftp) { + session_error_report(s, "failed to initialize sftp handle"); + ret = -EINVAL; + goto err; + } + + /* Open the remote file. */ + DPRINTF("opening file %s flags=0x%x creat_mode=0%o", + path, ssh_flags, creat_mode); + s->sftp_handle = libssh2_sftp_open(s->sftp, path, ssh_flags, creat_mode); + if (!s->sftp_handle) { + session_error_report(s, "failed to open remote file '%s'", path); + ret = -EINVAL; + goto err; + } + + r = libssh2_sftp_fstat(s->sftp_handle, &s->attrs); + if (r < 0) { + sftp_error_report(s, "failed to read file attributes"); + return -EINVAL; + } + + /* Delete the options we've used; any not deleted will cause the + * block layer to give an error about unused options. + */ + qdict_del(options, "host"); + qdict_del(options, "port"); + qdict_del(options, "user"); + qdict_del(options, "path"); + qdict_del(options, "host_key_check"); + + g_free(hostport); + return 0; + + err: + if (s->sftp_handle) { + libssh2_sftp_close(s->sftp_handle); + } + s->sftp_handle = NULL; + if (s->sftp) { + libssh2_sftp_shutdown(s->sftp); + } + s->sftp = NULL; + if (s->session) { + libssh2_session_disconnect(s->session, + "from qemu ssh client: " + "error opening connection"); + libssh2_session_free(s->session); + } + s->session = NULL; + g_free(hostport); + + return ret; +} + +static int ssh_file_open(BlockDriverState *bs, const char *filename, + QDict *options, int bdrv_flags) +{ + BDRVSSHState *s = bs->opaque; + int ret; + int ssh_flags; + + ssh_state_init(s); + + ssh_flags = LIBSSH2_FXF_READ; + if (bdrv_flags & BDRV_O_RDWR) { + ssh_flags |= LIBSSH2_FXF_WRITE; + } + + /* Start up SSH. */ + ret = connect_to_ssh(s, options, ssh_flags, 0); + if (ret < 0) { + goto err; + } + + /* Go non-blocking. */ + libssh2_session_set_blocking(s->session, 0); + + return 0; + + err: + if (s->sock >= 0) { + close(s->sock); + } + s->sock = -1; + + return ret; +} + +static QEMUOptionParameter ssh_create_options[] = { + { + .name = BLOCK_OPT_SIZE, + .type = OPT_SIZE, + .help = "Virtual disk size" + }, + { NULL } +}; + +static int ssh_create(const char *filename, QEMUOptionParameter *options) +{ + int r, ret; + Error *local_err = NULL; + int64_t total_size = 0; + QDict *uri_options = NULL; + BDRVSSHState s; + ssize_t r2; + char c[1] = { '\0' }; + + ssh_state_init(&s); + + /* Get desired file size. */ + while (options && options->name) { + if (!strcmp(options->name, BLOCK_OPT_SIZE)) { + total_size = options->value.n; + } + options++; + } + DPRINTF("total_size=%" PRIi64, total_size); + + uri_options = qdict_new(); + r = parse_uri(filename, uri_options, &local_err); + if (r < 0) { + qerror_report_err(local_err); + error_free(local_err); + ret = r; + goto out; + } + + r = connect_to_ssh(&s, uri_options, + LIBSSH2_FXF_READ|LIBSSH2_FXF_WRITE| + LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC, 0644); + if (r < 0) { + ret = r; + goto out; + } + + if (total_size > 0) { + libssh2_sftp_seek64(s.sftp_handle, total_size-1); + r2 = libssh2_sftp_write(s.sftp_handle, c, 1); + if (r2 < 0) { + sftp_error_report(&s, "truncate failed"); + ret = -EINVAL; + goto out; + } + s.attrs.filesize = total_size; + } + + ret = 0; + + out: + ssh_state_free(&s); + if (uri_options != NULL) { + QDECREF(uri_options); + } + return ret; +} + +static void ssh_close(BlockDriverState *bs) +{ + BDRVSSHState *s = bs->opaque; + + ssh_state_free(s); +} + +static void restart_coroutine(void *opaque) +{ + Coroutine *co = opaque; + + DPRINTF("co=%p", co); + + qemu_coroutine_enter(co, NULL); +} + +/* Always true because when we have called set_fd_handler there is + * always a request being processed. + */ +static int return_true(void *opaque) +{ + return 1; +} + +static coroutine_fn void set_fd_handler(BDRVSSHState *s) +{ + int r; + IOHandler *rd_handler = NULL, *wr_handler = NULL; + Coroutine *co = qemu_coroutine_self(); + + r = libssh2_session_block_directions(s->session); + + if (r & LIBSSH2_SESSION_BLOCK_INBOUND) { + rd_handler = restart_coroutine; + } + if (r & LIBSSH2_SESSION_BLOCK_OUTBOUND) { + wr_handler = restart_coroutine; + } + + DPRINTF("s->sock=%d rd_handler=%p wr_handler=%p", s->sock, + rd_handler, wr_handler); + + qemu_aio_set_fd_handler(s->sock, rd_handler, wr_handler, return_true, co); +} + +static coroutine_fn void clear_fd_handler(BDRVSSHState *s) +{ + DPRINTF("s->sock=%d", s->sock); + qemu_aio_set_fd_handler(s->sock, NULL, NULL, NULL, NULL); +} + +/* A non-blocking call returned EAGAIN, so yield, ensuring the + * handlers are set up so that we'll be rescheduled when there is an + * interesting event on the socket. + */ +static coroutine_fn void co_yield(BDRVSSHState *s) +{ + set_fd_handler(s); + qemu_coroutine_yield(); + clear_fd_handler(s); +} + +/* SFTP has a function `libssh2_sftp_seek64' which seeks to a position + * in the remote file. Notice that it just updates a field in the + * sftp_handle structure, so there is no network traffic and it cannot + * fail. + * + * However, `libssh2_sftp_seek64' does have a catastrophic effect on + * performance since it causes the handle to throw away all in-flight + * reads and buffered readahead data. Therefore this function tries + * to be intelligent about when to call the underlying libssh2 function. + */ +#define SSH_SEEK_WRITE 0 +#define SSH_SEEK_READ 1 +#define SSH_SEEK_FORCE 2 + +static void ssh_seek(BDRVSSHState *s, int64_t offset, int flags) +{ + bool op_read = (flags & SSH_SEEK_READ) != 0; + bool force = (flags & SSH_SEEK_FORCE) != 0; + + if (force || op_read != s->offset_op_read || offset != s->offset) { + DPRINTF("seeking to offset=%" PRIi64, offset); + libssh2_sftp_seek64(s->sftp_handle, offset); + s->offset = offset; + s->offset_op_read = op_read; + } +} + +static coroutine_fn int ssh_read(BDRVSSHState *s, + int64_t offset, size_t size, + QEMUIOVector *qiov) +{ + ssize_t r; + size_t got; + char *buf, *end_of_vec; + struct iovec *i; + + DPRINTF("offset=%" PRIi64 " size=%zu", offset, size); + + ssh_seek(s, offset, SSH_SEEK_READ); + + /* This keeps track of the current iovec element ('i'), where we + * will write to next ('buf'), and the end of the current iovec + * ('end_of_vec'). + */ + i = &qiov->iov[0]; + buf = i->iov_base; + end_of_vec = i->iov_base + i->iov_len; + + /* libssh2 has a hard-coded limit of 2000 bytes per request, + * although it will also do readahead behind our backs. Therefore + * we may have to do repeated reads here until we have read 'size' + * bytes. + */ + for (got = 0; got < size; ) { + again: + DPRINTF("sftp_read buf=%p size=%zu", buf, end_of_vec - buf); + r = libssh2_sftp_read(s->sftp_handle, buf, end_of_vec - buf); + DPRINTF("sftp_read returned %zd", r); + + if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) { + co_yield(s); + goto again; + } + if (r < 0) { + sftp_error_report(s, "read failed"); + s->offset = -1; + return -EIO; + } + if (r == 0) { + /* EOF: Short read so pad the buffer with zeroes and return it. */ + qemu_iovec_memset(qiov, got, 0, size - got); + return 0; + } + + got += r; + buf += r; + s->offset += r; + if (buf >= end_of_vec && got < size) { + i++; + buf = i->iov_base; + end_of_vec = i->iov_base + i->iov_len; + } + } + + return 0; +} + +static coroutine_fn int ssh_co_readv(BlockDriverState *bs, + int64_t sector_num, + int nb_sectors, QEMUIOVector *qiov) +{ + BDRVSSHState *s = bs->opaque; + int ret; + + qemu_co_mutex_lock(&s->lock); + ret = ssh_read(s, sector_num * BDRV_SECTOR_SIZE, + nb_sectors * BDRV_SECTOR_SIZE, qiov); + qemu_co_mutex_unlock(&s->lock); + + return ret; +} + +static int ssh_write(BDRVSSHState *s, + int64_t offset, size_t size, + QEMUIOVector *qiov) +{ + ssize_t r; + size_t written; + char *buf, *end_of_vec; + struct iovec *i; + + DPRINTF("offset=%" PRIi64 " size=%zu", offset, size); + + ssh_seek(s, offset, SSH_SEEK_WRITE); + + /* This keeps track of the current iovec element ('i'), where we + * will read from next ('buf'), and the end of the current iovec + * ('end_of_vec'). + */ + i = &qiov->iov[0]; + buf = i->iov_base; + end_of_vec = i->iov_base + i->iov_len; + + for (written = 0; written < size; ) { + again: + DPRINTF("sftp_write buf=%p size=%zu", buf, end_of_vec - buf); + r = libssh2_sftp_write(s->sftp_handle, buf, end_of_vec - buf); + DPRINTF("sftp_write returned %zd", r); + + if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) { + co_yield(s); + goto again; + } + if (r < 0) { + sftp_error_report(s, "write failed"); + s->offset = -1; + return -EIO; + } + /* The libssh2 API is very unclear about this. A comment in + * the code says "nothing was acked, and no EAGAIN was + * received!" which apparently means that no data got sent + * out, and the underlying channel didn't return any EAGAIN + * indication. I think this is a bug in either libssh2 or + * OpenSSH (server-side). In any case, forcing a seek (to + * discard libssh2 internal buffers), and then trying again + * works for me. + */ + if (r == 0) { + ssh_seek(s, offset + written, SSH_SEEK_WRITE|SSH_SEEK_FORCE); + co_yield(s); + goto again; + } + + written += r; + buf += r; + s->offset += r; + if (buf >= end_of_vec && written < size) { + i++; + buf = i->iov_base; + end_of_vec = i->iov_base + i->iov_len; + } + + if (offset + written > s->attrs.filesize) + s->attrs.filesize = offset + written; + } + + return 0; +} + +static coroutine_fn int ssh_co_writev(BlockDriverState *bs, + int64_t sector_num, + int nb_sectors, QEMUIOVector *qiov) +{ + BDRVSSHState *s = bs->opaque; + int ret; + + qemu_co_mutex_lock(&s->lock); + ret = ssh_write(s, sector_num * BDRV_SECTOR_SIZE, + nb_sectors * BDRV_SECTOR_SIZE, qiov); + qemu_co_mutex_unlock(&s->lock); + + return ret; +} + +static int64_t ssh_getlength(BlockDriverState *bs) +{ + BDRVSSHState *s = bs->opaque; + int64_t length; + + /* Note we cannot make a libssh2 call here. */ + length = (int64_t) s->attrs.filesize; + DPRINTF("length=%" PRIi64, length); + + return length; +} + +static BlockDriver bdrv_ssh = { + .format_name = "ssh", + .protocol_name = "ssh", + .instance_size = sizeof(BDRVSSHState), + .bdrv_parse_filename = ssh_parse_filename, + .bdrv_file_open = ssh_file_open, + .bdrv_create = ssh_create, + .bdrv_close = ssh_close, + .bdrv_co_readv = ssh_co_readv, + .bdrv_co_writev = ssh_co_writev, + .bdrv_getlength = ssh_getlength, + .create_options = ssh_create_options, +}; + +static void bdrv_ssh_init(void) +{ + int r; + + r = libssh2_init(0); + if (r != 0) { + fprintf(stderr, "libssh2 initialization failed, %d\n", r); + exit(EXIT_FAILURE); + } + + bdrv_register(&bdrv_ssh); +} + +block_init(bdrv_ssh_init); diff --git a/configure b/configure index 73df181406..e6c5d2c3f6 100755 --- a/configure +++ b/configure @@ -239,6 +239,7 @@ virtio_blk_data_plane="" gtk="" gtkabi="2.0" tpm="no" +libssh2="" # parse CC options first for opt do @@ -922,6 +923,10 @@ for opt do ;; --enable-tpm) tpm="yes" ;; + --disable-libssh2) libssh2="no" + ;; + --enable-libssh2) libssh2="yes" + ;; *) echo "ERROR: unknown option $opt"; show_help="yes" ;; esac @@ -1182,6 +1187,8 @@ echo " --disable-glusterfs disable GlusterFS backend" echo " --enable-gcov enable test coverage analysis with gcov" echo " --gcov=GCOV use specified gcov [$gcov_tool]" echo " --enable-tpm enable TPM support" +echo " --disable-libssh2 disable ssh block device support" +echo " --enable-libssh2 enable ssh block device support" echo "" echo "NOTE: The object files are built at the place where configure is launched" exit 1 @@ -2313,6 +2320,42 @@ EOF fi fi +########################################## +# libssh2 probe +if test "$libssh2" != "no" ; then + cat > $TMPC < +#include +#include +int main(void) { + LIBSSH2_SESSION *session; + session = libssh2_session_init (); + (void) libssh2_sftp_init (session); + return 0; +} +EOF + + if $pkg_config libssh2 --modversion >/dev/null 2>&1; then + libssh2_cflags=`$pkg_config libssh2 --cflags` + libssh2_libs=`$pkg_config libssh2 --libs` + else + libssh2_cflags= + libssh2_libs="-lssh2" + fi + + if compile_prog "$libssh2_cflags" "$libssh2_libs" ; then + libssh2=yes + libs_tools="$libssh2_libs $libs_tools" + libs_softmmu="$libssh2_libs $libs_softmmu" + QEMU_CFLAGS="$QEMU_CFLAGS $libssh2_cflags" + else + if test "$libssh2" = "yes" ; then + feature_not_found "libssh2" + fi + libssh2=no + fi +fi + ########################################## # linux-aio probe @@ -3435,6 +3478,7 @@ echo "virtio-blk-data-plane $virtio_blk_data_plane" echo "gcov $gcov_tool" echo "gcov enabled $gcov" echo "TPM support $tpm" +echo "libssh2 support $libssh2" if test "$sdl_too_old" = "yes"; then echo "-> Your SDL version is too old - please upgrade to have SDL support" @@ -3793,6 +3837,10 @@ if test "$glusterfs" = "yes" ; then echo "CONFIG_GLUSTERFS=y" >> $config_host_mak fi +if test "$libssh2" = "yes" ; then + echo "CONFIG_LIBSSH2=y" >> $config_host_mak +fi + if test "$virtio_blk_data_plane" = "yes" ; then echo 'CONFIG_VIRTIO_BLK_DATA_PLANE=$(CONFIG_VIRTIO)' >> $config_host_mak fi diff --git a/qemu-doc.texi b/qemu-doc.texi index af84bef0e9..5b36004873 100644 --- a/qemu-doc.texi +++ b/qemu-doc.texi @@ -423,6 +423,7 @@ snapshots. * disk_images_sheepdog:: Sheepdog disk images * disk_images_iscsi:: iSCSI LUNs * disk_images_gluster:: GlusterFS disk images +* disk_images_ssh:: Secure Shell (ssh) disk images @end menu @node disk_images_quickstart @@ -1038,6 +1039,54 @@ qemu-system-x86_64 -drive file=gluster+unix:///testvol/dir/a.img?socket=/tmp/glu qemu-system-x86_64 -drive file=gluster+rdma://1.2.3.4:24007/testvol/a.img @end example +@node disk_images_ssh +@subsection Secure Shell (ssh) disk images + +You can access disk images located on a remote ssh server +by using the ssh protocol: + +@example +qemu-system-x86_64 -drive file=ssh://[@var{user}@@]@var{server}[:@var{port}]/@var{path}[?host_key_check=@var{host_key_check}] +@end example + +Alternative syntax using properties: + +@example +qemu-system-x86_64 -drive file.driver=ssh[,file.user=@var{user}],file.host=@var{server}[,file.port=@var{port}],file.path=@var{path}[,file.host_key_check=@var{host_key_check}] +@end example + +@var{ssh} is the protocol. + +@var{user} is the remote user. If not specified, then the local +username is tried. + +@var{server} specifies the remote ssh server. Any ssh server can be +used, but it must implement the sftp-server protocol. Most Unix/Linux +systems should work without requiring any extra configuration. + +@var{port} is the port number on which sshd is listening. By default +the standard ssh port (22) is used. + +@var{path} is the path to the disk image. + +The optional @var{host_key_check} parameter controls how the remote +host's key is checked. The default is @code{yes} which means to use +the local @file{.ssh/known_hosts} file. Setting this to @code{no} +turns off known-hosts checking. Or you can check that the host key +matches a specific fingerprint: +@code{host_key_check=md5:78:45:8e:14:57:4f:d5:45:83:0a:0e:f3:49:82:c9:c8} +(@code{sha1:} can also be used as a prefix, but note that OpenSSH +tools only use MD5 to print fingerprints). + +Currently authentication must be done using ssh-agent. Other +authentication methods may be supported in future. + +Note: The ssh driver does not obey disk flush requests (ie. to commit +data to the backing disk when the guest requests it). This is because +the underlying protocol (SFTP) does not support this. Thus there is a +risk of guest disk corruption if the remote server or network goes +down during writes. + @node pcsys_network @section Network emulation diff --git a/qemu-options.hx b/qemu-options.hx index 7cd6002d95..5c115d1cf9 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -2107,6 +2107,18 @@ Example for Unix Domain Sockets qemu-system-i386 --drive file=nbd:unix:/tmp/nbd-socket @end example +@item SSH +QEMU supports SSH (Secure Shell) access to remote disks. + +Examples: +@example +qemu-system-i386 -drive file=ssh://user@@host/path/to/disk.img +qemu-system-i386 -drive file.driver=ssh,file.user=user,file.host=host,file.port=22,file.path=/path/to/disk.img +@end example + +Currently authentication must be done using ssh-agent. Other +authentication methods may be supported in future. + @item Sheepdog Sheepdog is a distributed storage system for QEMU. QEMU supports using either local sheepdog devices or remote networked -- cgit v1.2.3 From 9a2d462e7bfba36597ccbd3774ba3bb1bd4c54d8 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Tue, 9 Apr 2013 15:30:54 +0100 Subject: block: ssh: Use libssh2_sftp_fsync (if supported by libssh2) to flush to disk. libssh2_sftp_fsync is an extension to libssh2 to support fsync(2) over sftp, which is itself an extension of OpenSSH. If both libssh2 and the ssh daemon support it, this will allow bdrv_flush_to_disk to commit changes through to disk on the remote server. Signed-off-by: Stefan Hajnoczi --- block/ssh.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---- configure | 25 +++++++++++++++++++ qemu-doc.texi | 15 ++++++++---- 3 files changed, 108 insertions(+), 10 deletions(-) diff --git a/block/ssh.c b/block/ssh.c index 89a9017b63..8f78e2e4b9 100644 --- a/block/ssh.c +++ b/block/ssh.c @@ -72,6 +72,10 @@ typedef struct BDRVSSHState { * updated if it changes (eg by writing at the end of the file). */ LIBSSH2_SFTP_ATTRIBUTES attrs; + + /* Used to warn if 'flush' is not supported. */ + char *hostport; + bool unsafe_flush_warning; } BDRVSSHState; static void ssh_state_init(BDRVSSHState *s) @@ -84,6 +88,7 @@ static void ssh_state_init(BDRVSSHState *s) static void ssh_state_free(BDRVSSHState *s) { + g_free(s->hostport); if (s->sftp_handle) { libssh2_sftp_close(s->sftp_handle); } @@ -479,7 +484,6 @@ static int connect_to_ssh(BDRVSSHState *s, QDict *options, Error *err = NULL; const char *host, *user, *path, *host_key_check; int port; - char *hostport = NULL; host = qdict_get_str(options, "host"); @@ -507,9 +511,12 @@ static int connect_to_ssh(BDRVSSHState *s, QDict *options, host_key_check = "yes"; } + /* Construct the host:port name for inet_connect. */ + g_free(s->hostport); + s->hostport = g_strdup_printf("%s:%d", host, port); + /* Open the socket and connect. */ - hostport = g_strdup_printf("%s:%d", host, port); - s->sock = inet_connect(hostport, &err); + s->sock = inet_connect(s->hostport, &err); if (err != NULL) { ret = -errno; qerror_report_err(err); @@ -581,7 +588,6 @@ static int connect_to_ssh(BDRVSSHState *s, QDict *options, qdict_del(options, "path"); qdict_del(options, "host_key_check"); - g_free(hostport); return 0; err: @@ -600,7 +606,6 @@ static int connect_to_ssh(BDRVSSHState *s, QDict *options, libssh2_session_free(s->session); } s->session = NULL; - g_free(hostport); return ret; } @@ -953,6 +958,68 @@ static coroutine_fn int ssh_co_writev(BlockDriverState *bs, return ret; } +static void unsafe_flush_warning(BDRVSSHState *s, const char *what) +{ + if (!s->unsafe_flush_warning) { + error_report("warning: ssh server %s does not support fsync", + s->hostport); + if (what) { + error_report("to support fsync, you need %s", what); + } + s->unsafe_flush_warning = true; + } +} + +#ifdef HAS_LIBSSH2_SFTP_FSYNC + +static coroutine_fn int ssh_flush(BDRVSSHState *s) +{ + int r; + + DPRINTF("fsync"); + again: + r = libssh2_sftp_fsync(s->sftp_handle); + if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) { + co_yield(s); + goto again; + } + if (r == LIBSSH2_ERROR_SFTP_PROTOCOL && + libssh2_sftp_last_error(s->sftp) == LIBSSH2_FX_OP_UNSUPPORTED) { + unsafe_flush_warning(s, "OpenSSH >= 6.3"); + return 0; + } + if (r < 0) { + sftp_error_report(s, "fsync failed"); + return -EIO; + } + + return 0; +} + +static coroutine_fn int ssh_co_flush(BlockDriverState *bs) +{ + BDRVSSHState *s = bs->opaque; + int ret; + + qemu_co_mutex_lock(&s->lock); + ret = ssh_flush(s); + qemu_co_mutex_unlock(&s->lock); + + return ret; +} + +#else /* !HAS_LIBSSH2_SFTP_FSYNC */ + +static coroutine_fn int ssh_co_flush(BlockDriverState *bs) +{ + BDRVSSHState *s = bs->opaque; + + unsafe_flush_warning(s, "libssh2 >= 1.4.4"); + return 0; +} + +#endif /* !HAS_LIBSSH2_SFTP_FSYNC */ + static int64_t ssh_getlength(BlockDriverState *bs) { BDRVSSHState *s = bs->opaque; @@ -976,6 +1043,7 @@ static BlockDriver bdrv_ssh = { .bdrv_co_readv = ssh_co_readv, .bdrv_co_writev = ssh_co_writev, .bdrv_getlength = ssh_getlength, + .bdrv_co_flush_to_disk = ssh_co_flush, .create_options = ssh_create_options, }; diff --git a/configure b/configure index e6c5d2c3f6..a97bf311d3 100755 --- a/configure +++ b/configure @@ -2356,6 +2356,31 @@ EOF fi fi +########################################## +# libssh2_sftp_fsync probe + +if test "$libssh2" = "yes"; then + cat > $TMPC < +#include +#include +int main(void) { + LIBSSH2_SESSION *session; + LIBSSH2_SFTP *sftp; + LIBSSH2_SFTP_HANDLE *sftp_handle; + session = libssh2_session_init (); + sftp = libssh2_sftp_init (session); + sftp_handle = libssh2_sftp_open (sftp, "/", 0, 0); + libssh2_sftp_fsync (sftp_handle); + return 0; +} +EOF + # libssh2_cflags/libssh2_libs defined in previous test. + if compile_prog "$libssh2_cflags" "$libssh2_libs" ; then + QEMU_CFLAGS="-DHAS_LIBSSH2_SFTP_FSYNC $QEMU_CFLAGS" + fi +fi + ########################################## # linux-aio probe diff --git a/qemu-doc.texi b/qemu-doc.texi index 5b36004873..dfea4d3799 100644 --- a/qemu-doc.texi +++ b/qemu-doc.texi @@ -1081,11 +1081,16 @@ tools only use MD5 to print fingerprints). Currently authentication must be done using ssh-agent. Other authentication methods may be supported in future. -Note: The ssh driver does not obey disk flush requests (ie. to commit -data to the backing disk when the guest requests it). This is because -the underlying protocol (SFTP) does not support this. Thus there is a -risk of guest disk corruption if the remote server or network goes -down during writes. +Note: Many ssh servers do not support an @code{fsync}-style operation. +The ssh driver cannot guarantee that disk flush requests are +obeyed, and this causes a risk of disk corruption if the remote +server or network goes down during writes. The driver will +print a warning when @code{fsync} is not supported: + +warning: ssh server @code{ssh.example.com:22} does not support fsync + +With sufficiently new versions of libssh2 and OpenSSH, @code{fsync} is +supported. @node pcsys_network @section Network emulation -- cgit v1.2.3 From 342809e807ecb55551e579942f535966251cbe27 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Tue, 9 Apr 2013 15:30:55 +0100 Subject: iotests: Add 'check -ssh' option to test Secure Shell block device. Note in order to run these tests on ssh, you must be running a local ssh daemon, and that daemon must accept loopback connections, and ssh-agent has to be set up to allow logins on the local daemon. In other words, the following command should just work without demanding any passphrase: ssh localhost Signed-off-by: Richard W.M. Jones Signed-off-by: Stefan Hajnoczi --- tests/qemu-iotests/common | 5 +++++ tests/qemu-iotests/common.rc | 3 +++ 2 files changed, 8 insertions(+) diff --git a/tests/qemu-iotests/common b/tests/qemu-iotests/common index b3aad89e2c..6826ea72fe 100644 --- a/tests/qemu-iotests/common +++ b/tests/qemu-iotests/common @@ -137,6 +137,7 @@ check options -rbd test rbd -sheepdog test sheepdog -nbd test nbd + -ssh test ssh -xdiff graphical mode diff -nocache use O_DIRECT on backing file -misalign misalign memory allocations @@ -206,6 +207,10 @@ testlist options IMGPROTO=nbd xpand=false ;; + -ssh) + IMGPROTO=ssh + xpand=false + ;; -nocache) QEMU_IO_OPTIONS="$QEMU_IO_OPTIONS --nocache" xpand=false diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc index e522d617aa..a536bf70a7 100644 --- a/tests/qemu-iotests/common.rc +++ b/tests/qemu-iotests/common.rc @@ -52,6 +52,9 @@ if [ "$IMGPROTO" = "file" ]; then elif [ "$IMGPROTO" = "nbd" ]; then TEST_IMG_FILE=$TEST_DIR/t.$IMGFMT TEST_IMG="nbd:127.0.0.1:10810" +elif [ "$IMGPROTO" = "ssh" ]; then + TEST_IMG_FILE=$TEST_DIR/t.$IMGFMT + TEST_IMG="ssh://127.0.0.1$TEST_IMG_FILE" else TEST_IMG=$IMGPROTO:$TEST_DIR/t.$IMGFMT fi -- cgit v1.2.3 From dc7588c1eb3008bda53dde1d6b890cd299758155 Mon Sep 17 00:00:00 2001 From: Josh Durgin Date: Fri, 29 Mar 2013 13:03:23 -0700 Subject: rbd: add an asynchronous flush The existing bdrv_co_flush_to_disk implementation uses rbd_flush(), which is sychronous and causes the main qemu thread to block until it is complete. This results in unresponsiveness and extra latency for the guest. Fix this by using an asynchronous version of flush. This was added to librbd with a special #define to indicate its presence, since it will be backported to stable versions. Thus, there is no need to check the version of librbd. Implement this as bdrv_aio_flush, since it matches other aio functions in the rbd block driver, and leave out bdrv_co_flush_to_disk when the asynchronous version is available. Reported-by: Oliver Francke Signed-off-by: Josh Durgin Signed-off-by: Stefan Hajnoczi --- block/rbd.c | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/block/rbd.c b/block/rbd.c index 1a8ea6d492..141b488968 100644 --- a/block/rbd.c +++ b/block/rbd.c @@ -63,7 +63,8 @@ typedef enum { RBD_AIO_READ, RBD_AIO_WRITE, - RBD_AIO_DISCARD + RBD_AIO_DISCARD, + RBD_AIO_FLUSH } RBDAIOCmd; typedef struct RBDAIOCB { @@ -379,8 +380,7 @@ static void qemu_rbd_complete_aio(RADOSCB *rcb) r = rcb->ret; - if (acb->cmd == RBD_AIO_WRITE || - acb->cmd == RBD_AIO_DISCARD) { + if (acb->cmd != RBD_AIO_READ) { if (r < 0) { acb->ret = r; acb->error = 1; @@ -659,6 +659,16 @@ static int rbd_aio_discard_wrapper(rbd_image_t image, #endif } +static int rbd_aio_flush_wrapper(rbd_image_t image, + rbd_completion_t comp) +{ +#ifdef LIBRBD_SUPPORTS_AIO_FLUSH + return rbd_aio_flush(image, comp); +#else + return -ENOTSUP; +#endif +} + static BlockDriverAIOCB *rbd_start_aio(BlockDriverState *bs, int64_t sector_num, QEMUIOVector *qiov, @@ -679,7 +689,7 @@ static BlockDriverAIOCB *rbd_start_aio(BlockDriverState *bs, acb = qemu_aio_get(&rbd_aiocb_info, bs, cb, opaque); acb->cmd = cmd; acb->qiov = qiov; - if (cmd == RBD_AIO_DISCARD) { + if (cmd == RBD_AIO_DISCARD || cmd == RBD_AIO_FLUSH) { acb->bounce = NULL; } else { acb->bounce = qemu_blockalign(bs, qiov->size); @@ -723,6 +733,9 @@ static BlockDriverAIOCB *rbd_start_aio(BlockDriverState *bs, case RBD_AIO_DISCARD: r = rbd_aio_discard_wrapper(s->image, off, size, c); break; + case RBD_AIO_FLUSH: + r = rbd_aio_flush_wrapper(s->image, c); + break; default: r = -EINVAL; } @@ -762,6 +775,16 @@ static BlockDriverAIOCB *qemu_rbd_aio_writev(BlockDriverState *bs, RBD_AIO_WRITE); } +#ifdef LIBRBD_SUPPORTS_AIO_FLUSH +static BlockDriverAIOCB *qemu_rbd_aio_flush(BlockDriverState *bs, + BlockDriverCompletionFunc *cb, + void *opaque) +{ + return rbd_start_aio(bs, 0, NULL, 0, cb, opaque, RBD_AIO_FLUSH); +} + +#else + static int qemu_rbd_co_flush(BlockDriverState *bs) { #if LIBRBD_VERSION_CODE >= LIBRBD_VERSION(0, 1, 1) @@ -772,6 +795,7 @@ static int qemu_rbd_co_flush(BlockDriverState *bs) return 0; #endif } +#endif static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi) { @@ -949,7 +973,12 @@ static BlockDriver bdrv_rbd = { .bdrv_aio_readv = qemu_rbd_aio_readv, .bdrv_aio_writev = qemu_rbd_aio_writev, + +#ifdef LIBRBD_SUPPORTS_AIO_FLUSH + .bdrv_aio_flush = qemu_rbd_aio_flush, +#else .bdrv_co_flush_to_disk = qemu_rbd_co_flush, +#endif #ifdef LIBRBD_SUPPORTS_DISCARD .bdrv_aio_discard = qemu_rbd_aio_discard, -- cgit v1.2.3