qemu-devel
[Top][All Lists]
Advanced

[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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]