| #include "qemu/osdep.h" |
| #include <windows.h> |
| #include <io.h> |
| #include "guest-agent-core.h" |
| #include "channel.h" |
| |
| typedef struct GAChannelReadState { |
| guint thread_id; |
| uint8_t *buf; |
| size_t buf_size; |
| size_t cur; /* current buffer start */ |
| size_t pending; /* pending buffered bytes to read */ |
| OVERLAPPED ov; |
| bool ov_pending; /* whether on async read is outstanding */ |
| } GAChannelReadState; |
| |
| struct GAChannel { |
| HANDLE handle; |
| GAChannelCallback cb; |
| gpointer user_data; |
| GAChannelReadState rstate; |
| GIOCondition pending_events; /* TODO: use GAWatch.pollfd.revents */ |
| GSource *source; |
| }; |
| |
| typedef struct GAWatch { |
| GSource source; |
| GPollFD pollfd; |
| GAChannel *channel; |
| GIOCondition events_mask; |
| } GAWatch; |
| |
| /* |
| * Called by glib prior to polling to set up poll events if polling is needed. |
| * |
| */ |
| static gboolean ga_channel_prepare(GSource *source, gint *timeout_ms) |
| { |
| GAWatch *watch = (GAWatch *)source; |
| GAChannel *c = (GAChannel *)watch->channel; |
| GAChannelReadState *rs = &c->rstate; |
| DWORD count_read, count_to_read = 0; |
| bool success; |
| GIOCondition new_events = 0; |
| |
| g_debug("prepare"); |
| /* go ahead and submit another read if there's room in the buffer |
| * and no previous reads are outstanding |
| */ |
| if (!rs->ov_pending) { |
| if (rs->cur + rs->pending >= rs->buf_size) { |
| if (rs->cur) { |
| memmove(rs->buf, rs->buf + rs->cur, rs->pending); |
| rs->cur = 0; |
| } |
| } |
| count_to_read = rs->buf_size - rs->cur - rs->pending; |
| } |
| |
| if (rs->ov_pending || count_to_read <= 0) { |
| goto out; |
| } |
| |
| /* submit the read */ |
| success = ReadFile(c->handle, rs->buf + rs->cur + rs->pending, |
| count_to_read, &count_read, &rs->ov); |
| if (success) { |
| rs->pending += count_read; |
| rs->ov_pending = false; |
| } else { |
| if (GetLastError() == ERROR_IO_PENDING) { |
| rs->ov_pending = true; |
| } else { |
| new_events |= G_IO_ERR; |
| } |
| } |
| |
| out: |
| /* don't block forever, iterate the main loop every once in a while */ |
| *timeout_ms = 500; |
| /* if there's data in the read buffer, or another event is pending, |
| * skip polling and issue user cb. |
| */ |
| if (rs->pending) { |
| new_events |= G_IO_IN; |
| } |
| c->pending_events |= new_events; |
| return !!c->pending_events; |
| } |
| |
| /* |
| * Called by glib after an outstanding read request is completed. |
| */ |
| static gboolean ga_channel_check(GSource *source) |
| { |
| GAWatch *watch = (GAWatch *)source; |
| GAChannel *c = (GAChannel *)watch->channel; |
| GAChannelReadState *rs = &c->rstate; |
| DWORD count_read, error; |
| BOOL success; |
| |
| GIOCondition new_events = 0; |
| |
| g_debug("check"); |
| |
| /* failing this implies we issued a read that completed immediately, |
| * yet no data was placed into the buffer (and thus we did not skip |
| * polling). but since EOF is not obtainable until we retrieve an |
| * overlapped result, it must be the case that there was data placed |
| * into the buffer, or an error was generated by Readfile(). in either |
| * case, we should've skipped the polling for this round. |
| */ |
| g_assert(rs->ov_pending); |
| |
| success = GetOverlappedResult(c->handle, &rs->ov, &count_read, FALSE); |
| if (success) { |
| g_debug("thread: overlapped result, count_read: %d", (int)count_read); |
| rs->pending += count_read; |
| new_events |= G_IO_IN; |
| } else { |
| error = GetLastError(); |
| if (error == 0 || error == ERROR_HANDLE_EOF || |
| error == ERROR_NO_SYSTEM_RESOURCES || |
| error == ERROR_OPERATION_ABORTED) { |
| /* note: On WinXP SP3 with rhel6ga virtio-win-1.1.16 vioser drivers, |
| * ENSR seems to be synonymous with when we'd normally expect |
| * ERROR_HANDLE_EOF. So treat it as such. Microsoft's |
| * recommendation for ERROR_NO_SYSTEM_RESOURCES is to |
| * retry the read, so this happens to work out anyway. On newer |
| * virtio-win driver, this seems to be replaced with EOA, so |
| * handle that in the same fashion. |
| */ |
| new_events |= G_IO_HUP; |
| } else if (error != ERROR_IO_INCOMPLETE) { |
| g_critical("error retrieving overlapped result: %d", (int)error); |
| new_events |= G_IO_ERR; |
| } |
| } |
| |
| if (new_events) { |
| rs->ov_pending = 0; |
| } |
| c->pending_events |= new_events; |
| |
| return !!c->pending_events; |
| } |
| |
| /* |
| * Called by glib after either prepare or check routines signal readiness |
| */ |
| static gboolean ga_channel_dispatch(GSource *source, GSourceFunc unused, |
| gpointer user_data) |
| { |
| GAWatch *watch = (GAWatch *)source; |
| GAChannel *c = (GAChannel *)watch->channel; |
| GAChannelReadState *rs = &c->rstate; |
| gboolean success; |
| |
| g_debug("dispatch"); |
| success = c->cb(watch->pollfd.revents, c->user_data); |
| |
| if (c->pending_events & G_IO_ERR) { |
| g_critical("channel error, removing source"); |
| return false; |
| } |
| |
| /* TODO: replace rs->pending with watch->revents */ |
| c->pending_events &= ~G_IO_HUP; |
| if (!rs->pending) { |
| c->pending_events &= ~G_IO_IN; |
| } else { |
| c->pending_events = 0; |
| } |
| return success; |
| } |
| |
| static void ga_channel_finalize(GSource *source) |
| { |
| g_debug("finalize"); |
| } |
| |
| GSourceFuncs ga_channel_watch_funcs = { |
| ga_channel_prepare, |
| ga_channel_check, |
| ga_channel_dispatch, |
| ga_channel_finalize |
| }; |
| |
| static GSource *ga_channel_create_watch(GAChannel *c) |
| { |
| GSource *source = g_source_new(&ga_channel_watch_funcs, sizeof(GAWatch)); |
| GAWatch *watch = (GAWatch *)source; |
| |
| watch->channel = c; |
| watch->pollfd.fd = (gintptr) c->rstate.ov.hEvent; |
| g_source_add_poll(source, &watch->pollfd); |
| |
| return source; |
| } |
| |
| GIOStatus ga_channel_read(GAChannel *c, char *buf, size_t size, gsize *count) |
| { |
| GAChannelReadState *rs = &c->rstate; |
| GIOStatus status; |
| size_t to_read = 0; |
| |
| if (c->pending_events & G_IO_ERR) { |
| return G_IO_STATUS_ERROR; |
| } |
| |
| *count = to_read = MIN(size, rs->pending); |
| if (to_read) { |
| memcpy(buf, rs->buf + rs->cur, to_read); |
| rs->cur += to_read; |
| rs->pending -= to_read; |
| status = G_IO_STATUS_NORMAL; |
| } else { |
| status = G_IO_STATUS_AGAIN; |
| } |
| |
| return status; |
| } |
| |
| static GIOStatus ga_channel_write(GAChannel *c, const char *buf, size_t size, |
| size_t *count) |
| { |
| GIOStatus status; |
| OVERLAPPED ov = {0}; |
| BOOL ret; |
| DWORD written; |
| |
| ov.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); |
| ret = WriteFile(c->handle, buf, size, &written, &ov); |
| if (!ret) { |
| if (GetLastError() == ERROR_IO_PENDING) { |
| /* write is pending */ |
| ret = GetOverlappedResult(c->handle, &ov, &written, TRUE); |
| if (!ret) { |
| if (!GetLastError()) { |
| status = G_IO_STATUS_AGAIN; |
| } else { |
| status = G_IO_STATUS_ERROR; |
| } |
| } else { |
| /* write is complete */ |
| status = G_IO_STATUS_NORMAL; |
| *count = written; |
| } |
| } else { |
| status = G_IO_STATUS_ERROR; |
| } |
| } else { |
| /* write returned immediately */ |
| status = G_IO_STATUS_NORMAL; |
| *count = written; |
| } |
| |
| if (ov.hEvent) { |
| CloseHandle(ov.hEvent); |
| ov.hEvent = NULL; |
| } |
| return status; |
| } |
| |
| GIOStatus ga_channel_write_all(GAChannel *c, const char *buf, size_t size) |
| { |
| GIOStatus status = G_IO_STATUS_NORMAL; |
| size_t count = 0; |
| |
| while (size) { |
| status = ga_channel_write(c, buf, size, &count); |
| if (status == G_IO_STATUS_NORMAL) { |
| size -= count; |
| buf += count; |
| } else if (status != G_IO_STATUS_AGAIN) { |
| break; |
| } |
| } |
| |
| return status; |
| } |
| |
| static gboolean ga_channel_open(GAChannel *c, GAChannelMethod method, |
| const gchar *path) |
| { |
| 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; |
| } |
| |
| 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; |
| } |
| |
| 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); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| GAChannel *ga_channel_new(GAChannelMethod method, const gchar *path, |
| int listen_fd, GAChannelCallback cb, gpointer opaque) |
| { |
| 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); |
| return NULL; |
| } |
| |
| c->cb = cb; |
| c->user_data = opaque; |
| |
| 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->source = ga_channel_create_watch(c); |
| g_source_attach(c->source, NULL); |
| return c; |
| } |
| |
| void ga_channel_free(GAChannel *c) |
| { |
| if (c->source) { |
| g_source_destroy(c->source); |
| } |
| if (c->rstate.ov.hEvent) { |
| CloseHandle(c->rstate.ov.hEvent); |
| } |
| g_free(c->rstate.buf); |
| g_free(c); |
| } |