Add vsock-listen channel mode to the Windows guest agent. The listen/accept path uses the standard QEMU socket helpers (socket_parse(), socket_listen(), qemu_accept()) and plugs the resulting SOCKET into the GMainLoop via g_io_channel_win32_new_socket().
Signed-off-by: Polina Vishneva <[email protected]> --- docs/interop/qemu-ga.rst | 4 + qga/channel-win32.c | 257 ++++++++++++++++++++++++++++++++------- qga/main.c | 6 + 3 files changed, 222 insertions(+), 45 deletions(-) diff --git a/docs/interop/qemu-ga.rst b/docs/interop/qemu-ga.rst index d16cc1b9f0..9c415fc8f8 100644 --- a/docs/interop/qemu-ga.rst +++ b/docs/interop/qemu-ga.rst @@ -59,6 +59,10 @@ Options Transport method: one of ``unix-listen``, ``virtio-serial``, or ``isa-serial``, or ``vsock-listen`` (``virtio-serial`` is the default). + .. note:: + On Windows, ``vsock-listen`` requires the viosock driver from + kvm-guest-drivers-windows recent enough to include commit ``687c776``. + .. option:: -p, --path=PATH Device/socket path (the default for virtio-serial is diff --git a/qga/channel-win32.c b/qga/channel-win32.c index 2bd6fc1538..257576815a 100644 --- a/qga/channel-win32.c +++ b/qga/channel-win32.c @@ -1,6 +1,8 @@ #include "qemu/osdep.h" #include <windows.h> #include <io.h> +#include "qapi/error.h" +#include "qemu/sockets.h" #include "guest-agent-core.h" #include "channel-common.h" @@ -17,6 +19,9 @@ typedef struct GAChannelReadState { struct GAChannel { GAChannelCommon common; + int listen_fd; + int client_fd; + HANDLE handle; GAChannelReadState rstate; GIOCondition pending_events; /* TODO: use GAWatch.pollfd.revents */ @@ -198,7 +203,116 @@ static GSource *ga_channel_create_watch(GAChannel *c) return source; } -GIOStatus ga_channel_read(GAChannel *c, char *buf, size_t size, gsize *count) +/* + * Socket mode: GIOChannel-based listen/accept/client helpers + */ + +static gboolean ga_channel_socket_client_event(GIOChannel *channel, + GIOCondition condition, + gpointer data); +static gboolean ga_channel_socket_listen_accept(GIOChannel *channel, + GIOCondition condition, + gpointer data); + +static void ga_channel_socket_listen_add(GAChannel *c, bool create) +{ + if (create) { + c->common.listen_channel = + g_io_channel_win32_new_socket(_get_osfhandle(c->listen_fd)); + g_assert(c->common.listen_channel); + } + g_io_add_watch(c->common.listen_channel, G_IO_IN, + ga_channel_socket_listen_accept, c); +} + +static gboolean ga_channel_socket_client_close(GAChannel *c) +{ + /* Free the CRT fd wrapper while the SOCKET is still valid. */ + if (c->client_fd >= 0) { + qemu_close_socket_osfhandle(c->client_fd); + c->client_fd = -1; + } + ga_channel_common_client_close(&c->common); + if (c->common.listen_channel) { + ga_channel_socket_listen_add(c, false); + } + return false; +} + +static gboolean ga_channel_socket_listen_accept(GIOChannel *channel, + GIOCondition condition, + gpointer data) +{ + GAChannel *c = data; + int client_fd; + GIOChannel *client_ch; + bool accepted = false; + + client_fd = qemu_accept(c->listen_fd, NULL, NULL); + if (client_fd < 0) { + g_warning("error accepting connection: %s", strerror(errno)); + goto out; + } + + /* + * g_io_channel_win32_new_socket() makes the socket nonblocking via + * WSAEventSelect. + */ + client_ch = g_io_channel_win32_new_socket(_get_osfhandle(client_fd)); + g_assert(client_ch); + + if (ga_channel_common_client_add(&c->common, client_ch, + ga_channel_socket_client_event, c)) { + g_warning("error setting up connection"); + /* + * Release the CRT fd wrapper while its SOCKET is still open - only + * then qemu_close_socket_osfhandle() can detach the fd and let the + * channel teardown perform the single closesocket(). + */ + qemu_close_socket_osfhandle(client_fd); + ga_channel_common_gio_destroy(client_ch); + goto out; + } + c->client_fd = client_fd; + accepted = true; + +out: + /* only accept 1 connection at a time */ + return !accepted; +} + +static gboolean ga_channel_socket_client_event(GIOChannel *channel, + GIOCondition condition, + gpointer data) +{ + GAChannel *c = data; + + g_assert(c); + if (!c->common.event_cb(condition, c->common.user_data)) { + return ga_channel_socket_client_close(c); + } + /* + * A peer that sends data and disconnects at once is never reported: + * GLib turns the coalesced FD_READ|FD_CLOSE into a plain G_IO_IN, + * and Winsock records FD_CLOSE only once, so no later event follows. + * Peek after each event to detect the disconnect. + */ + if (c->client_fd >= 0) { + char peek; + ssize_t r = recv(c->client_fd, &peek, 1, MSG_PEEK); + if (r == 0 || (r < 0 && errno != EAGAIN)) { + return ga_channel_socket_client_close(c); + } + } + return true; +} + +/* + * Handle mode: overlapped I/O read/write + */ + +static GIOStatus ga_channel_handle_read(GAChannel *c, char *buf, size_t size, + gsize *count) { GAChannelReadState *rs = &c->rstate; GIOStatus status; @@ -221,8 +335,8 @@ GIOStatus ga_channel_read(GAChannel *c, char *buf, size_t size, gsize *count) return status; } -static GIOStatus ga_channel_write(GAChannel *c, const char *buf, size_t size, - size_t *count) +static GIOStatus ga_channel_handle_write(GAChannel *c, const char *buf, + size_t size, size_t *count) { GIOStatus status; OVERLAPPED ov = {0}; @@ -262,13 +376,25 @@ static GIOStatus ga_channel_write(GAChannel *c, const char *buf, size_t size, return status; } +GIOStatus ga_channel_read(GAChannel *c, char *buf, size_t size, gsize *count) +{ + if (c->common.method == GA_CHANNEL_VSOCK_LISTEN) { + return ga_channel_common_read(&c->common, buf, size, count); + } + return ga_channel_handle_read(c, buf, size, count); +} + GIOStatus ga_channel_write_all(GAChannel *c, const char *buf, size_t size) { GIOStatus status = G_IO_STATUS_NORMAL; size_t count = 0; + if (c->common.method == GA_CHANNEL_VSOCK_LISTEN) { + return ga_channel_common_write_all(&c->common, buf, size); + } + while (size) { - status = ga_channel_write(c, buf, size, &count); + status = ga_channel_handle_write(c, buf, size, &count); if (status == G_IO_STATUS_NORMAL) { size -= count; buf += count; @@ -276,86 +402,127 @@ GIOStatus ga_channel_write_all(GAChannel *c, const char *buf, size_t size) break; } } - return status; } static gboolean ga_channel_open(GAChannel *c, GAChannelMethod method, - const gchar *path) + const gchar *path, Error **errp) { COMMTIMEOUTS comTimeOut = {0}; gchar newpath[MAXPATHLEN] = {0}; comTimeOut.ReadIntervalTimeout = 1; - if (method != GA_CHANNEL_VIRTIO_SERIAL && method != GA_CHANNEL_ISA_SERIAL) { - g_critical("unsupported communication method"); - return false; - } + c->common.method = method; - if (method == GA_CHANNEL_ISA_SERIAL) { - snprintf(newpath, sizeof(newpath), "\\\\.\\%s", path); - } else { - g_strlcpy(newpath, path, sizeof(newpath)); - } + switch (method) { + case GA_CHANNEL_VIRTIO_SERIAL: + case GA_CHANNEL_ISA_SERIAL: + if (method == GA_CHANNEL_ISA_SERIAL) { + snprintf(newpath, sizeof(newpath), "\\\\.\\%s", path); + } else { + g_strlcpy(newpath, path, sizeof(newpath)); + } - c->handle = CreateFile(newpath, GENERIC_READ | GENERIC_WRITE, 0, NULL, - OPEN_EXISTING, - FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, NULL); - if (c->handle == INVALID_HANDLE_VALUE) { - g_autofree gchar *emsg = g_win32_error_message(GetLastError()); - g_critical("error opening path %s: %s", newpath, emsg); - return false; + c->handle = CreateFile(newpath, GENERIC_READ | GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, + FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, + NULL); + if (c->handle == INVALID_HANDLE_VALUE) { + g_autofree gchar *emsg = g_win32_error_message(GetLastError()); + error_setg(errp, "error opening path %s: %s", newpath, emsg); + c->handle = NULL; + return false; + } + + if (method == GA_CHANNEL_ISA_SERIAL + && !SetCommTimeouts(c->handle, &comTimeOut)) { + g_autofree gchar *emsg = g_win32_error_message(GetLastError()); + error_setg(errp, "error setting timeout for com port: %s", emsg); + CloseHandle(c->handle); + c->handle = NULL; + return false; + } + return true; + +#ifdef CONFIG_AF_VSOCK + case GA_CHANNEL_VSOCK_LISTEN: { + int fd = ga_channel_common_vsock_listen(path, errp); + if (fd < 0) { + return false; + } + + c->listen_fd = fd; + ga_channel_socket_listen_add(c, true); + return true; } +#endif - if (method == GA_CHANNEL_ISA_SERIAL - && !SetCommTimeouts(c->handle, &comTimeOut)) { - g_autofree gchar *emsg = g_win32_error_message(GetLastError()); - g_critical("error setting timeout for com port: %s", emsg); - CloseHandle(c->handle); + default: + error_setg(errp, "unsupported communication method"); return false; } - - return true; } GAChannel *ga_channel_new(GAChannelMethod method, const gchar *path, int listen_fd, GAChannelCallback cb, gpointer opaque) { + Error *err = NULL; GAChannel *c = g_new0(GAChannel, 1); - SECURITY_ATTRIBUTES sec_attrs; - if (!ga_channel_open(c, method, path)) { - g_critical("error opening channel"); - g_free(c); + /* listen_fd is for systemd socket activation; not applicable on Windows. */ + (void)listen_fd; + c->listen_fd = -1; + c->client_fd = -1; + c->common.event_cb = cb; + c->common.user_data = opaque; + + if (!ga_channel_open(c, method, path, &err)) { + g_critical("%s", error_get_pretty(err)); + error_free(err); + ga_channel_free(c); return NULL; } - c->common.event_cb = cb; - c->common.user_data = opaque; + if (c->common.method != GA_CHANNEL_VSOCK_LISTEN) { + SECURITY_ATTRIBUTES sec_attrs; + + sec_attrs.nLength = sizeof(SECURITY_ATTRIBUTES); + sec_attrs.lpSecurityDescriptor = NULL; + sec_attrs.bInheritHandle = false; - sec_attrs.nLength = sizeof(SECURITY_ATTRIBUTES); - sec_attrs.lpSecurityDescriptor = NULL; - sec_attrs.bInheritHandle = false; + c->rstate.buf_size = QGA_READ_COUNT_DEFAULT; + c->rstate.buf = g_malloc(QGA_READ_COUNT_DEFAULT); + c->rstate.ov.hEvent = CreateEvent(&sec_attrs, FALSE, FALSE, NULL); - c->rstate.buf_size = QGA_READ_COUNT_DEFAULT; - c->rstate.buf = g_malloc(QGA_READ_COUNT_DEFAULT); - c->rstate.ov.hEvent = CreateEvent(&sec_attrs, FALSE, FALSE, NULL); + c->source = ga_channel_create_watch(c); + g_source_attach(c->source, NULL); + } - c->source = ga_channel_create_watch(c); - g_source_attach(c->source, NULL); return c; } void ga_channel_free(GAChannel *c) { + if (c->common.method == GA_CHANNEL_VSOCK_LISTEN) { + /* Free the CRT fd wrappers before the GIOChannels close the SOCKETs. */ + if (c->listen_fd >= 0) { + qemu_close_socket_osfhandle(c->listen_fd); + } + if (c->client_fd >= 0) { + qemu_close_socket_osfhandle(c->client_fd); + } + ga_channel_common_free(&c->common); + g_free(c); + return; + } + + /* Handle mode */ if (c->source) { g_source_destroy(c->source); } if (c->rstate.ov.hEvent) { CloseHandle(c->rstate.ov.hEvent); } - - ga_channel_common_free(&c->common); g_free(c->rstate.buf); g_free(c); } diff --git a/qga/main.c b/qga/main.c index fd19c7037d..35068ac7bb 100644 --- a/qga/main.c +++ b/qga/main.c @@ -1675,6 +1675,12 @@ int main(int argc, char **argv) config->log_level = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL; + /* Required for Windows, harmless otherwise. */ + if (socket_init() < 0) { + g_critical("failed to initialize sockets"); + goto end; + } + qemu_init_exec_dir(argv[0]); qga_qmp_init_marshal(&ga_commands); -- 2.54.0
