diff --git a/block/ssh.c b/block/ssh.c index 89a9017b63..8f78e2e4b9 100644 --- a/block/ssh.c +++ b/block/ssh.c @@ -72,6 +72,10 @@ typedef struct BDRVSSHState { * 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) @@ -84,6 +88,7 @@ static void ssh_state_init(BDRVSSHState *s) static void ssh_state_free(BDRVSSHState *s) { + g_free(s->hostport); if (s->sftp_handle) { libssh2_sftp_close(s->sftp_handle); } @@ -479,7 +484,6 @@ static int connect_to_ssh(BDRVSSHState *s, QDict *options, Error *err = NULL; const char *host, *user, *path, *host_key_check; int port; - char *hostport = NULL; host = qdict_get_str(options, "host"); @@ -507,9 +511,12 @@ static int connect_to_ssh(BDRVSSHState *s, QDict *options, 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. */ - hostport = g_strdup_printf("%s:%d", host, port); - s->sock = inet_connect(hostport, &err); + s->sock = inet_connect(s->hostport, &err); if (err != NULL) { ret = -errno; qerror_report_err(err); @@ -581,7 +588,6 @@ static int connect_to_ssh(BDRVSSHState *s, QDict *options, qdict_del(options, "path"); qdict_del(options, "host_key_check"); - g_free(hostport); return 0; err: @@ -600,7 +606,6 @@ static int connect_to_ssh(BDRVSSHState *s, QDict *options, libssh2_session_free(s->session); } s->session = NULL; - g_free(hostport); return ret; } @@ -953,6 +958,68 @@ static coroutine_fn int ssh_co_writev(BlockDriverState *bs, 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; @@ -976,6 +1043,7 @@ static BlockDriver bdrv_ssh = { .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, }; diff --git a/configure b/configure index e6c5d2c3f6..a97bf311d3 100755 --- a/configure +++ b/configure @@ -2356,6 +2356,31 @@ EOF 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 diff --git a/qemu-doc.texi b/qemu-doc.texi index 5b36004873..dfea4d3799 100644 --- a/qemu-doc.texi +++ b/qemu-doc.texi @@ -1081,11 +1081,16 @@ tools only use MD5 to print fingerprints). 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. +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