static coroutine_fn int ssh_read(BDRVSSHState *s, BlockDriverState *bs, 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, bs); 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 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 coroutine_fn int ssh_flush(BDRVSSHState *s, BlockDriverState *bs) { int r; DPRINTF("fsync"); again: r = libssh2_sftp_fsync(s->sftp_handle); if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) { co_yield(s, bs); 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 int ssh_write(BDRVSSHState *s, BlockDriverState *bs, 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, bs); 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, bs); 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 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; }