root / qga / channel-win32.c @ 1ab516ed
History | View | Annotate | Download (9.5 kB)
1 | 7868e26e | Michael Roth | #include <stdlib.h> |
---|---|---|---|
2 | 7868e26e | Michael Roth | #include <stdio.h> |
3 | 7868e26e | Michael Roth | #include <stdbool.h> |
4 | 7868e26e | Michael Roth | #include <glib.h> |
5 | 7868e26e | Michael Roth | #include <windows.h> |
6 | 7868e26e | Michael Roth | #include <errno.h> |
7 | 7868e26e | Michael Roth | #include <io.h> |
8 | 7868e26e | Michael Roth | #include "qga/guest-agent-core.h" |
9 | 7868e26e | Michael Roth | #include "qga/channel.h" |
10 | 7868e26e | Michael Roth | |
11 | 7868e26e | Michael Roth | typedef struct GAChannelReadState { |
12 | 7868e26e | Michael Roth | guint thread_id; |
13 | 7868e26e | Michael Roth | uint8_t *buf; |
14 | 7868e26e | Michael Roth | size_t buf_size; |
15 | 7868e26e | Michael Roth | size_t cur; /* current buffer start */
|
16 | 7868e26e | Michael Roth | size_t pending; /* pending buffered bytes to read */
|
17 | 7868e26e | Michael Roth | OVERLAPPED ov; |
18 | 7868e26e | Michael Roth | bool ov_pending; /* whether on async read is outstanding */ |
19 | 7868e26e | Michael Roth | } GAChannelReadState; |
20 | 7868e26e | Michael Roth | |
21 | 7868e26e | Michael Roth | struct GAChannel {
|
22 | 7868e26e | Michael Roth | HANDLE handle; |
23 | 7868e26e | Michael Roth | GAChannelCallback cb; |
24 | 7868e26e | Michael Roth | gpointer user_data; |
25 | 7868e26e | Michael Roth | GAChannelReadState rstate; |
26 | 7868e26e | Michael Roth | GIOCondition pending_events; /* TODO: use GAWatch.pollfd.revents */
|
27 | 7868e26e | Michael Roth | GSource *source; |
28 | 7868e26e | Michael Roth | }; |
29 | 7868e26e | Michael Roth | |
30 | 7868e26e | Michael Roth | typedef struct GAWatch { |
31 | 7868e26e | Michael Roth | GSource source; |
32 | 7868e26e | Michael Roth | GPollFD pollfd; |
33 | 7868e26e | Michael Roth | GAChannel *channel; |
34 | 7868e26e | Michael Roth | GIOCondition events_mask; |
35 | 7868e26e | Michael Roth | } GAWatch; |
36 | 7868e26e | Michael Roth | |
37 | 7868e26e | Michael Roth | /*
|
38 | 7868e26e | Michael Roth | * Called by glib prior to polling to set up poll events if polling is needed.
|
39 | 7868e26e | Michael Roth | *
|
40 | 7868e26e | Michael Roth | */
|
41 | 7868e26e | Michael Roth | static gboolean ga_channel_prepare(GSource *source, gint *timeout_ms)
|
42 | 7868e26e | Michael Roth | { |
43 | 7868e26e | Michael Roth | GAWatch *watch = (GAWatch *)source; |
44 | 7868e26e | Michael Roth | GAChannel *c = (GAChannel *)watch->channel; |
45 | 7868e26e | Michael Roth | GAChannelReadState *rs = &c->rstate; |
46 | 7868e26e | Michael Roth | DWORD count_read, count_to_read = 0;
|
47 | 7868e26e | Michael Roth | bool success;
|
48 | 7868e26e | Michael Roth | GIOCondition new_events = 0;
|
49 | 7868e26e | Michael Roth | |
50 | 7868e26e | Michael Roth | g_debug("prepare");
|
51 | 7868e26e | Michael Roth | /* go ahead and submit another read if there's room in the buffer
|
52 | 7868e26e | Michael Roth | * and no previous reads are outstanding
|
53 | 7868e26e | Michael Roth | */
|
54 | 7868e26e | Michael Roth | if (!rs->ov_pending) {
|
55 | 7868e26e | Michael Roth | if (rs->cur + rs->pending >= rs->buf_size) {
|
56 | 7868e26e | Michael Roth | if (rs->cur) {
|
57 | 7868e26e | Michael Roth | memmove(rs->buf, rs->buf + rs->cur, rs->pending); |
58 | 7868e26e | Michael Roth | rs->cur = 0;
|
59 | 7868e26e | Michael Roth | } |
60 | 7868e26e | Michael Roth | } |
61 | 7868e26e | Michael Roth | count_to_read = rs->buf_size - rs->cur - rs->pending; |
62 | 7868e26e | Michael Roth | } |
63 | 7868e26e | Michael Roth | |
64 | 7868e26e | Michael Roth | if (rs->ov_pending || count_to_read <= 0) { |
65 | 7868e26e | Michael Roth | goto out;
|
66 | 7868e26e | Michael Roth | } |
67 | 7868e26e | Michael Roth | |
68 | 7868e26e | Michael Roth | /* submit the read */
|
69 | 7868e26e | Michael Roth | success = ReadFile(c->handle, rs->buf + rs->cur + rs->pending, |
70 | 7868e26e | Michael Roth | count_to_read, &count_read, &rs->ov); |
71 | 7868e26e | Michael Roth | if (success) {
|
72 | 7868e26e | Michael Roth | rs->pending += count_read; |
73 | 7868e26e | Michael Roth | rs->ov_pending = false;
|
74 | 7868e26e | Michael Roth | } else {
|
75 | 7868e26e | Michael Roth | if (GetLastError() == ERROR_IO_PENDING) {
|
76 | 7868e26e | Michael Roth | rs->ov_pending = true;
|
77 | 7868e26e | Michael Roth | } else {
|
78 | 7868e26e | Michael Roth | new_events |= G_IO_ERR; |
79 | 7868e26e | Michael Roth | } |
80 | 7868e26e | Michael Roth | } |
81 | 7868e26e | Michael Roth | |
82 | 7868e26e | Michael Roth | out:
|
83 | 7868e26e | Michael Roth | /* dont block forever, iterate the main loop every once and a while */
|
84 | 7868e26e | Michael Roth | *timeout_ms = 500;
|
85 | 7868e26e | Michael Roth | /* if there's data in the read buffer, or another event is pending,
|
86 | 7868e26e | Michael Roth | * skip polling and issue user cb.
|
87 | 7868e26e | Michael Roth | */
|
88 | 7868e26e | Michael Roth | if (rs->pending) {
|
89 | 7868e26e | Michael Roth | new_events |= G_IO_IN; |
90 | 7868e26e | Michael Roth | } |
91 | 7868e26e | Michael Roth | c->pending_events |= new_events; |
92 | 7868e26e | Michael Roth | return !!c->pending_events;
|
93 | 7868e26e | Michael Roth | } |
94 | 7868e26e | Michael Roth | |
95 | 7868e26e | Michael Roth | /*
|
96 | 7868e26e | Michael Roth | * Called by glib after an outstanding read request is completed.
|
97 | 7868e26e | Michael Roth | */
|
98 | 7868e26e | Michael Roth | static gboolean ga_channel_check(GSource *source)
|
99 | 7868e26e | Michael Roth | { |
100 | 7868e26e | Michael Roth | GAWatch *watch = (GAWatch *)source; |
101 | 7868e26e | Michael Roth | GAChannel *c = (GAChannel *)watch->channel; |
102 | 7868e26e | Michael Roth | GAChannelReadState *rs = &c->rstate; |
103 | 7868e26e | Michael Roth | DWORD count_read, error; |
104 | 7868e26e | Michael Roth | BOOL success; |
105 | 7868e26e | Michael Roth | |
106 | 7868e26e | Michael Roth | GIOCondition new_events = 0;
|
107 | 7868e26e | Michael Roth | |
108 | 7868e26e | Michael Roth | g_debug("check");
|
109 | 7868e26e | Michael Roth | |
110 | 7868e26e | Michael Roth | /* failing this implies we issued a read that completed immediately,
|
111 | 7868e26e | Michael Roth | * yet no data was placed into the buffer (and thus we did not skip
|
112 | 7868e26e | Michael Roth | * polling). but since EOF is not obtainable until we retrieve an
|
113 | 7868e26e | Michael Roth | * overlapped result, it must be the case that there was data placed
|
114 | 7868e26e | Michael Roth | * into the buffer, or an error was generated by Readfile(). in either
|
115 | 7868e26e | Michael Roth | * case, we should've skipped the polling for this round.
|
116 | 7868e26e | Michael Roth | */
|
117 | 7868e26e | Michael Roth | g_assert(rs->ov_pending); |
118 | 7868e26e | Michael Roth | |
119 | 7868e26e | Michael Roth | success = GetOverlappedResult(c->handle, &rs->ov, &count_read, FALSE); |
120 | 7868e26e | Michael Roth | if (success) {
|
121 | 7868e26e | Michael Roth | g_debug("thread: overlapped result, count_read: %d", (int)count_read); |
122 | 7868e26e | Michael Roth | rs->pending += count_read; |
123 | 7868e26e | Michael Roth | new_events |= G_IO_IN; |
124 | 7868e26e | Michael Roth | } else {
|
125 | 7868e26e | Michael Roth | error = GetLastError(); |
126 | 7868e26e | Michael Roth | if (error == 0 || error == ERROR_HANDLE_EOF || |
127 | 7868e26e | Michael Roth | error == ERROR_NO_SYSTEM_RESOURCES || |
128 | 7868e26e | Michael Roth | error == ERROR_OPERATION_ABORTED) { |
129 | 7868e26e | Michael Roth | /* note: On WinXP SP3 with rhel6ga virtio-win-1.1.16 vioser drivers,
|
130 | 7868e26e | Michael Roth | * ENSR seems to be synonymous with when we'd normally expect
|
131 | 7868e26e | Michael Roth | * ERROR_HANDLE_EOF. So treat it as such. Microsoft's
|
132 | 7868e26e | Michael Roth | * recommendation for ERROR_NO_SYSTEM_RESOURCES is to
|
133 | 7868e26e | Michael Roth | * retry the read, so this happens to work out anyway. On newer
|
134 | 7868e26e | Michael Roth | * virtio-win driver, this seems to be replaced with EOA, so
|
135 | 7868e26e | Michael Roth | * handle that in the same fashion.
|
136 | 7868e26e | Michael Roth | */
|
137 | 7868e26e | Michael Roth | new_events |= G_IO_HUP; |
138 | 7868e26e | Michael Roth | } else if (error != ERROR_IO_INCOMPLETE) { |
139 | 7868e26e | Michael Roth | g_critical("error retrieving overlapped result: %d", (int)error); |
140 | 7868e26e | Michael Roth | new_events |= G_IO_ERR; |
141 | 7868e26e | Michael Roth | } |
142 | 7868e26e | Michael Roth | } |
143 | 7868e26e | Michael Roth | |
144 | 7868e26e | Michael Roth | if (new_events) {
|
145 | 7868e26e | Michael Roth | rs->ov_pending = 0;
|
146 | 7868e26e | Michael Roth | } |
147 | 7868e26e | Michael Roth | c->pending_events |= new_events; |
148 | 7868e26e | Michael Roth | |
149 | 7868e26e | Michael Roth | return !!c->pending_events;
|
150 | 7868e26e | Michael Roth | } |
151 | 7868e26e | Michael Roth | |
152 | 7868e26e | Michael Roth | /*
|
153 | 7868e26e | Michael Roth | * Called by glib after either prepare or check routines signal readiness
|
154 | 7868e26e | Michael Roth | */
|
155 | 7868e26e | Michael Roth | static gboolean ga_channel_dispatch(GSource *source, GSourceFunc unused,
|
156 | 7868e26e | Michael Roth | gpointer user_data) |
157 | 7868e26e | Michael Roth | { |
158 | 7868e26e | Michael Roth | GAWatch *watch = (GAWatch *)source; |
159 | 7868e26e | Michael Roth | GAChannel *c = (GAChannel *)watch->channel; |
160 | 7868e26e | Michael Roth | GAChannelReadState *rs = &c->rstate; |
161 | 7868e26e | Michael Roth | gboolean success; |
162 | 7868e26e | Michael Roth | |
163 | 7868e26e | Michael Roth | g_debug("dispatch");
|
164 | 7868e26e | Michael Roth | success = c->cb(watch->pollfd.revents, c->user_data); |
165 | 7868e26e | Michael Roth | |
166 | 7868e26e | Michael Roth | if (c->pending_events & G_IO_ERR) {
|
167 | 7868e26e | Michael Roth | g_critical("channel error, removing source");
|
168 | 7868e26e | Michael Roth | return false; |
169 | 7868e26e | Michael Roth | } |
170 | 7868e26e | Michael Roth | |
171 | 7868e26e | Michael Roth | /* TODO: replace rs->pending with watch->revents */
|
172 | 7868e26e | Michael Roth | c->pending_events &= ~G_IO_HUP; |
173 | 7868e26e | Michael Roth | if (!rs->pending) {
|
174 | 7868e26e | Michael Roth | c->pending_events &= ~G_IO_IN; |
175 | 7868e26e | Michael Roth | } else {
|
176 | 7868e26e | Michael Roth | c->pending_events = 0;
|
177 | 7868e26e | Michael Roth | } |
178 | 7868e26e | Michael Roth | return success;
|
179 | 7868e26e | Michael Roth | } |
180 | 7868e26e | Michael Roth | |
181 | 7868e26e | Michael Roth | static void ga_channel_finalize(GSource *source) |
182 | 7868e26e | Michael Roth | { |
183 | 7868e26e | Michael Roth | g_debug("finalize");
|
184 | 7868e26e | Michael Roth | } |
185 | 7868e26e | Michael Roth | |
186 | 7868e26e | Michael Roth | GSourceFuncs ga_channel_watch_funcs = { |
187 | 7868e26e | Michael Roth | ga_channel_prepare, |
188 | 7868e26e | Michael Roth | ga_channel_check, |
189 | 7868e26e | Michael Roth | ga_channel_dispatch, |
190 | 7868e26e | Michael Roth | ga_channel_finalize |
191 | 7868e26e | Michael Roth | }; |
192 | 7868e26e | Michael Roth | |
193 | 7868e26e | Michael Roth | static GSource *ga_channel_create_watch(GAChannel *c)
|
194 | 7868e26e | Michael Roth | { |
195 | 7868e26e | Michael Roth | GSource *source = g_source_new(&ga_channel_watch_funcs, sizeof(GAWatch));
|
196 | 7868e26e | Michael Roth | GAWatch *watch = (GAWatch *)source; |
197 | 7868e26e | Michael Roth | |
198 | 7868e26e | Michael Roth | watch->channel = c; |
199 | 7868e26e | Michael Roth | watch->pollfd.fd = (gintptr) c->rstate.ov.hEvent; |
200 | 7868e26e | Michael Roth | g_source_add_poll(source, &watch->pollfd); |
201 | 7868e26e | Michael Roth | |
202 | 7868e26e | Michael Roth | return source;
|
203 | 7868e26e | Michael Roth | } |
204 | 7868e26e | Michael Roth | |
205 | 7868e26e | Michael Roth | GIOStatus ga_channel_read(GAChannel *c, char *buf, size_t size, gsize *count)
|
206 | 7868e26e | Michael Roth | { |
207 | 7868e26e | Michael Roth | GAChannelReadState *rs = &c->rstate; |
208 | 7868e26e | Michael Roth | GIOStatus status; |
209 | 7868e26e | Michael Roth | size_t to_read = 0;
|
210 | 7868e26e | Michael Roth | |
211 | 7868e26e | Michael Roth | if (c->pending_events & G_IO_ERR) {
|
212 | 7868e26e | Michael Roth | return G_IO_STATUS_ERROR;
|
213 | 7868e26e | Michael Roth | } |
214 | 7868e26e | Michael Roth | |
215 | 7868e26e | Michael Roth | *count = to_read = MIN(size, rs->pending); |
216 | 7868e26e | Michael Roth | if (to_read) {
|
217 | 7868e26e | Michael Roth | memcpy(buf, rs->buf + rs->cur, to_read); |
218 | 7868e26e | Michael Roth | rs->cur += to_read; |
219 | 7868e26e | Michael Roth | rs->pending -= to_read; |
220 | 7868e26e | Michael Roth | status = G_IO_STATUS_NORMAL; |
221 | 7868e26e | Michael Roth | } else {
|
222 | 7868e26e | Michael Roth | status = G_IO_STATUS_AGAIN; |
223 | 7868e26e | Michael Roth | } |
224 | 7868e26e | Michael Roth | |
225 | 7868e26e | Michael Roth | return status;
|
226 | 7868e26e | Michael Roth | } |
227 | 7868e26e | Michael Roth | |
228 | 7868e26e | Michael Roth | static GIOStatus ga_channel_write(GAChannel *c, const char *buf, size_t size, |
229 | 7868e26e | Michael Roth | size_t *count) |
230 | 7868e26e | Michael Roth | { |
231 | 7868e26e | Michael Roth | GIOStatus status; |
232 | 7868e26e | Michael Roth | OVERLAPPED ov = {0};
|
233 | 7868e26e | Michael Roth | BOOL ret; |
234 | 7868e26e | Michael Roth | DWORD written; |
235 | 7868e26e | Michael Roth | |
236 | 7868e26e | Michael Roth | ov.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); |
237 | 7868e26e | Michael Roth | ret = WriteFile(c->handle, buf, size, &written, &ov); |
238 | 7868e26e | Michael Roth | if (!ret) {
|
239 | 7868e26e | Michael Roth | if (GetLastError() == ERROR_IO_PENDING) {
|
240 | 7868e26e | Michael Roth | /* write is pending */
|
241 | 7868e26e | Michael Roth | ret = GetOverlappedResult(c->handle, &ov, &written, TRUE); |
242 | 7868e26e | Michael Roth | if (!ret) {
|
243 | 7868e26e | Michael Roth | if (!GetLastError()) {
|
244 | 7868e26e | Michael Roth | status = G_IO_STATUS_AGAIN; |
245 | 7868e26e | Michael Roth | } else {
|
246 | 7868e26e | Michael Roth | status = G_IO_STATUS_ERROR; |
247 | 7868e26e | Michael Roth | } |
248 | 7868e26e | Michael Roth | } else {
|
249 | 7868e26e | Michael Roth | /* write is complete */
|
250 | 7868e26e | Michael Roth | status = G_IO_STATUS_NORMAL; |
251 | 7868e26e | Michael Roth | *count = written; |
252 | 7868e26e | Michael Roth | } |
253 | 7868e26e | Michael Roth | } else {
|
254 | 7868e26e | Michael Roth | status = G_IO_STATUS_ERROR; |
255 | 7868e26e | Michael Roth | } |
256 | 7868e26e | Michael Roth | } else {
|
257 | 7868e26e | Michael Roth | /* write returned immediately */
|
258 | 7868e26e | Michael Roth | status = G_IO_STATUS_NORMAL; |
259 | 7868e26e | Michael Roth | *count = written; |
260 | 7868e26e | Michael Roth | } |
261 | 7868e26e | Michael Roth | |
262 | b71706d1 | Jeff Cody | if (ov.hEvent) {
|
263 | b71706d1 | Jeff Cody | CloseHandle(ov.hEvent); |
264 | b71706d1 | Jeff Cody | ov.hEvent = NULL;
|
265 | b71706d1 | Jeff Cody | } |
266 | 7868e26e | Michael Roth | return status;
|
267 | 7868e26e | Michael Roth | } |
268 | 7868e26e | Michael Roth | |
269 | 7868e26e | Michael Roth | GIOStatus ga_channel_write_all(GAChannel *c, const char *buf, size_t size) |
270 | 7868e26e | Michael Roth | { |
271 | 7868e26e | Michael Roth | GIOStatus status = G_IO_STATUS_NORMAL;; |
272 | 7868e26e | Michael Roth | size_t count; |
273 | 7868e26e | Michael Roth | |
274 | 7868e26e | Michael Roth | while (size) {
|
275 | 7868e26e | Michael Roth | status = ga_channel_write(c, buf, size, &count); |
276 | 7868e26e | Michael Roth | if (status == G_IO_STATUS_NORMAL) {
|
277 | 7868e26e | Michael Roth | size -= count; |
278 | 7868e26e | Michael Roth | buf += count; |
279 | 7868e26e | Michael Roth | } else if (status != G_IO_STATUS_AGAIN) { |
280 | 7868e26e | Michael Roth | break;
|
281 | 7868e26e | Michael Roth | } |
282 | 7868e26e | Michael Roth | } |
283 | 7868e26e | Michael Roth | |
284 | 7868e26e | Michael Roth | return status;
|
285 | 7868e26e | Michael Roth | } |
286 | 7868e26e | Michael Roth | |
287 | 7868e26e | Michael Roth | static gboolean ga_channel_open(GAChannel *c, GAChannelMethod method,
|
288 | 7868e26e | Michael Roth | const gchar *path)
|
289 | 7868e26e | Michael Roth | { |
290 | 7868e26e | Michael Roth | if (!method == GA_CHANNEL_VIRTIO_SERIAL) {
|
291 | 7868e26e | Michael Roth | g_critical("unsupported communication method");
|
292 | 7868e26e | Michael Roth | return false; |
293 | 7868e26e | Michael Roth | } |
294 | 7868e26e | Michael Roth | |
295 | 7868e26e | Michael Roth | c->handle = CreateFile(path, GENERIC_READ | GENERIC_WRITE, 0, NULL, |
296 | 7868e26e | Michael Roth | OPEN_EXISTING, |
297 | 7868e26e | Michael Roth | FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, NULL);
|
298 | 7868e26e | Michael Roth | if (c->handle == INVALID_HANDLE_VALUE) {
|
299 | 7868e26e | Michael Roth | g_critical("error opening path");
|
300 | 7868e26e | Michael Roth | return false; |
301 | 7868e26e | Michael Roth | } |
302 | 7868e26e | Michael Roth | |
303 | 7868e26e | Michael Roth | return true; |
304 | 7868e26e | Michael Roth | } |
305 | 7868e26e | Michael Roth | |
306 | 7868e26e | Michael Roth | GAChannel *ga_channel_new(GAChannelMethod method, const gchar *path,
|
307 | 7868e26e | Michael Roth | GAChannelCallback cb, gpointer opaque) |
308 | 7868e26e | Michael Roth | { |
309 | 7868e26e | Michael Roth | GAChannel *c = g_malloc0(sizeof(GAChannel));
|
310 | 7868e26e | Michael Roth | SECURITY_ATTRIBUTES sec_attrs; |
311 | 7868e26e | Michael Roth | |
312 | 7868e26e | Michael Roth | if (!ga_channel_open(c, method, path)) {
|
313 | 7868e26e | Michael Roth | g_critical("error opening channel");
|
314 | 7868e26e | Michael Roth | g_free(c); |
315 | 7868e26e | Michael Roth | return NULL; |
316 | 7868e26e | Michael Roth | } |
317 | 7868e26e | Michael Roth | |
318 | 7868e26e | Michael Roth | c->cb = cb; |
319 | 7868e26e | Michael Roth | c->user_data = opaque; |
320 | 7868e26e | Michael Roth | |
321 | 7868e26e | Michael Roth | sec_attrs.nLength = sizeof(SECURITY_ATTRIBUTES);
|
322 | 7868e26e | Michael Roth | sec_attrs.lpSecurityDescriptor = NULL;
|
323 | 7868e26e | Michael Roth | sec_attrs.bInheritHandle = false;
|
324 | 7868e26e | Michael Roth | |
325 | 7868e26e | Michael Roth | c->rstate.buf_size = QGA_READ_COUNT_DEFAULT; |
326 | 7868e26e | Michael Roth | c->rstate.buf = g_malloc(QGA_READ_COUNT_DEFAULT); |
327 | 7868e26e | Michael Roth | c->rstate.ov.hEvent = CreateEvent(&sec_attrs, FALSE, FALSE, NULL);
|
328 | 7868e26e | Michael Roth | |
329 | 7868e26e | Michael Roth | c->source = ga_channel_create_watch(c); |
330 | 7868e26e | Michael Roth | g_source_attach(c->source, NULL);
|
331 | 7868e26e | Michael Roth | return c;
|
332 | 7868e26e | Michael Roth | } |
333 | 7868e26e | Michael Roth | |
334 | 7868e26e | Michael Roth | void ga_channel_free(GAChannel *c)
|
335 | 7868e26e | Michael Roth | { |
336 | 7868e26e | Michael Roth | if (c->source) {
|
337 | 7868e26e | Michael Roth | g_source_destroy(c->source); |
338 | 7868e26e | Michael Roth | } |
339 | 7868e26e | Michael Roth | if (c->rstate.ov.hEvent) {
|
340 | 7868e26e | Michael Roth | CloseHandle(c->rstate.ov.hEvent); |
341 | 7868e26e | Michael Roth | } |
342 | 7868e26e | Michael Roth | g_free(c->rstate.buf); |
343 | 7868e26e | Michael Roth | g_free(c); |
344 | 7868e26e | Michael Roth | } |