diff --git a/block.c b/block.c index 602d8a443e..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); } /* @@ -3183,14 +3219,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/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/qcow2.c b/block/qcow2.c index 1d180732e9..e8934de185 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -1652,8 +1652,8 @@ 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; @@ -1661,7 +1661,7 @@ static int qcow2_save_vmstate(BlockDriverState *bs, const uint8_t *buf, BLKDBG_EVENT(bs->file, BLKDBG_VMSTATE_SAVE); bs->growable = 1; - ret = bdrv_pwrite(bs, qcow2_vm_state_offset(s) + pos, buf, size); + ret = bdrv_pwritev(bs, qcow2_vm_state_offset(s) + pos, qiov); bs->growable = growable; return ret; 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, 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/block/ssh.c b/block/ssh.c new file mode 100644 index 0000000000..8f78e2e4b9 --- /dev/null +++ b/block/ssh.c @@ -0,0 +1,1063 @@ +/* + * 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; + + /* Used to warn if 'flush' is not supported. */ + char *hostport; + bool unsafe_flush_warning; +} 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) +{ + g_free(s->hostport); + 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; + + 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"; + } + + /* 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. */ + s->sock = inet_connect(s->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"); + + 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; + + 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 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; + 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, + .bdrv_co_flush_to_disk = ssh_co_flush, + .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..a97bf311d3 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,67 @@ 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 + +########################################## +# 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 @@ -3435,6 +3503,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 +3862,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/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; diff --git a/include/block/block.h b/include/block/block.h index 9dc6aada99..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, @@ -348,6 +350,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); 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/qemu-doc.texi b/qemu-doc.texi index af84bef0e9..dfea4d3799 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,59 @@ 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: 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 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 diff --git a/savevm.c b/savevm.c index 63f4c828a9..53515cb794 100644 --- a/savevm.c +++ b/savevm.c @@ -175,7 +175,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; @@ -457,6 +458,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) { @@ -480,8 +496,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) @@ -535,7 +552,7 @@ static void qemu_fflush(QEMUFile *f) if (f->ops->writev_buffer) { if (f->iovcnt > 0) { - ret = f->ops->writev_buffer(f->opaque, f->iov, f->iovcnt); + ret = f->ops->writev_buffer(f->opaque, f->iov, f->iovcnt, f->pos); } } else { if (f->buf_index > 0) { 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 diff --git a/tests/qemu-iotests/051 b/tests/qemu-iotests/051 new file mode 100755 index 0000000000..8b51de3f31 --- /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 | _filter_qemu +} + +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..48456d5b07 --- /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_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_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_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_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 === + +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_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_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_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_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 + + +=== 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_PROG: Device needs media, but drive is empty +QEMU_PROG: Initialization of device ide-hd failed + +Testing: -drive if=virtio +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_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 + +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_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_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_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_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 === + +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_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 + +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_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_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 + +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_PROG: -drive media=cdrom,cache=invalid_value: invalid cache option + +*** done 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.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 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 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