[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Qemu-devel] [PATCH v3] block: Add support for Secure Shell (ssh) bl
From: |
Anthony Liguori |
Subject: |
Re: [Qemu-devel] [PATCH v3] block: Add support for Secure Shell (ssh) block device. |
Date: |
Wed, 27 Mar 2013 10:12:42 -0500 |
User-agent: |
Notmuch/0.13.2+93~ged93d79 (http://notmuchmail.org) Emacs/23.3.1 (x86_64-pc-linux-gnu) |
"Richard W.M. Jones" <address@hidden> writes:
> From: "Richard W.M. Jones" <address@hidden>
>
> 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://address@hidden/...) and/or a port number
> (ssh://host:port/...).
>
> Current limitations:
>
> - Authentication must be done without passwords or passphrases, using
> ssh-agent. Other authentication methods are not supported. (*)
>
> - New remote files cannot be created. (*)
>
> - Uses a single connection, instead of concurrent AIO with multiple
> SSH connections.
>
> (*) = potentially easy fix
>
> 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.
>
> Thanks: Stefan Hajnoczi, Kevin Wolf.
Curl actually supports sftp already. In theory, we just need to add:
static BlockDriver bdrv_sftp = {
.format_name = "sftp",
.protocol_name = "sftp",
.instance_size = sizeof(BDRVCURLState),
.bdrv_file_open = curl_open,
.bdrv_close = curl_close,
.bdrv_getlength = curl_getlength,
.bdrv_aio_readv = curl_aio_readv,
};
To block/curl.c and it should Just Work. Have you considered doing
this through curl?
Regards,
Anthony Liguori
> ---
> block/Makefile.objs | 1 +
> block/ssh.c | 750
> ++++++++++++++++++++++++++++++++++++++++++++++++++++
> configure | 47 ++++
> qemu-doc.texi | 40 +++
> qemu-options.hx | 12 +
> 5 files changed, 850 insertions(+)
> create mode 100644 block/ssh.c
>
> diff --git a/block/Makefile.objs b/block/Makefile.objs
> index c067f38..6c4b5bc 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 0000000..f7b8d32
> --- /dev/null
> +++ b/block/ssh.c
> @@ -0,0 +1,750 @@
> +/*
> + * Secure Shell (ssh) backend for QEMU.
> + *
> + * Copyright (C) 2013 Red Hat Inc., Richard W.M. Jones <address@hidden>
> + *
> + * 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 <stdio.h>
> +#include <stdlib.h>
> +#include <stdarg.h>
> +
> +#ifndef _WIN32
> +#include <pwd.h>
> +#endif
> +
> +#include <libssh2.h>
> +#include <libssh2_sftp.h>
> +
> +#include "block/block_int.h"
> +#include "qemu/sockets.h"
> +#include "qemu/uri.h"
> +#include "qapi/qmp/qint.h"
> +
> +#define DEBUG_SSH 1
> +
> +#if DEBUG_SSH
> +#define DPRINTF(fmt,...) \
> + do { \
> + fprintf(stderr, "ssh: %-15s " fmt "\n", __func__, ##__VA_ARGS__); \
> + } while (0)
> +#else
> +#define DPRINTF(fmt,...) /* nothing */
> +#endif
> +
> +typedef struct BDRVSSHState {
> + CoMutex lock; /* coroutine 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 */
> +
> + /* file attributes (at open) */
> + LIBSSH2_SFTP_ATTRIBUTES attrs;
> +} BDRVSSHState;
> +
> +/* 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);
> +
> + if ((s)->session) {
> + char *ssh_err;
> + int ssh_err_len = sizeof ssh_err;
> + int ssh_err_code;
> +
> + libssh2_session_last_error((s)->session,
> + &ssh_err, &ssh_err_len, 0);
> + /* This is not an errno. See <libssh2.h>. */
> + ssh_err_code = libssh2_session_last_errno((s)->session);
> +
> + error_vprintf(fs, args);
> + error_printf(": %s (libssh2 error code: %d)", ssh_err, ssh_err_code);
> + }
> + else {
> + error_vprintf(fs, args);
> + }
> +
> + va_end(args);
> + error_printf("\n");
> +}
> +
> +static void
> +sftp_error_report(BDRVSSHState *s, const char *fs, ...)
> +{
> + va_list args;
> +
> + va_start(args, fs);
> +
> + if ((s)->sftp) {
> + char *ssh_err;
> + int ssh_err_len = sizeof ssh_err;
> + int ssh_err_code;
> + unsigned long sftp_err_code;
> +
> + libssh2_session_last_error((s)->session,
> + &ssh_err, &ssh_err_len, 0);
> + /* This is not an errno. See <libssh2.h>. */
> + ssh_err_code = libssh2_session_last_errno((s)->session);
> + /* See <libssh2_sftp.h>. */
> + sftp_err_code = libssh2_sftp_last_error((s)->sftp);
> +
> + error_vprintf(fs, args);
> + error_printf(": %s (libssh2 error code: %d, sftp error code: %lu)",
> + ssh_err, ssh_err_code, sftp_err_code);
> + }
> + else {
> + error_vprintf(fs, args);
> + }
> +
> + va_end(args);
> + error_printf("\n");
> +}
> +
> +#ifndef _WIN32
> +static const char *get_username(void)
> +{
> + struct passwd *pwd;
> +
> + pwd = getpwuid(geteuid ());
> + if (!pwd) {
> + error_report("failed to get current user name");
> + return NULL;
> + }
> + return pwd->pw_name;
> +}
> +#else
> +static const char *get_username(void)
> +{
> + error_report("on Windows, specify a remote user name in the URI");
> + return NULL;
> +}
> +#endif
> +
> +static int parse_uri(const char *filename, QDict *options)
> +{
> + URI *uri;
> + char *host = NULL;
> +
> + uri = uri_parse(filename);
> + if (!uri) {
> + return -EINVAL;
> + }
> +
> + if (strcmp(uri->scheme, "ssh") != 0) {
> + error_report("URI scheme must be 'ssh'");
> + goto err;
> + }
> +
> + if (!uri->server || strcmp(uri->server, "") == 0) {
> + error_report("missing hostname in URI");
> + goto err;
> + }
> +
> + if (!uri->path || strcmp(uri->path, "") == 0) {
> + error_report("missing remote path in URI");
> + 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));
> +
> + uri_free(uri);
> + free(host);
> + return 0;
> +
> + err:
> + uri_free(uri);
> + free(host);
> + 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")) {
> + error_setg(errp, "user, host, port or path cannot be used at the
> same time as a file option");
> + return;
> + }
> +
> + if (parse_uri(filename, options) < 0) {
> + error_setg(errp, "no valid URL specified");
> + }
> +}
> +
> +static int check_host_key(BDRVSSHState *s, const char *host, int port)
> +{
> + int ret;
> + const char *home;
> + char *knh_file = NULL;
> + LIBSSH2_KNOWNHOSTS *knh = NULL;
> + const char *fingerprint;
> + size_t len;
> + int type;
> +
> + 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);
> +
> + fingerprint = libssh2_session_hostkey(s->session, &len, &type);
> + if (fingerprint) {
> + struct libssh2_knownhost *found;
> + int r;
> +
> + r = libssh2_knownhost_checkp(knh, host, port, fingerprint, 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;
> + }
> + }
> +
> + ret = 0;
> +
> + out:
> + if (knh != NULL) {
> + libssh2_knownhost_free(knh);
> + }
> + g_free(knh_file);
> + return ret;
> +}
> +
> +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 bdrv_flags)
> +{
> + int r, ret, flags;
> + Error *err = NULL;
> + const char *host, *user, *path;
> + 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 = get_username();
> + if (!user) {
> + ret = -EINVAL;
> + goto err;
> + }
> + }
> +
> + /* 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;
> + }
> +
> + 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);
> + 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. */
> + flags = LIBSSH2_FXF_READ;
> + if (bdrv_flags & BDRV_O_RDWR) {
> + flags |= LIBSSH2_FXF_WRITE;
> + }
> + s->sftp_handle = libssh2_sftp_open(s->sftp, path, flags, 0);
> + 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 of '%s'",
> + path);
> + ret = -EINVAL;
> + goto err;
> + }
> +
> + /* 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");
> +
> + 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;
> +
> + s->sock = -1;
> + qemu_co_mutex_init(&s->lock);
> +
> + /* Start up SSH. */
> + ret = connect_to_ssh(s, options, bdrv_flags);
> + 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 void ssh_close(BlockDriverState *bs)
> +{
> + BDRVSSHState *s = bs->opaque;
> +
> + 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);
> + }
> +}
> +
> +static void restart_coroutine(void *opaque)
> +{
> + Coroutine *co = opaque;
> +
> + 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)
> +{
> + 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);
> +}
> +
> +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);
> +
> + /* According to the docs, this just updates a field in the
> + * sftp_handle structure, so there is no network traffic and it
> + * cannot fail.
> + */
> + libssh2_sftp_seek64(s->sftp_handle, offset);
> +
> + /* 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);
> +
> + if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) {
> + co_yield(s);
> + goto again;
> + }
> + if (r < 0) {
> + sftp_error_report(s, "read failed");
> + return -EIO;
> + }
> + if (r == 0) {
> + error_report("read failed (EOF)");
> + return -EIO;
> + }
> +
> + got += r;
> + buf += r;
> + if (buf >= end_of_vec) {
> + 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);
> +
> + /* According to the docs, this just updates a field in the
> + * sftp_handle structure, so there is no network traffic and it
> + * cannot fail.
> + */
> + libssh2_sftp_seek64(s->sftp_handle, offset);
> +
> + /* 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);
> +
> + if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) {
> + co_yield(s);
> + goto again;
> + }
> + if (r < 0) {
> + sftp_error_report(s, "write failed");
> + return -EIO;
> + }
> +
> + written += r;
> + buf += r;
> + if (buf >= end_of_vec) {
> + i++;
> + buf = i->iov_base;
> + end_of_vec = i->iov_base + i->iov_len;
> + }
> + }
> +
> + 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;
> + return (int64_t) s->attrs.filesize;
> +}
> +
> +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_close = ssh_close,
> + .bdrv_co_readv = ssh_co_readv,
> + .bdrv_co_writev = ssh_co_writev,
> + .bdrv_getlength = ssh_getlength,
> +};
> +
> +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 f2af714..d666066 100755
> --- a/configure
> +++ b/configure
> @@ -229,6 +229,7 @@ virtio_blk_data_plane=""
> gtk=""
> gtkabi="2.0"
> tpm="no"
> +libssh2=""
>
> # parse CC options first
> for opt do
> @@ -908,6 +909,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
> @@ -1166,6 +1171,7 @@ 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 " --enable-libssh2 enable ssh block device support"
> echo ""
> echo "NOTE: The object files are built at the place where configure is
> launched"
> exit 1
> @@ -2330,6 +2336,42 @@ EOF
> fi
>
> ##########################################
> +# libssh2 probe
> +if test "$libssh2" != "no" ; then
> + cat > $TMPC <<EOF
> +#include <stdio.h>
> +#include <libssh2.h>
> +#include <libssh2_sftp.h>
> +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
>
> if test "$linux_aio" != "no" ; then
> @@ -3441,6 +3483,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"
> @@ -3806,6 +3849,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=y" >> $config_host_mak
> fi
> diff --git a/qemu-doc.texi b/qemu-doc.texi
> index af84bef..7a0f373 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,45 @@ 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
>
> address@hidden disk_images_ssh
> address@hidden Secure Shell (ssh) disk images
> +
> +You can access disk images located on a remote ssh server
> +by using the ssh protocol:
> +
> address@hidden
> +qemu-system-x86_64 -drive
> file=ssh://address@hidden@@address@hidden:@var{port}]/@var{path}
> address@hidden example
> +
> +Alternative syntax using properties:
> +
> address@hidden
> +qemu-system-x86_64 -drive
> file.driver=ssh[,address@hidden,address@hidden,address@hidden,address@hidden
> address@hidden example
> +
> address@hidden is the protocol.
> +
> address@hidden is the remote user. If not specified, then the local
> +username is tried.
> +
> address@hidden 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.
> +
> address@hidden is the port number on which sshd is listening. By default
> +the standard ssh port (22) is used.
> +
> address@hidden is the path to the disk image.
> +
> +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 d7afeab..590a287 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -2106,6 +2106,18 @@ Example for Unix Domain Sockets
> qemu-system-i386 --drive file=nbd:unix:/tmp/nbd-socket
> @end example
>
> address@hidden SSH
> +QEMU supports SSH (Secure Shell) access to remote disks.
> +
> +Examples:
> address@hidden
> +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
> address@hidden 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
> --
> 1.8.1.4