#include <libssh2_sftp.h>
#include "block/block_int.h"
+#include "block/qdict.h"
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "qemu/option.h"
#include "qemu/sockets.h"
#include "qemu/uri.h"
#include "qapi/qapi-visit-sockets.h"
+#include "qapi/qapi-visit-block-core.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qstring.h"
#include "qapi/qobject-input-visitor.h"
#include "qapi/qobject-output-visitor.h"
+#include "trace.h"
-/* DEBUG_SSH=1 enables the DPRINTF (debugging printf) statements in
- * this block driver code.
- *
+/*
* TRACE_LIBSSH2=<bitmask> 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 <bitmask> 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;
switch (r) {
case LIBSSH2_KNOWNHOST_CHECK_MATCH:
/* OK */
- DPRINTF("host key OK: %s", found->key);
+ trace_ssh_check_host_key_knownhosts(found->key);
break;
case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
ret = -EINVAL;
}
static int check_host_key(BDRVSSHState *s, const char *host, int port,
- const char *host_key_check, Error **errp)
+ SshHostKeyCheck *hkc, Error **errp)
{
- /* host_key_check=no */
- if (strcmp(host_key_check, "no") == 0) {
- return 0;
- }
-
- /* host_key_check=md5:xx:yy:zz:... */
- if (strncmp(host_key_check, "md5:", 4) == 0) {
- return check_host_key_hash(s, &host_key_check[4],
- LIBSSH2_HOSTKEY_HASH_MD5, 16, errp);
- }
+ SshHostKeyCheckMode mode;
- /* host_key_check=sha1:xx:yy:zz:... */
- if (strncmp(host_key_check, "sha1:", 5) == 0) {
- return check_host_key_hash(s, &host_key_check[5],
- LIBSSH2_HOSTKEY_HASH_SHA1, 20, errp);
+ if (hkc) {
+ mode = hkc->mode;
+ } else {
+ mode = SSH_HOST_KEY_CHECK_MODE_KNOWN_HOSTS;
}
- /* host_key_check=yes */
- if (strcmp(host_key_check, "yes") == 0) {
+ switch (mode) {
+ case SSH_HOST_KEY_CHECK_MODE_NONE:
+ return 0;
+ case SSH_HOST_KEY_CHECK_MODE_HASH:
+ if (hkc->u.hash.type == SSH_HOST_KEY_CHECK_HASH_TYPE_MD5) {
+ return check_host_key_hash(s, hkc->u.hash.hash,
+ LIBSSH2_HOSTKEY_HASH_MD5, 16, errp);
+ } else if (hkc->u.hash.type == SSH_HOST_KEY_CHECK_HASH_TYPE_SHA1) {
+ return check_host_key_hash(s, hkc->u.hash.hash,
+ LIBSSH2_HOSTKEY_HASH_SHA1, 20, errp);
+ }
+ g_assert_not_reached();
+ break;
+ case SSH_HOST_KEY_CHECK_MODE_KNOWN_HOSTS:
return check_host_key_knownhosts(s, host, port, errp);
+ default:
+ g_assert_not_reached();
}
- error_setg(errp, "unknown host_key_check setting (%s)", host_key_check);
return -EINVAL;
}
.type = QEMU_OPT_NUMBER,
.help = "Port to connect to",
},
- {
- .name = "path",
- .type = QEMU_OPT_STRING,
- .help = "Path of the image on the host",
- },
- {
- .name = "user",
- .type = QEMU_OPT_STRING,
- .help = "User as which to connect",
- },
{
.name = "host_key_check",
.type = QEMU_OPT_STRING,
},
};
-static bool ssh_process_legacy_socket_options(QDict *output_opts,
- QemuOpts *legacy_opts,
- Error **errp)
+static bool ssh_process_legacy_options(QDict *output_opts,
+ QemuOpts *legacy_opts,
+ Error **errp)
{
const char *host = qemu_opt_get(legacy_opts, "host");
const char *port = qemu_opt_get(legacy_opts, "port");
+ const char *host_key_check = qemu_opt_get(legacy_opts, "host_key_check");
if (!host && port) {
error_setg(errp, "port may not be used without host");
qdict_put_str(output_opts, "server.port", port ?: stringify(22));
}
- return true;
-}
-
-static InetSocketAddress *ssh_config(QDict *options, Error **errp)
-{
- InetSocketAddress *inet = NULL;
- QDict *addr = NULL;
- QObject *crumpled_addr = NULL;
- Visitor *iv = NULL;
- Error *local_error = NULL;
-
- qdict_extract_subqdict(options, &addr, "server.");
- if (!qdict_size(addr)) {
- error_setg(errp, "SSH server address missing");
- goto out;
- }
-
- crumpled_addr = qdict_crumple(addr, errp);
- if (!crumpled_addr) {
- goto out;
- }
-
- /*
- * FIXME .numeric, .to, .ipv4 or .ipv6 don't work with -drive.
- * .to doesn't matter, it's ignored anyway.
- * That's because when @options come from -blockdev or
- * blockdev_add, members are typed according to the QAPI schema,
- * but when they come from -drive, they're all QString. The
- * visitor expects the former.
- */
- iv = qobject_input_visitor_new(crumpled_addr);
- visit_type_InetSocketAddress(iv, NULL, &inet, &local_error);
- if (local_error) {
- error_propagate(errp, local_error);
- goto out;
+ if (host_key_check) {
+ if (strcmp(host_key_check, "no") == 0) {
+ qdict_put_str(output_opts, "host-key-check.mode", "none");
+ } else if (strncmp(host_key_check, "md5:", 4) == 0) {
+ qdict_put_str(output_opts, "host-key-check.mode", "hash");
+ qdict_put_str(output_opts, "host-key-check.type", "md5");
+ qdict_put_str(output_opts, "host-key-check.hash",
+ &host_key_check[4]);
+ } else if (strncmp(host_key_check, "sha1:", 5) == 0) {
+ qdict_put_str(output_opts, "host-key-check.mode", "hash");
+ qdict_put_str(output_opts, "host-key-check.type", "sha1");
+ qdict_put_str(output_opts, "host-key-check.hash",
+ &host_key_check[5]);
+ } else if (strcmp(host_key_check, "yes") == 0) {
+ qdict_put_str(output_opts, "host-key-check.mode", "known_hosts");
+ } else {
+ error_setg(errp, "unknown host_key_check setting (%s)",
+ host_key_check);
+ return false;
+ }
}
-out:
- QDECREF(addr);
- qobject_decref(crumpled_addr);
- visit_free(iv);
- return inet;
+ return true;
}
-static int connect_to_ssh(BDRVSSHState *s, QDict *options,
- int ssh_flags, int creat_mode, Error **errp)
+static BlockdevOptionsSsh *ssh_parse_options(QDict *options, Error **errp)
{
- int r, ret;
+ BlockdevOptionsSsh *result = NULL;
QemuOpts *opts = NULL;
Error *local_err = NULL;
- const char *user, *path, *host_key_check;
- long port = 0;
+ const QDictEntry *e;
+ Visitor *v;
+ /* Translate legacy options */
opts = qemu_opts_create(&ssh_runtime_opts, NULL, 0, &error_abort);
qemu_opts_absorb_qdict(opts, options, &local_err);
if (local_err) {
- ret = -EINVAL;
error_propagate(errp, local_err);
- goto err;
+ goto fail;
}
- if (!ssh_process_legacy_socket_options(options, opts, errp)) {
- ret = -EINVAL;
- goto err;
+ if (!ssh_process_legacy_options(options, opts, errp)) {
+ goto fail;
}
- path = qemu_opt_get(opts, "path");
- if (!path) {
- ret = -EINVAL;
- error_setg(errp, "No path was specified");
- goto err;
+ /* Create the QAPI object */
+ v = qobject_input_visitor_new_flat_confused(options, errp);
+ if (!v) {
+ goto fail;
}
- user = qemu_opt_get(opts, "user");
- if (!user) {
+ visit_type_BlockdevOptionsSsh(v, NULL, &result, &local_err);
+ visit_free(v);
+
+ if (local_err) {
+ error_propagate(errp, local_err);
+ goto fail;
+ }
+
+ /* Remove the processed options from the QDict (the visitor processes
+ * _all_ options in the QDict) */
+ while ((e = qdict_first(options))) {
+ qdict_del(options, e->key);
+ }
+
+fail:
+ qemu_opts_del(opts);
+ return result;
+}
+
+static int connect_to_ssh(BDRVSSHState *s, BlockdevOptionsSsh *opts,
+ int ssh_flags, int creat_mode, Error **errp)
+{
+ int r, ret;
+ const char *user;
+ long port = 0;
+
+ if (opts->has_user) {
+ user = opts->user;
+ } else {
user = g_get_user_name();
if (!user) {
error_setg_errno(errp, errno, "Can't get user name");
}
}
- host_key_check = qemu_opt_get(opts, "host_key_check");
- if (!host_key_check) {
- host_key_check = "yes";
- }
-
/* Pop the config into our state object, Exit if invalid */
- s->inet = ssh_config(options, errp);
- if (!s->inet) {
- ret = -EINVAL;
- goto err;
- }
+ s->inet = opts->server;
+ opts->server = NULL;
if (qemu_strtol(s->inet->port, NULL, 10, &port) < 0) {
error_setg(errp, "Use only numeric port value");
}
/* Check the remote host's key against known_hosts. */
- ret = check_host_key(s, s->inet->host, port, host_key_check,
- errp);
+ ret = check_host_key(s, s->inet->host, port, opts->host_key_check, errp);
if (ret < 0) {
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);
+ trace_ssh_connect_to_ssh(opts->path, ssh_flags, creat_mode);
+ s->sftp_handle = libssh2_sftp_open(s->sftp, opts->path, ssh_flags,
+ creat_mode);
if (!s->sftp_handle) {
- session_error_setg(errp, s, "failed to open remote file '%s'", path);
+ session_error_setg(errp, s, "failed to open remote file '%s'",
+ opts->path);
ret = -EINVAL;
goto err;
}
- qemu_opts_del(opts);
-
r = libssh2_sftp_fstat(s->sftp_handle, &s->attrs);
if (r < 0) {
sftp_error_setg(errp, s, "failed to read file attributes");
}
s->session = NULL;
- qemu_opts_del(opts);
-
return ret;
}
Error **errp)
{
BDRVSSHState *s = bs->opaque;
+ BlockdevOptionsSsh *opts;
int ret;
int ssh_flags;
ssh_flags |= LIBSSH2_FXF_WRITE;
}
+ opts = ssh_parse_options(options, errp);
+ if (opts == NULL) {
+ return -EINVAL;
+ }
+
/* Start up SSH. */
- ret = connect_to_ssh(s, options, ssh_flags, 0, errp);
+ ret = connect_to_ssh(s, opts, ssh_flags, 0, errp);
if (ret < 0) {
goto err;
}
/* Go non-blocking. */
libssh2_session_set_blocking(s->session, 0);
+ qapi_free_BlockdevOptionsSsh(opts);
+
return 0;
err:
}
s->sock = -1;
+ qapi_free_BlockdevOptionsSsh(opts);
+
return ret;
}
}
};
+static int ssh_co_create(BlockdevCreateOptions *options, Error **errp)
+{
+ BlockdevCreateOptionsSsh *opts = &options->u.ssh;
+ BDRVSSHState s;
+ int ret;
+
+ assert(options->driver == BLOCKDEV_DRIVER_SSH);
+
+ ssh_state_init(&s);
+
+ ret = connect_to_ssh(&s, opts->location,
+ LIBSSH2_FXF_READ|LIBSSH2_FXF_WRITE|
+ LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC,
+ 0644, errp);
+ if (ret < 0) {
+ goto fail;
+ }
+
+ if (opts->size > 0) {
+ ret = ssh_grow_file(&s, opts->size, errp);
+ if (ret < 0) {
+ goto fail;
+ }
+ }
+
+ ret = 0;
+fail:
+ ssh_state_free(&s);
+ return ret;
+}
+
static int coroutine_fn ssh_co_create_opts(const char *filename, QemuOpts *opts,
Error **errp)
{
- int r, ret;
- int64_t total_size = 0;
+ BlockdevCreateOptions *create_options;
+ BlockdevCreateOptionsSsh *ssh_opts;
+ int ret;
QDict *uri_options = NULL;
- BDRVSSHState s;
- ssh_state_init(&s);
+ create_options = g_new0(BlockdevCreateOptions, 1);
+ create_options->driver = BLOCKDEV_DRIVER_SSH;
+ ssh_opts = &create_options->u.ssh;
/* Get desired file size. */
- total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
- BDRV_SECTOR_SIZE);
- DPRINTF("total_size=%" PRIi64, total_size);
+ ssh_opts->size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
+ BDRV_SECTOR_SIZE);
+ trace_ssh_co_create_opts(ssh_opts->size);
uri_options = qdict_new();
- r = parse_uri(filename, uri_options, errp);
- if (r < 0) {
- ret = r;
+ ret = parse_uri(filename, uri_options, errp);
+ if (ret < 0) {
goto out;
}
- r = connect_to_ssh(&s, uri_options,
- LIBSSH2_FXF_READ|LIBSSH2_FXF_WRITE|
- LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC,
- 0644, errp);
- if (r < 0) {
- ret = r;
+ ssh_opts->location = ssh_parse_options(uri_options, errp);
+ if (ssh_opts->location == NULL) {
+ ret = -EINVAL;
goto out;
}
- if (total_size > 0) {
- ret = ssh_grow_file(&s, total_size, errp);
- if (ret < 0) {
- goto out;
- }
- }
-
- ret = 0;
+ ret = ssh_co_create(create_options, errp);
out:
- ssh_state_free(&s);
- if (uri_options != NULL) {
- QDECREF(uri_options);
- }
+ qobject_unref(uri_options);
+ qapi_free_BlockdevCreateOptions(create_options);
return ret;
}
BDRVSSHState *s = bs->opaque;
AioContext *ctx = bdrv_get_aio_context(bs);
- DPRINTF("co=%p", restart->co);
+ trace_ssh_restart_coroutine(restart->co);
aio_set_fd_handler(ctx, s->sock, false, NULL, NULL, NULL, NULL);
aio_co_wake(restart->co);
wr_handler = restart_coroutine;
}
- DPRINTF("s->sock=%d rd_handler=%p wr_handler=%p", s->sock,
- rd_handler, wr_handler);
+ trace_ssh_co_yield(s->sock, rd_handler, wr_handler);
aio_set_fd_handler(bdrv_get_aio_context(bs), s->sock,
false, rd_handler, wr_handler, NULL, &restart);
qemu_coroutine_yield();
- DPRINTF("s->sock=%d - back", s->sock);
+ trace_ssh_co_yield_back(s->sock);
}
/* SFTP has a function `libssh2_sftp_seek64' which seeks to a position
bool force = (flags & SSH_SEEK_FORCE) != 0;
if (force || op_read != s->offset_op_read || offset != s->offset) {
- DPRINTF("seeking to offset=%" PRIi64, offset);
+ trace_ssh_seek(offset);
libssh2_sftp_seek64(s->sftp_handle, offset);
s->offset = offset;
s->offset_op_read = op_read;
char *buf, *end_of_vec;
struct iovec *i;
- DPRINTF("offset=%" PRIi64 " size=%zu", offset, size);
+ trace_ssh_read(offset, size);
ssh_seek(s, offset, SSH_SEEK_READ);
*/
for (got = 0; got < size; ) {
again:
- DPRINTF("sftp_read buf=%p size=%zu", buf, end_of_vec - buf);
+ trace_ssh_read_buf(buf, end_of_vec - buf);
r = libssh2_sftp_read(s->sftp_handle, buf, end_of_vec - buf);
- DPRINTF("sftp_read returned %zd", r);
+ trace_ssh_read_return(r);
if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) {
co_yield(s, bs);
char *buf, *end_of_vec;
struct iovec *i;
- DPRINTF("offset=%" PRIi64 " size=%zu", offset, size);
+ trace_ssh_write(offset, size);
ssh_seek(s, offset, SSH_SEEK_WRITE);
for (written = 0; written < size; ) {
again:
- DPRINTF("sftp_write buf=%p size=%zu", buf, end_of_vec - buf);
+ trace_ssh_write_buf(buf, end_of_vec - buf);
r = libssh2_sftp_write(s->sftp_handle, buf, end_of_vec - buf);
- DPRINTF("sftp_write returned %zd", r);
+ trace_ssh_write_return(r);
if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) {
co_yield(s, bs);
static coroutine_fn int ssh_co_writev(BlockDriverState *bs,
int64_t sector_num,
- int nb_sectors, QEMUIOVector *qiov)
+ int nb_sectors, QEMUIOVector *qiov,
+ int flags)
{
BDRVSSHState *s = bs->opaque;
int ret;
+ assert(!flags);
qemu_co_mutex_lock(&s->lock);
ret = ssh_write(s, bs, sector_num * BDRV_SECTOR_SIZE,
nb_sectors * BDRV_SECTOR_SIZE, qiov);
{
int r;
- DPRINTF("fsync");
+ trace_ssh_flush();
again:
r = libssh2_sftp_fsync(s->sftp_handle);
if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) {
/* Note we cannot make a libssh2 call here. */
length = (int64_t) s->attrs.filesize;
- DPRINTF("length=%" PRIi64, length);
+ trace_ssh_getlength(length);
return length;
}
-static int ssh_truncate(BlockDriverState *bs, int64_t offset,
- PreallocMode prealloc, Error **errp)
+static int coroutine_fn ssh_co_truncate(BlockDriverState *bs, int64_t offset,
+ PreallocMode prealloc, Error **errp)
{
BDRVSSHState *s = bs->opaque;
.instance_size = sizeof(BDRVSSHState),
.bdrv_parse_filename = ssh_parse_filename,
.bdrv_file_open = ssh_file_open,
+ .bdrv_co_create = ssh_co_create,
.bdrv_co_create_opts = ssh_co_create_opts,
.bdrv_close = ssh_close,
.bdrv_has_zero_init = ssh_has_zero_init,
.bdrv_co_readv = ssh_co_readv,
.bdrv_co_writev = ssh_co_writev,
.bdrv_getlength = ssh_getlength,
- .bdrv_truncate = ssh_truncate,
+ .bdrv_co_truncate = ssh_co_truncate,
.bdrv_co_flush_to_disk = ssh_co_flush,
.create_opts = &ssh_create_opts,
};