winipc 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1246 @@
1
+ /*
2
+ * winipc — Windows local IPC for Ruby: named pipes, shared memory (file
3
+ * mappings), and named synchronization objects (mutex / event / semaphore).
4
+ *
5
+ * PURE C (no C++), so rb_raise/longjmp is the normal, safe error mechanism —
6
+ * the same discipline as winloop, and the opposite of the lithos /EHsc hazard.
7
+ * Every kernel object lives in a TypedData wrapper whose free hook closes it,
8
+ * so a forgotten #close never leaks a HANDLE. Blocking waits release the GVL
9
+ * (rb_thread_call_without_gvl) with an unblock function so other Ruby threads
10
+ * run and Thread#kill / Ctrl-C / Timeout can break them.
11
+ *
12
+ * Links kernel32 (pipes / mappings / sync) + advapi32 (security descriptors).
13
+ */
14
+
15
+ #include <ruby.h>
16
+ #include <ruby/thread.h>
17
+ #include <ruby/encoding.h>
18
+ #include <limits.h>
19
+
20
+ #include <windows.h>
21
+ #include <sddl.h> /* ConvertStringSecurityDescriptorToSecurityDescriptorW, ConvertSidToStringSidW */
22
+
23
+ /* ------------------------------------------------------------------ globals */
24
+
25
+ static VALUE mWinipc;
26
+ static VALUE cPipe, cListener, cConn, cSharedMemory, cMutex, cEvent, cSemaphore;
27
+ static VALUE eError, eOSError, eTimeout, eNotFound, eExists, eAccessDenied,
28
+ eBrokenPipe, ePipeBusy, eCanceled, eModeError, eRangeError,
29
+ eClosed, eNotOwner, eAbandoned, eWouldExceedMax;
30
+
31
+ /* ------------------------------------------------------------ small helpers */
32
+
33
+ /* UTF-8 Ruby String -> freshly xmalloc'd NUL-terminated UTF-16. Caller xfrees. */
34
+ static WCHAR *
35
+ to_wide(VALUE str)
36
+ {
37
+ int len, n;
38
+ WCHAR *w;
39
+ StringValue(str);
40
+ len = (int)RSTRING_LEN(str);
41
+ if (len == 0) {
42
+ w = (WCHAR *)xmalloc(sizeof(WCHAR));
43
+ w[0] = 0;
44
+ return w;
45
+ }
46
+ n = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(str), len, NULL, 0);
47
+ if (n <= 0) rb_raise(eError, "winipc: invalid UTF-8 in name");
48
+ w = (WCHAR *)xmalloc(sizeof(WCHAR) * (n + 1));
49
+ MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(str), len, w, n);
50
+ w[n] = 0;
51
+ return w;
52
+ }
53
+
54
+ /* Build + raise a winipc error carrying @code (the Win32 error). The code must
55
+ * be captured by the caller immediately after the failing syscall. */
56
+ static void
57
+ raise_code(VALUE klass, const char *api, DWORD code)
58
+ {
59
+ VALUE exc, msg;
60
+ WCHAR *buf = NULL;
61
+ char detail[512];
62
+ DWORD n;
63
+
64
+ detail[0] = 0;
65
+ n = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
66
+ FORMAT_MESSAGE_IGNORE_INSERTS, NULL, code,
67
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&buf, 0, NULL);
68
+ if (n && buf) {
69
+ /* trim trailing CR/LF/period, then copy to a stack buffer */
70
+ while (n && (buf[n-1] == L'\r' || buf[n-1] == L'\n' || buf[n-1] == L'.')) buf[--n] = 0;
71
+ WideCharToMultiByte(CP_UTF8, 0, buf, -1, detail, (int)sizeof(detail), NULL, NULL);
72
+ detail[sizeof(detail) - 1] = 0;
73
+ }
74
+ /* Release the OS buffer BEFORE any Ruby allocation, so an OOM longjmp from
75
+ * rb_sprintf / rb_exc_new_str cannot leak it. */
76
+ if (buf) LocalFree(buf);
77
+
78
+ if (detail[0])
79
+ msg = rb_sprintf("%s: %s (error %lu)", api, detail, (unsigned long)code);
80
+ else
81
+ msg = rb_sprintf("%s failed (error %lu)", api, (unsigned long)code);
82
+ exc = rb_exc_new_str(klass, msg);
83
+ rb_iv_set(exc, "@code", ULONG2NUM(code));
84
+ rb_exc_raise(exc);
85
+ }
86
+
87
+ /* Pick the right subclass for a Win32 error and raise it (used at call sites
88
+ * that don't have a more specific mapping). */
89
+ static void
90
+ raise_gle(const char *api, DWORD code)
91
+ {
92
+ VALUE klass = eOSError;
93
+ switch (code) {
94
+ case ERROR_FILE_NOT_FOUND: klass = eNotFound; break; /* 2 */
95
+ case ERROR_ACCESS_DENIED: klass = eAccessDenied; break; /* 5 */
96
+ case ERROR_BROKEN_PIPE: /* 109 */
97
+ case ERROR_NO_DATA: /* 232 */
98
+ case ERROR_PIPE_NOT_CONNECTED: klass = eBrokenPipe; break;/* 233 */
99
+ case ERROR_OPERATION_ABORTED: klass = eCanceled; break; /* 995 */
100
+ case ERROR_SEM_TIMEOUT: klass = eTimeout; break; /* 121 */
101
+ case ERROR_PIPE_BUSY: klass = ePipeBusy; break; /* 231 */
102
+ default: break;
103
+ }
104
+ raise_code(klass, api, code);
105
+ }
106
+
107
+ /* Length of a Ruby String as a DWORD, guarding the 4 GiB ceiling. */
108
+ static DWORD
109
+ dword_len(VALUE str)
110
+ {
111
+ long n;
112
+ StringValue(str);
113
+ n = RSTRING_LEN(str);
114
+ if ((unsigned long long)n > 0xFFFFFFFFull)
115
+ rb_raise(rb_eArgError, "winipc: data too large (> 4 GiB)");
116
+ return (DWORD)n;
117
+ }
118
+
119
+ /* ----------------------------------------------------- security descriptors */
120
+
121
+ /* Build a SECURITY_ATTRIBUTES for the requested access policy. Returns 1 and
122
+ * fills *sa (+ *psd to LocalFree later) when an explicit SD is used, or 0 when
123
+ * the caller should pass NULL (default ACL). access:
124
+ * :everyone / nil -> 0 (NULL sa = OS default ACL)
125
+ * :owner -> explicit SD: current user + SYSTEM + Administrators
126
+ * a String -> treated as an SDDL descriptor
127
+ */
128
+ static int
129
+ build_sa(VALUE access, SECURITY_ATTRIBUTES *sa, PSECURITY_DESCRIPTOR *psd)
130
+ {
131
+ WCHAR *sddl = NULL;
132
+ int owned = 0; /* whether sddl was xmalloc'd */
133
+ PSECURITY_DESCRIPTOR sd = NULL;
134
+ WCHAR owner_sddl[320]; /* function-scope so it outlives the :owner block */
135
+
136
+ *psd = NULL;
137
+ if (NIL_P(access) || access == ID2SYM(rb_intern("everyone")))
138
+ return 0;
139
+
140
+ if (access == ID2SYM(rb_intern("owner"))) {
141
+ HANDLE tok = NULL;
142
+ DWORD cb = 0;
143
+ TOKEN_USER *tu = NULL;
144
+ LPWSTR sidstr = NULL;
145
+
146
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &tok))
147
+ raise_gle("OpenProcessToken", GetLastError());
148
+ GetTokenInformation(tok, TokenUser, NULL, 0, &cb);
149
+ /* plain malloc (not xmalloc) so an OOM doesn't longjmp and leak `tok` */
150
+ tu = (TOKEN_USER *)malloc(cb ? cb : 1);
151
+ if (!tu) { CloseHandle(tok); rb_raise(rb_eNoMemError, "winipc: out of memory"); }
152
+ if (!GetTokenInformation(tok, TokenUser, tu, cb, &cb)) {
153
+ DWORD e = GetLastError(); CloseHandle(tok); free(tu);
154
+ raise_gle("GetTokenInformation", e);
155
+ }
156
+ if (!ConvertSidToStringSidW(tu->User.Sid, &sidstr)) {
157
+ DWORD e = GetLastError(); CloseHandle(tok); free(tu);
158
+ raise_gle("ConvertSidToStringSid", e);
159
+ }
160
+ CloseHandle(tok);
161
+ free(tu);
162
+ /* Format into the stack buffer and free sidstr immediately, so no OS
163
+ * allocation is held across a later raise. (A string SID is well under
164
+ * the buffer size.) */
165
+ _snwprintf(owner_sddl, 320, L"D:(A;;GA;;;%s)(A;;GA;;;SY)(A;;GA;;;BA)", sidstr);
166
+ owner_sddl[319] = 0;
167
+ LocalFree(sidstr);
168
+ sddl = owner_sddl;
169
+ owned = 0; /* stack buffer; nothing to xfree */
170
+ } else {
171
+ /* explicit SDDL string */
172
+ sddl = to_wide(access);
173
+ owned = 1;
174
+ }
175
+
176
+ if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl, SDDL_REVISION_1, &sd, NULL)) {
177
+ DWORD e = GetLastError();
178
+ if (owned) xfree(sddl);
179
+ raise_gle("ConvertStringSecurityDescriptor", e);
180
+ }
181
+ if (owned) xfree(sddl);
182
+
183
+ sa->nLength = sizeof(*sa);
184
+ sa->lpSecurityDescriptor = sd;
185
+ sa->bInheritHandle = FALSE;
186
+ *psd = sd;
187
+ return 1;
188
+ }
189
+
190
+ /* =====================================================================
191
+ * Named pipe connection — Winipc::Pipe::Conn
192
+ * ===================================================================== */
193
+
194
+ typedef struct {
195
+ HANDLE h;
196
+ HANDLE read_event; /* manual-reset event for an in-flight read */
197
+ HANDLE write_event; /* manual-reset event for an in-flight write */
198
+ int is_server;
199
+ int message_read; /* PIPE_READMODE_MESSAGE in effect */
200
+ int closed;
201
+ } conn_t;
202
+
203
+ static void
204
+ conn_free(void *p)
205
+ {
206
+ conn_t *c = (conn_t *)p;
207
+ /* Graceful close: just CloseHandle. The peer can drain any buffered data
208
+ * and then sees EOF. (DisconnectNamedPipe would discard unread data — e.g.
209
+ * a reply the server just wrote — so it is deliberately NOT used here.) */
210
+ if (c->h && c->h != INVALID_HANDLE_VALUE) {
211
+ CloseHandle(c->h);
212
+ }
213
+ if (c->read_event) CloseHandle(c->read_event);
214
+ if (c->write_event) CloseHandle(c->write_event);
215
+ xfree(c);
216
+ }
217
+
218
+ static const rb_data_type_t conn_type = {
219
+ "Winipc::Pipe::Conn", { 0, conn_free, 0, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
220
+ };
221
+
222
+ static VALUE
223
+ conn_alloc(VALUE klass)
224
+ {
225
+ conn_t *c;
226
+ VALUE obj = TypedData_Make_Struct(klass, conn_t, &conn_type, c);
227
+ c->h = INVALID_HANDLE_VALUE;
228
+ c->read_event = NULL;
229
+ c->write_event = NULL;
230
+ c->is_server = 0;
231
+ c->message_read = 0;
232
+ c->closed = 0;
233
+ return obj;
234
+ }
235
+
236
+ static conn_t *
237
+ conn_get(VALUE self)
238
+ {
239
+ conn_t *c;
240
+ TypedData_Get_Struct(self, conn_t, &conn_type, c);
241
+ return c;
242
+ }
243
+
244
+ static conn_t *
245
+ conn_live(VALUE self)
246
+ {
247
+ conn_t *c = conn_get(self);
248
+ if (c->closed || c->h == INVALID_HANDLE_VALUE)
249
+ rb_raise(eClosed, "winipc: connection is closed");
250
+ return c;
251
+ }
252
+
253
+ /* --- GVL-released wait on an overlapped op, cancelable via CancelIoEx ------ */
254
+
255
+ typedef struct {
256
+ HANDLE h;
257
+ OVERLAPPED *ov;
258
+ DWORD ms;
259
+ DWORD wait;
260
+ DWORD gle;
261
+ } ov_wait_t;
262
+
263
+ static void *
264
+ ov_wait_fn(void *p)
265
+ {
266
+ ov_wait_t *w = (ov_wait_t *)p;
267
+ w->wait = WaitForSingleObject(w->ov->hEvent, w->ms);
268
+ if (w->wait == WAIT_FAILED) w->gle = GetLastError(); /* capture before GVL */
269
+ return NULL;
270
+ }
271
+
272
+ static void
273
+ ov_wait_ubf(void *p)
274
+ {
275
+ ov_wait_t *w = (ov_wait_t *)p;
276
+ /* Cancel THIS specific overlapped op. Passing the OVERLAPPED* (not NULL)
277
+ * cancels it regardless of which thread issued it — required because the
278
+ * unblock runs on a different thread than the blocked wait. */
279
+ CancelIoEx(w->h, w->ov);
280
+ }
281
+
282
+ /* Wait for a pending overlapped op to complete (GVL released, cancelable).
283
+ * Returns the WaitForSingleObject result; *gle gets GetLastError on WAIT_FAILED. */
284
+ static DWORD
285
+ ov_wait(HANDLE h, OVERLAPPED *ov, DWORD ms, DWORD *gle)
286
+ {
287
+ ov_wait_t w;
288
+ w.h = h;
289
+ w.ov = ov;
290
+ w.ms = ms;
291
+ w.wait = WAIT_FAILED;
292
+ w.gle = 0;
293
+ rb_thread_call_without_gvl(ov_wait_fn, &w, ov_wait_ubf, &w);
294
+ if (gle) *gle = w.gle;
295
+ return w.wait;
296
+ }
297
+
298
+ /* Cancel a still-pending overlapped op and wait for it to settle, so its
299
+ * OVERLAPPED and buffer can be safely freed before we raise. */
300
+ static void
301
+ ov_drain(HANDLE h, OVERLAPPED *ov)
302
+ {
303
+ DWORD n;
304
+ CancelIoEx(h, ov);
305
+ GetOverlappedResult(h, ov, &n, TRUE);
306
+ }
307
+
308
+ /* =====================================================================
309
+ * Listener — Winipc::Pipe::Listener
310
+ * ===================================================================== */
311
+
312
+ typedef struct {
313
+ WCHAR *name;
314
+ DWORD open_mode; /* PIPE_ACCESS_* | FILE_FLAG_OVERLAPPED [| FIRST_PIPE_INSTANCE] */
315
+ DWORD pipe_mode; /* PIPE_TYPE_* | PIPE_READMODE_* | PIPE_WAIT [| REJECT_REMOTE] */
316
+ DWORD max_inst;
317
+ DWORD in_buf;
318
+ DWORD out_buf;
319
+ PSECURITY_DESCRIPTOR psd; /* may be NULL */
320
+ HANDLE pending; /* the next instance awaiting a client (live after listen) */
321
+ HANDLE event; /* manual-reset event for the pending instance's connect */
322
+ int first_done;
323
+ int closed;
324
+ } listener_t;
325
+
326
+ static void
327
+ listener_free(void *p)
328
+ {
329
+ listener_t *l = (listener_t *)p;
330
+ if (l->pending && l->pending != INVALID_HANDLE_VALUE) {
331
+ DisconnectNamedPipe(l->pending);
332
+ CloseHandle(l->pending);
333
+ }
334
+ if (l->event) CloseHandle(l->event);
335
+ if (l->name) xfree(l->name);
336
+ if (l->psd) LocalFree(l->psd);
337
+ xfree(l);
338
+ }
339
+
340
+ static const rb_data_type_t listener_type = {
341
+ "Winipc::Pipe::Listener", { 0, listener_free, 0, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
342
+ };
343
+
344
+ static VALUE
345
+ listener_alloc(VALUE klass)
346
+ {
347
+ listener_t *l;
348
+ VALUE obj = TypedData_Make_Struct(klass, listener_t, &listener_type, l);
349
+ l->name = NULL;
350
+ l->psd = NULL;
351
+ l->pending = INVALID_HANDLE_VALUE;
352
+ l->event = NULL;
353
+ l->first_done = 0;
354
+ l->closed = 0;
355
+ return obj;
356
+ }
357
+
358
+ static listener_t *
359
+ listener_get(VALUE self)
360
+ {
361
+ listener_t *l;
362
+ TypedData_Get_Struct(self, listener_t, &listener_type, l);
363
+ return l;
364
+ }
365
+
366
+ /* Create one pipe instance per the listener's config. FILE_FLAG_FIRST_PIPE_INSTANCE
367
+ * goes on the very first instance only, so a squatter can't pre-own the name. */
368
+ static HANDLE
369
+ listener_make_instance(listener_t *l)
370
+ {
371
+ DWORD open_mode = l->open_mode | (l->first_done ? 0 : FILE_FLAG_FIRST_PIPE_INSTANCE);
372
+ SECURITY_ATTRIBUTES sa;
373
+ LPSECURITY_ATTRIBUTES psa = NULL;
374
+ HANDLE h;
375
+ if (l->psd) { sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = l->psd; sa.bInheritHandle = FALSE; psa = &sa; }
376
+ h = CreateNamedPipeW(l->name, open_mode, l->pipe_mode, l->max_inst,
377
+ l->out_buf, l->in_buf, 0, psa);
378
+ if (h == INVALID_HANDLE_VALUE)
379
+ raise_gle("CreateNamedPipe", GetLastError());
380
+ l->first_done = 1;
381
+ return h;
382
+ }
383
+
384
+ /* Winipc::Pipe._listen(wname, open_mode, pipe_mode, max_inst, in_buf, out_buf, access) */
385
+ static VALUE
386
+ pipe_listen(VALUE klass, VALUE wname, VALUE open_mode, VALUE pipe_mode,
387
+ VALUE max_inst, VALUE in_buf, VALUE out_buf, VALUE access)
388
+ {
389
+ VALUE obj = listener_alloc(cListener);
390
+ listener_t *l = listener_get(obj);
391
+ SECURITY_ATTRIBUTES sa;
392
+ PSECURITY_DESCRIPTOR psd = NULL;
393
+
394
+ l->open_mode = NUM2ULONG(open_mode);
395
+ l->pipe_mode = NUM2ULONG(pipe_mode);
396
+ l->max_inst = NUM2ULONG(max_inst);
397
+ l->in_buf = NUM2ULONG(in_buf);
398
+ l->out_buf = NUM2ULONG(out_buf);
399
+ l->name = to_wide(wname); /* already \\.\pipe\... from Ruby */
400
+ if (build_sa(access, &sa, &psd))
401
+ l->psd = psd; /* stored; freed in listener_free */
402
+
403
+ l->event = CreateEventW(NULL, TRUE, FALSE, NULL);
404
+ if (!l->event) raise_gle("CreateEvent", GetLastError());
405
+ /* Create the first listening instance now so the name is live immediately. */
406
+ l->pending = listener_make_instance(l);
407
+ return obj;
408
+ }
409
+
410
+ /* Listener#_accept(timeout_ms) -> Conn, or nil on timeout (-1 ms == INFINITE). */
411
+ static VALUE
412
+ listener_accept(VALUE self, VALUE vms)
413
+ {
414
+ listener_t *l = listener_get(self);
415
+ long ms_in = NUM2LONG(vms);
416
+ DWORD ms = (ms_in < 0) ? INFINITE : (DWORD)ms_in;
417
+ OVERLAPPED ov;
418
+ HANDLE cur;
419
+ VALUE connobj;
420
+ conn_t *c;
421
+ DWORD gle, wgle = 0;
422
+ int connected = 0;
423
+
424
+ if (l->closed) rb_raise(eClosed, "winipc: listener is closed");
425
+ if (l->pending == INVALID_HANDLE_VALUE)
426
+ l->pending = listener_make_instance(l); /* recover if a prior accept failed mid-rotate */
427
+
428
+ memset(&ov, 0, sizeof(ov));
429
+ ov.hEvent = l->event;
430
+ ResetEvent(l->event);
431
+
432
+ if (ConnectNamedPipe(l->pending, &ov)) {
433
+ connected = 1; /* completed synchronously (rare) */
434
+ } else {
435
+ gle = GetLastError();
436
+ if (gle == ERROR_PIPE_CONNECTED) {
437
+ connected = 1; /* client connected in the create/connect gap — success */
438
+ } else if (gle == ERROR_IO_PENDING) {
439
+ DWORD w = ov_wait(l->pending, &ov, ms, &wgle);
440
+ DWORD n;
441
+ if (w == WAIT_TIMEOUT) {
442
+ CancelIoEx(l->pending, &ov);
443
+ /* If a client connected during the cancel race, keep it rather
444
+ * than orphaning it; otherwise report the timeout. */
445
+ if (GetOverlappedResult(l->pending, &ov, &n, TRUE))
446
+ connected = 1;
447
+ else
448
+ return Qnil;
449
+ } else if (w != WAIT_OBJECT_0) {
450
+ ov_drain(l->pending, &ov);
451
+ raise_gle("WaitForSingleObject", wgle);
452
+ } else if (GetOverlappedResult(l->pending, &ov, &n, FALSE)) {
453
+ connected = 1;
454
+ } else {
455
+ gle = GetLastError();
456
+ if (gle == ERROR_OPERATION_ABORTED) return Qnil; /* interrupted */
457
+ raise_gle("ConnectNamedPipe", gle);
458
+ }
459
+ } else {
460
+ raise_gle("ConnectNamedPipe", gle);
461
+ }
462
+ }
463
+
464
+ if (!connected) return Qnil;
465
+
466
+ /* Move the connected instance into a Conn (so its free hook owns it), then
467
+ * pre-create the next listening instance so the name stays live for the
468
+ * next client (no ERROR_FILE_NOT_FOUND gap between accepts). */
469
+ cur = l->pending;
470
+ connobj = conn_alloc(cConn);
471
+ c = conn_get(connobj);
472
+ c->h = cur;
473
+ c->is_server = 1;
474
+ c->message_read = (l->pipe_mode & PIPE_READMODE_MESSAGE) ? 1 : 0;
475
+ l->pending = INVALID_HANDLE_VALUE; /* ownership moved to the Conn */
476
+ c->read_event = CreateEventW(NULL, TRUE, FALSE, NULL);
477
+ c->write_event = CreateEventW(NULL, TRUE, FALSE, NULL);
478
+ if (!c->read_event || !c->write_event) raise_gle("CreateEvent", GetLastError());
479
+
480
+ l->pending = listener_make_instance(l);
481
+ return connobj;
482
+ }
483
+
484
+ static VALUE
485
+ listener_close(VALUE self)
486
+ {
487
+ listener_t *l = listener_get(self);
488
+ if (l->pending && l->pending != INVALID_HANDLE_VALUE) {
489
+ DisconnectNamedPipe(l->pending);
490
+ CloseHandle(l->pending);
491
+ l->pending = INVALID_HANDLE_VALUE;
492
+ }
493
+ if (l->event) { CloseHandle(l->event); l->event = NULL; }
494
+ l->closed = 1;
495
+ return Qnil;
496
+ }
497
+
498
+ static VALUE
499
+ listener_closed_p(VALUE self)
500
+ {
501
+ return listener_get(self)->closed ? Qtrue : Qfalse;
502
+ }
503
+
504
+ /* =====================================================================
505
+ * Client connect — Winipc::Pipe._connect
506
+ * ===================================================================== */
507
+
508
+ /* WaitNamedPipeW with the GVL released. */
509
+ typedef struct { const WCHAR *name; DWORD ms; BOOL ok; DWORD gle; } wnp_t;
510
+ static void *wnp_fn(void *p) {
511
+ wnp_t *w = (wnp_t *)p;
512
+ w->ok = WaitNamedPipeW(w->name, w->ms);
513
+ w->gle = w->ok ? 0 : GetLastError();
514
+ return NULL;
515
+ }
516
+
517
+ /* Sleep with the GVL released (short slices, used while polling for a server
518
+ * instance to appear). */
519
+ static void *sleep_fn(void *p) { Sleep(*(DWORD *)p); return NULL; }
520
+ static void sleep_gvl(DWORD ms) {
521
+ rb_thread_call_without_gvl(sleep_fn, &ms, RUBY_UBF_IO, NULL);
522
+ }
523
+
524
+ /* Winipc::Pipe._connect(wname, access_rights, want_message, timeout_ms) -> Conn */
525
+ static VALUE
526
+ pipe_connect(VALUE klass, VALUE wname, VALUE access_rights, VALUE want_message, VALUE vms)
527
+ {
528
+ DWORD access = NUM2ULONG(access_rights);
529
+ long ms_in = NUM2LONG(vms);
530
+ int want_msg = RTEST(want_message);
531
+ HANDLE h = INVALID_HANDLE_VALUE;
532
+ VALUE connobj;
533
+ conn_t *c;
534
+ DWORD gle;
535
+ ULONGLONG deadline = (ms_in > 0) ? GetTickCount64() + (ULONGLONG)ms_in : 0;
536
+
537
+ /* The wide name is rebuilt each iteration and freed before any raise /
538
+ * interrupt check, so an interrupt (Thread#kill/Timeout) raised mid-loop
539
+ * never leaks it. Each blocking wait is sliced so interrupts are serviced
540
+ * promptly rather than only at the deadline. */
541
+ for (;;) {
542
+ WCHAR *name = to_wide(wname);
543
+ DWORD remaining, slice;
544
+ h = CreateFileW(name, access, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
545
+ if (h != INVALID_HANDLE_VALUE) { xfree(name); break; }
546
+ gle = GetLastError();
547
+
548
+ if (!((gle == ERROR_PIPE_BUSY || gle == ERROR_FILE_NOT_FOUND) && ms_in != 0)) {
549
+ xfree(name);
550
+ raise_gle("CreateFile(pipe)", gle);
551
+ }
552
+ /* server busy (all instances in use) or not-yet-present — wait/poll */
553
+ if (ms_in < 0) {
554
+ remaining = INFINITE;
555
+ } else {
556
+ ULONGLONG now = GetTickCount64();
557
+ if (now >= deadline) { xfree(name); raise_gle("Connect(pipe)", gle); }
558
+ remaining = (DWORD)(deadline - now);
559
+ }
560
+ slice = (remaining == INFINITE || remaining > 100) ? 100 : remaining;
561
+ if (gle == ERROR_PIPE_BUSY) {
562
+ wnp_t w;
563
+ w.name = name; w.ms = slice; w.ok = FALSE; w.gle = 0;
564
+ rb_thread_call_without_gvl(wnp_fn, &w, RUBY_UBF_IO, NULL);
565
+ } else {
566
+ sleep_gvl(slice);
567
+ }
568
+ xfree(name); /* freed before the interrupt check below */
569
+ rb_thread_check_ints(); /* deliver Thread#kill / Timeout promptly */
570
+ }
571
+
572
+ connobj = conn_alloc(cConn);
573
+ c = conn_get(connobj);
574
+ c->h = h;
575
+ c->is_server = 0;
576
+ c->read_event = CreateEventW(NULL, TRUE, FALSE, NULL);
577
+ c->write_event = CreateEventW(NULL, TRUE, FALSE, NULL);
578
+ if (!c->read_event || !c->write_event) raise_gle("CreateEvent", GetLastError());
579
+
580
+ if (want_msg) {
581
+ DWORD mode = PIPE_READMODE_MESSAGE;
582
+ if (!SetNamedPipeHandleState(c->h, &mode, NULL, NULL))
583
+ raise_gle("SetNamedPipeHandleState", GetLastError());
584
+ c->message_read = 1;
585
+ }
586
+ return connobj;
587
+ }
588
+
589
+ /* =====================================================================
590
+ * Conn I/O
591
+ * ===================================================================== */
592
+
593
+ /* One overlapped read into a buffer of cap bytes. Returns:
594
+ * >=0 bytes read (0 means peer wrote 0 bytes)
595
+ * -1 clean EOF (peer closed) -> Ruby nil
596
+ * -2 more data available (message mode) -> caller loops
597
+ * Raises on real errors. *abort_canceled set if cancelled. */
598
+ #define READ_EOF (-1)
599
+ #define READ_MORE (-2)
600
+
601
+ static long
602
+ conn_read_once(conn_t *c, char *buf, DWORD cap)
603
+ {
604
+ OVERLAPPED ov;
605
+ DWORD n = 0, gle, wgle = 0;
606
+ BOOL ok;
607
+
608
+ memset(&ov, 0, sizeof(ov));
609
+ ov.hEvent = c->read_event;
610
+ ResetEvent(c->read_event);
611
+
612
+ ok = ReadFile(c->h, buf, cap, &n, &ov);
613
+ if (!ok) {
614
+ gle = GetLastError();
615
+ if (gle == ERROR_IO_PENDING) {
616
+ DWORD w = ov_wait(c->h, &ov, INFINITE, &wgle);
617
+ if (w != WAIT_OBJECT_0) { ov_drain(c->h, &ov); raise_gle("WaitForSingleObject", wgle); }
618
+ ok = GetOverlappedResult(c->h, &ov, &n, FALSE);
619
+ if (!ok) {
620
+ gle = GetLastError();
621
+ if (gle == ERROR_BROKEN_PIPE || gle == ERROR_PIPE_NOT_CONNECTED) return READ_EOF;
622
+ raise_gle("ReadFile", gle); /* ERROR_OPERATION_ABORTED -> Canceled */
623
+ }
624
+ return (long)n;
625
+ }
626
+ if (gle == ERROR_BROKEN_PIPE || gle == ERROR_PIPE_NOT_CONNECTED) return READ_EOF;
627
+ raise_gle("ReadFile", gle);
628
+ }
629
+ return (long)n;
630
+ }
631
+
632
+ /* Conn#_read(maxlen) -> String or nil (EOF). Byte semantics: returns up to
633
+ * maxlen bytes as they arrive. */
634
+ static VALUE
635
+ conn_read(VALUE self, VALUE vmax)
636
+ {
637
+ conn_t *c = conn_live(self);
638
+ long max = NUM2LONG(vmax);
639
+ VALUE out;
640
+ long r;
641
+
642
+ if (max <= 0) rb_raise(rb_eArgError, "winipc: read length must be > 0");
643
+ if (c->message_read)
644
+ rb_raise(eModeError, "winipc: use #read_message on a message-mode pipe");
645
+ out = rb_str_new(NULL, max);
646
+ r = conn_read_once(c, RSTRING_PTR(out), (DWORD)max);
647
+ if (r == READ_EOF) return Qnil;
648
+ rb_str_set_len(out, r);
649
+ rb_enc_associate(out, rb_ascii8bit_encoding());
650
+ return out;
651
+ }
652
+
653
+ /* Conn#_read_message(maxlen) -> String or nil. Reassembles a whole message,
654
+ * growing past ERROR_MORE_DATA. */
655
+ static VALUE
656
+ conn_read_message(VALUE self, VALUE vmax)
657
+ {
658
+ conn_t *c = conn_live(self);
659
+ long max = NUM2LONG(vmax);
660
+ OVERLAPPED ov;
661
+ DWORD n = 0, gle, off = 0, cap;
662
+ VALUE out;
663
+ BOOL ok;
664
+
665
+ if (max <= 0) rb_raise(rb_eArgError, "winipc: read length must be > 0");
666
+ cap = (DWORD)max;
667
+ out = rb_str_new(NULL, cap);
668
+
669
+ for (;;) {
670
+ DWORD wgle = 0;
671
+ memset(&ov, 0, sizeof(ov));
672
+ ov.hEvent = c->read_event;
673
+ ResetEvent(c->read_event);
674
+ ok = ReadFile(c->h, RSTRING_PTR(out) + off, cap - off, &n, &ov);
675
+ if (!ok) {
676
+ gle = GetLastError();
677
+ if (gle == ERROR_IO_PENDING) {
678
+ DWORD w = ov_wait(c->h, &ov, INFINITE, &wgle);
679
+ if (w != WAIT_OBJECT_0) { ov_drain(c->h, &ov); raise_gle("WaitForSingleObject", wgle); }
680
+ ok = GetOverlappedResult(c->h, &ov, &n, FALSE);
681
+ if (!ok) gle = GetLastError();
682
+ }
683
+ if (!ok) {
684
+ if (gle == ERROR_MORE_DATA) {
685
+ off += n;
686
+ if (cap > 0x7FFFFFFFu / 2u) rb_raise(rb_eArgError, "winipc: message too large");
687
+ cap *= 2;
688
+ rb_str_resize(out, cap);
689
+ continue;
690
+ }
691
+ if (gle == ERROR_BROKEN_PIPE || gle == ERROR_PIPE_NOT_CONNECTED) {
692
+ if (off == 0) return Qnil; /* clean EOF before any bytes */
693
+ raise_gle("ReadFile", gle);
694
+ }
695
+ raise_gle("ReadFile", gle);
696
+ }
697
+ }
698
+ off += n;
699
+ break;
700
+ }
701
+ rb_str_set_len(out, off);
702
+ rb_enc_associate(out, rb_ascii8bit_encoding());
703
+ return out;
704
+ }
705
+
706
+ /* Conn#_write(bytes) -> Integer bytes written. */
707
+ static VALUE
708
+ conn_write(VALUE self, VALUE data)
709
+ {
710
+ conn_t *c = conn_live(self);
711
+ OVERLAPPED ov;
712
+ DWORD len, n = 0, gle, wgle = 0;
713
+ const char *p;
714
+ BOOL ok;
715
+
716
+ len = dword_len(data);
717
+ p = RSTRING_PTR(data);
718
+ memset(&ov, 0, sizeof(ov));
719
+ ov.hEvent = c->write_event;
720
+ ResetEvent(c->write_event);
721
+
722
+ ok = WriteFile(c->h, p, len, &n, &ov);
723
+ if (!ok) {
724
+ gle = GetLastError();
725
+ if (gle == ERROR_IO_PENDING) {
726
+ DWORD w = ov_wait(c->h, &ov, INFINITE, &wgle);
727
+ if (w != WAIT_OBJECT_0) { ov_drain(c->h, &ov); raise_gle("WaitForSingleObject", wgle); }
728
+ ok = GetOverlappedResult(c->h, &ov, &n, FALSE);
729
+ if (!ok) gle = GetLastError();
730
+ }
731
+ if (!ok) raise_gle("WriteFile", gle);
732
+ }
733
+ return ULONG2NUM(n);
734
+ }
735
+
736
+ static VALUE
737
+ conn_flush(VALUE self)
738
+ {
739
+ conn_t *c = conn_live(self);
740
+ if (!FlushFileBuffers(c->h)) raise_gle("FlushFileBuffers", GetLastError());
741
+ return self;
742
+ }
743
+
744
+ static VALUE
745
+ conn_close(VALUE self)
746
+ {
747
+ conn_t *c = conn_get(self);
748
+ if (!c->closed && c->h != INVALID_HANDLE_VALUE) {
749
+ CloseHandle(c->h); /* graceful: peer drains buffered data, then EOF */
750
+ c->h = INVALID_HANDLE_VALUE;
751
+ }
752
+ if (c->read_event) { CloseHandle(c->read_event); c->read_event = NULL; }
753
+ if (c->write_event) { CloseHandle(c->write_event); c->write_event = NULL; }
754
+ c->closed = 1;
755
+ return Qnil;
756
+ }
757
+
758
+ static VALUE conn_closed_p(VALUE self) { return conn_get(self)->closed ? Qtrue : Qfalse; }
759
+ static VALUE conn_server_p(VALUE self) { return conn_get(self)->is_server ? Qtrue : Qfalse; }
760
+ static VALUE conn_message_p(VALUE self) { return conn_get(self)->message_read ? Qtrue : Qfalse; }
761
+
762
+ /* =====================================================================
763
+ * Shared memory — Winipc::SharedMemory
764
+ * ===================================================================== */
765
+
766
+ typedef struct {
767
+ HANDLE map;
768
+ void *view;
769
+ unsigned long long size;
770
+ int writable;
771
+ int closed;
772
+ } shm_t;
773
+
774
+ static void
775
+ shm_free(void *p)
776
+ {
777
+ shm_t *s = (shm_t *)p;
778
+ if (s->view) UnmapViewOfFile(s->view);
779
+ if (s->map) CloseHandle(s->map);
780
+ xfree(s);
781
+ }
782
+
783
+ static const rb_data_type_t shm_type = {
784
+ "Winipc::SharedMemory", { 0, shm_free, 0, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
785
+ };
786
+
787
+ static VALUE
788
+ shm_alloc(VALUE klass)
789
+ {
790
+ shm_t *s;
791
+ VALUE obj = TypedData_Make_Struct(klass, shm_t, &shm_type, s);
792
+ s->map = NULL; s->view = NULL; s->size = 0; s->writable = 1; s->closed = 0;
793
+ return obj;
794
+ }
795
+
796
+ static shm_t *
797
+ shm_get(VALUE self)
798
+ {
799
+ shm_t *s;
800
+ TypedData_Get_Struct(self, shm_t, &shm_type, s);
801
+ return s;
802
+ }
803
+
804
+ static shm_t *
805
+ shm_live(VALUE self)
806
+ {
807
+ shm_t *s = shm_get(self);
808
+ if (s->closed || !s->view) rb_raise(eClosed, "winipc: shared memory is closed");
809
+ return s;
810
+ }
811
+
812
+ /* SharedMemory._create(wname, size, readwrite, access) -> SharedMemory */
813
+ static VALUE
814
+ shm_create(VALUE klass, VALUE wname, VALUE vsize, VALUE rw, VALUE access)
815
+ {
816
+ VALUE obj = shm_alloc(cSharedMemory);
817
+ shm_t *s = shm_get(obj);
818
+ unsigned long long size = NUM2ULL(vsize);
819
+ int writable = RTEST(rw);
820
+ SECURITY_ATTRIBUTES sa;
821
+ PSECURITY_DESCRIPTOR psd = NULL;
822
+ DWORD prot = writable ? PAGE_READWRITE : PAGE_READONLY;
823
+ DWORD hi = (DWORD)(size >> 32), lo = (DWORD)(size & 0xFFFFFFFFull);
824
+ DWORD gle;
825
+ int use_sa;
826
+ WCHAR *name;
827
+
828
+ if (size == 0) rb_raise(rb_eArgError, "winipc: size must be > 0");
829
+ use_sa = build_sa(access, &sa, &psd); /* may raise; do before allocating name */
830
+ name = to_wide(wname);
831
+
832
+ s->map = CreateFileMappingW(INVALID_HANDLE_VALUE, use_sa ? &sa : NULL, prot, hi, lo, name);
833
+ gle = GetLastError();
834
+ xfree(name);
835
+ if (psd) LocalFree(psd);
836
+ if (!s->map) raise_gle("CreateFileMapping", gle);
837
+ if (gle == ERROR_ALREADY_EXISTS) {
838
+ /* a mapping of that name already exists with its OWN size — refuse */
839
+ CloseHandle(s->map); s->map = NULL;
840
+ raise_code(eExists, "CreateFileMapping", ERROR_ALREADY_EXISTS);
841
+ }
842
+
843
+ s->view = MapViewOfFile(s->map, writable ? FILE_MAP_WRITE : FILE_MAP_READ, 0, 0, 0);
844
+ if (!s->view) raise_gle("MapViewOfFile", GetLastError());
845
+ s->size = size;
846
+ s->writable = writable;
847
+ return obj;
848
+ }
849
+
850
+ /* SharedMemory._open(wname, readwrite) -> SharedMemory */
851
+ static VALUE
852
+ shm_open(VALUE klass, VALUE wname, VALUE rw)
853
+ {
854
+ VALUE obj = shm_alloc(cSharedMemory);
855
+ shm_t *s = shm_get(obj);
856
+ int writable = RTEST(rw);
857
+ WCHAR *name = to_wide(wname);
858
+ DWORD acc = writable ? FILE_MAP_WRITE : FILE_MAP_READ;
859
+ MEMORY_BASIC_INFORMATION mbi;
860
+ DWORD gle;
861
+
862
+ s->map = OpenFileMappingW(acc, FALSE, name);
863
+ gle = GetLastError();
864
+ xfree(name);
865
+ if (!s->map) raise_gle("OpenFileMapping", gle);
866
+
867
+ s->view = MapViewOfFile(s->map, acc, 0, 0, 0);
868
+ if (!s->view) raise_gle("MapViewOfFile", GetLastError());
869
+ /* Discover the mapped region size (the opener didn't specify it). */
870
+ if (VirtualQuery(s->view, &mbi, sizeof(mbi)) == 0)
871
+ raise_gle("VirtualQuery", GetLastError());
872
+ s->size = (unsigned long long)mbi.RegionSize;
873
+ s->writable = writable;
874
+ return obj;
875
+ }
876
+
877
+ static void
878
+ shm_bounds(shm_t *s, unsigned long long off, unsigned long long len)
879
+ {
880
+ if (off > s->size || len > s->size || off + len > s->size)
881
+ rb_raise(eRangeError, "winipc: [%llu,%llu) out of bounds (size %llu)",
882
+ off, off + len, s->size);
883
+ }
884
+
885
+ static VALUE
886
+ shm_read(VALUE self, VALUE voff, VALUE vlen)
887
+ {
888
+ shm_t *s = shm_live(self);
889
+ unsigned long long off = NUM2ULL(voff), len = NUM2ULL(vlen);
890
+ VALUE out;
891
+ shm_bounds(s, off, len);
892
+ if (len > (unsigned long long)LONG_MAX)
893
+ rb_raise(rb_eArgError, "winipc: read length too large");
894
+ out = rb_str_new((const char *)s->view + off, (long)len);
895
+ rb_enc_associate(out, rb_ascii8bit_encoding());
896
+ return out;
897
+ }
898
+
899
+ static VALUE
900
+ shm_write(VALUE self, VALUE voff, VALUE data)
901
+ {
902
+ shm_t *s = shm_live(self);
903
+ unsigned long long off = NUM2ULL(voff);
904
+ DWORD len;
905
+ if (!s->writable) rb_raise(eError, "winipc: shared memory is read-only");
906
+ len = dword_len(data);
907
+ shm_bounds(s, off, (unsigned long long)len);
908
+ memcpy((char *)s->view + off, RSTRING_PTR(data), len);
909
+ return ULONG2NUM(len);
910
+ }
911
+
912
+ static VALUE
913
+ shm_flush(VALUE self, VALUE voff, VALUE vlen)
914
+ {
915
+ shm_t *s = shm_live(self);
916
+ unsigned long long off = NUM2ULL(voff), len = NIL_P(vlen) ? 0 : NUM2ULL(vlen);
917
+ if (off > s->size) rb_raise(eRangeError, "winipc: flush offset out of bounds");
918
+ if (len) shm_bounds(s, off, len);
919
+ if (off >= s->size) return self; /* nothing past the end to flush (no-op) */
920
+ if (!FlushViewOfFile((char *)s->view + off, (SIZE_T)len))
921
+ raise_gle("FlushViewOfFile", GetLastError());
922
+ return self;
923
+ }
924
+
925
+ static VALUE shm_size(VALUE self) { return ULL2NUM(shm_get(self)->size); }
926
+
927
+ static VALUE
928
+ shm_close(VALUE self)
929
+ {
930
+ shm_t *s = shm_get(self);
931
+ if (s->view) { UnmapViewOfFile(s->view); s->view = NULL; }
932
+ if (s->map) { CloseHandle(s->map); s->map = NULL; }
933
+ s->closed = 1;
934
+ return Qnil;
935
+ }
936
+
937
+ static VALUE shm_closed_p(VALUE self) { return shm_get(self)->closed ? Qtrue : Qfalse; }
938
+
939
+ /* =====================================================================
940
+ * Named synchronization objects — Mutex / Event / Semaphore
941
+ * ===================================================================== */
942
+
943
+ enum { K_MUTEX, K_EVENT, K_SEM };
944
+
945
+ typedef struct {
946
+ HANDLE h;
947
+ int kind;
948
+ int created; /* false if it already existed */
949
+ int abandoned; /* last wait returned WAIT_ABANDONED (mutex) */
950
+ int closed;
951
+ } sync_t;
952
+
953
+ static void
954
+ sync_free(void *p)
955
+ {
956
+ sync_t *s = (sync_t *)p;
957
+ if (s->h) CloseHandle(s->h);
958
+ xfree(s);
959
+ }
960
+
961
+ static const rb_data_type_t sync_type = {
962
+ "Winipc::SyncObject", { 0, sync_free, 0, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
963
+ };
964
+
965
+ static VALUE
966
+ sync_alloc(VALUE klass)
967
+ {
968
+ sync_t *s;
969
+ VALUE obj = TypedData_Make_Struct(klass, sync_t, &sync_type, s);
970
+ s->h = NULL; s->kind = -1; s->created = 0; s->abandoned = 0; s->closed = 0;
971
+ return obj;
972
+ }
973
+
974
+ static sync_t *
975
+ sync_get(VALUE self)
976
+ {
977
+ sync_t *s;
978
+ TypedData_Get_Struct(self, sync_t, &sync_type, s);
979
+ return s;
980
+ }
981
+
982
+ static sync_t *
983
+ sync_live(VALUE self)
984
+ {
985
+ sync_t *s = sync_get(self);
986
+ if (s->closed || !s->h) rb_raise(eClosed, "winipc: object is closed");
987
+ return s;
988
+ }
989
+
990
+ /* GVL-released, interruptible wait via bounded slices. Each slice releases the
991
+ * GVL for at most SYNC_SLICE_MS, then re-acquires it to service interrupts
992
+ * (Thread#kill / Ctrl-C / Timeout) before continuing. No shared wake object, so
993
+ * concurrent waiters on the same sync object don't interfere. Returns
994
+ * WAIT_OBJECT_0 / WAIT_TIMEOUT / WAIT_ABANDONED_0; raises on WAIT_FAILED. */
995
+ #define SYNC_SLICE_MS 100
996
+ typedef struct { HANDLE h; DWORD ms; DWORD result; DWORD gle; } sw_t;
997
+ static void *sw_fn(void *p) {
998
+ sw_t *w = (sw_t *)p;
999
+ w->result = WaitForSingleObject(w->h, w->ms);
1000
+ if (w->result == WAIT_FAILED) w->gle = GetLastError(); /* capture before GVL */
1001
+ return NULL;
1002
+ }
1003
+
1004
+ static DWORD
1005
+ sync_wait(sync_t *s, DWORD total_ms)
1006
+ {
1007
+ ULONGLONG start = GetTickCount64();
1008
+ for (;;) {
1009
+ sw_t w;
1010
+ DWORD slice;
1011
+ /* A 0-length slice still performs ONE non-blocking probe, so
1012
+ * timeout==0 (try_lock / try_acquire) actually tests the object. */
1013
+ if (total_ms == INFINITE) {
1014
+ slice = SYNC_SLICE_MS;
1015
+ } else {
1016
+ ULONGLONG elapsed = GetTickCount64() - start;
1017
+ DWORD remaining = (elapsed >= total_ms) ? 0 : (DWORD)(total_ms - elapsed);
1018
+ slice = (remaining > SYNC_SLICE_MS) ? SYNC_SLICE_MS : remaining;
1019
+ }
1020
+ w.h = s->h; w.ms = slice; w.result = WAIT_FAILED; w.gle = 0;
1021
+ rb_thread_call_without_gvl(sw_fn, &w, NULL, NULL);
1022
+ if (w.result == WAIT_OBJECT_0) return WAIT_OBJECT_0; /* acquired */
1023
+ if (w.result == WAIT_ABANDONED_0) return WAIT_ABANDONED_0; /* mutex abandoned */
1024
+ if (w.result == WAIT_FAILED) raise_gle("WaitForSingleObject", w.gle);
1025
+ /* WAIT_TIMEOUT for this slice: stop if the overall deadline passed,
1026
+ * else service interrupts and keep waiting. */
1027
+ if (total_ms != INFINITE) {
1028
+ ULONGLONG elapsed = GetTickCount64() - start;
1029
+ if (elapsed >= total_ms) return WAIT_TIMEOUT;
1030
+ }
1031
+ rb_thread_check_ints(); /* may raise (Thread#kill / Timeout) */
1032
+ }
1033
+ }
1034
+
1035
+ /* sync_create(kind, wname, a, b, access) — a/b meaning depends on kind:
1036
+ * mutex: (unused, unused)
1037
+ * event: (manual_reset, initial_state)
1038
+ * semaphore: (initial_count, maximum_count)
1039
+ */
1040
+ static VALUE
1041
+ sync_create(VALUE klass, VALUE vkind, VALUE wname, VALUE a, VALUE b, VALUE access)
1042
+ {
1043
+ VALUE obj = sync_alloc(klass);
1044
+ sync_t *s = sync_get(obj);
1045
+ int kind = NUM2INT(vkind);
1046
+ SECURITY_ATTRIBUTES sa;
1047
+ PSECURITY_DESCRIPTOR psd = NULL;
1048
+ int use_sa = build_sa(access, &sa, &psd); /* may raise; do before allocating name */
1049
+ LPSECURITY_ATTRIBUTES psa = use_sa ? &sa : NULL;
1050
+ WCHAR *name = to_wide(wname);
1051
+ DWORD gle;
1052
+
1053
+ s->kind = kind;
1054
+ if (kind == K_MUTEX) {
1055
+ s->h = CreateMutexW(psa, FALSE, name);
1056
+ } else if (kind == K_EVENT) {
1057
+ s->h = CreateEventW(psa, RTEST(a), RTEST(b), name);
1058
+ } else {
1059
+ s->h = CreateSemaphoreW(psa, NUM2LONG(a), NUM2LONG(b), name);
1060
+ }
1061
+ gle = GetLastError();
1062
+ xfree(name);
1063
+ if (psd) LocalFree(psd);
1064
+ if (!s->h) raise_gle("Create(sync)", gle);
1065
+ s->created = (gle == ERROR_ALREADY_EXISTS) ? 0 : 1;
1066
+ return obj;
1067
+ }
1068
+
1069
+ /* sync_open(kind, wname) */
1070
+ static VALUE
1071
+ sync_open(VALUE klass, VALUE vkind, VALUE wname)
1072
+ {
1073
+ VALUE obj = sync_alloc(klass);
1074
+ sync_t *s = sync_get(obj);
1075
+ int kind = NUM2INT(vkind);
1076
+ WCHAR *name = to_wide(wname);
1077
+ DWORD gle;
1078
+
1079
+ s->kind = kind;
1080
+ if (kind == K_MUTEX) {
1081
+ s->h = OpenMutexW(SYNCHRONIZE, FALSE, name);
1082
+ } else if (kind == K_EVENT) {
1083
+ s->h = OpenEventW(EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, name);
1084
+ } else {
1085
+ s->h = OpenSemaphoreW(SEMAPHORE_MODIFY_STATE | SYNCHRONIZE, FALSE, name);
1086
+ }
1087
+ gle = GetLastError();
1088
+ xfree(name);
1089
+ if (!s->h) raise_gle("Open(sync)", gle);
1090
+ s->created = 0;
1091
+ return obj;
1092
+ }
1093
+
1094
+ /* #_wait(ms) -> :ok / :timeout / :abandoned (-1 ms = INFINITE) */
1095
+ static VALUE
1096
+ sync_do_wait(VALUE self, VALUE vms)
1097
+ {
1098
+ sync_t *s = sync_live(self);
1099
+ long ms_in = NUM2LONG(vms);
1100
+ DWORD r = sync_wait(s, (ms_in < 0) ? INFINITE : (DWORD)ms_in);
1101
+ if (r == WAIT_OBJECT_0) return ID2SYM(rb_intern("ok"));
1102
+ if (r == WAIT_TIMEOUT) return ID2SYM(rb_intern("timeout"));
1103
+ if (r == WAIT_ABANDONED_0) { s->abandoned = 1; return ID2SYM(rb_intern("abandoned")); }
1104
+ return ID2SYM(rb_intern("timeout"));
1105
+ }
1106
+
1107
+ static VALUE
1108
+ mutex_unlock(VALUE self)
1109
+ {
1110
+ sync_t *s = sync_live(self);
1111
+ if (!ReleaseMutex(s->h)) {
1112
+ DWORD e = GetLastError();
1113
+ if (e == ERROR_NOT_OWNER) raise_code(eNotOwner, "ReleaseMutex", e);
1114
+ raise_gle("ReleaseMutex", e);
1115
+ }
1116
+ return self;
1117
+ }
1118
+
1119
+ static VALUE
1120
+ event_signal(VALUE self)
1121
+ {
1122
+ sync_t *s = sync_live(self);
1123
+ if (!SetEvent(s->h)) raise_gle("SetEvent", GetLastError());
1124
+ return self;
1125
+ }
1126
+
1127
+ static VALUE
1128
+ event_reset(VALUE self)
1129
+ {
1130
+ sync_t *s = sync_live(self);
1131
+ if (!ResetEvent(s->h)) raise_gle("ResetEvent", GetLastError());
1132
+ return self;
1133
+ }
1134
+
1135
+ static VALUE
1136
+ sem_release(VALUE self, VALUE vcount)
1137
+ {
1138
+ sync_t *s = sync_live(self);
1139
+ LONG count = NUM2LONG(vcount), prev = 0;
1140
+ if (!ReleaseSemaphore(s->h, count, &prev)) {
1141
+ DWORD e = GetLastError();
1142
+ if (e == ERROR_TOO_MANY_POSTS) raise_code(eWouldExceedMax, "ReleaseSemaphore", e);
1143
+ raise_gle("ReleaseSemaphore", e);
1144
+ }
1145
+ return LONG2NUM(prev);
1146
+ }
1147
+
1148
+ static VALUE sync_created_p(VALUE self) { return sync_get(self)->created ? Qtrue : Qfalse; }
1149
+ static VALUE sync_abandoned_p(VALUE self) { return sync_get(self)->abandoned ? Qtrue : Qfalse; }
1150
+
1151
+ static VALUE
1152
+ sync_close(VALUE self)
1153
+ {
1154
+ sync_t *s = sync_get(self);
1155
+ if (s->h) { CloseHandle(s->h); s->h = NULL; }
1156
+ s->closed = 1;
1157
+ return Qnil;
1158
+ }
1159
+
1160
+ static VALUE sync_closed_p(VALUE self) { return sync_get(self)->closed ? Qtrue : Qfalse; }
1161
+
1162
+ /* ----------------------------------------------------------------- Init --- */
1163
+
1164
+ void
1165
+ Init_winipc(void)
1166
+ {
1167
+ mWinipc = rb_define_module("Winipc");
1168
+
1169
+ eError = rb_define_class_under(mWinipc, "Error", rb_eStandardError);
1170
+ eOSError = rb_define_class_under(mWinipc, "OSError", eError);
1171
+ eTimeout = rb_define_class_under(mWinipc, "TimeoutError", eOSError);
1172
+ eNotFound = rb_define_class_under(mWinipc, "NotFound", eOSError);
1173
+ eExists = rb_define_class_under(mWinipc, "Exists", eOSError);
1174
+ eAccessDenied = rb_define_class_under(mWinipc, "AccessDenied", eOSError);
1175
+ eBrokenPipe = rb_define_class_under(mWinipc, "BrokenPipe", eOSError);
1176
+ ePipeBusy = rb_define_class_under(mWinipc, "PipeBusy", eOSError);
1177
+ eCanceled = rb_define_class_under(mWinipc, "Canceled", eOSError);
1178
+ eModeError = rb_define_class_under(mWinipc, "ModeError", eError);
1179
+ eRangeError = rb_define_class_under(mWinipc, "RangeError", eError);
1180
+ eClosed = rb_define_class_under(mWinipc, "Closed", eError);
1181
+ eNotOwner = rb_define_class_under(mWinipc, "NotOwner", eOSError);
1182
+ eAbandoned = rb_define_class_under(mWinipc, "Abandoned", eError);
1183
+ eWouldExceedMax = rb_define_class_under(mWinipc, "WouldExceedMax", eOSError);
1184
+
1185
+ /* Pipe (class methods _listen / _connect; defined in Ruby wrappers too) */
1186
+ cPipe = rb_define_class_under(mWinipc, "Pipe", rb_cObject);
1187
+ rb_define_singleton_method(cPipe, "_listen", pipe_listen, 7);
1188
+ rb_define_singleton_method(cPipe, "_connect", pipe_connect, 4);
1189
+
1190
+ cListener = rb_define_class_under(cPipe, "Listener", rb_cObject);
1191
+ rb_define_alloc_func(cListener, listener_alloc);
1192
+ rb_define_method(cListener, "_accept", listener_accept, 1);
1193
+ rb_define_method(cListener, "close", listener_close, 0);
1194
+ rb_define_method(cListener, "closed?", listener_closed_p, 0);
1195
+
1196
+ cConn = rb_define_class_under(cPipe, "Conn", rb_cObject);
1197
+ rb_define_alloc_func(cConn, conn_alloc);
1198
+ rb_define_method(cConn, "_read", conn_read, 1);
1199
+ rb_define_method(cConn, "_read_message", conn_read_message, 1);
1200
+ rb_define_method(cConn, "_write", conn_write, 1);
1201
+ rb_define_method(cConn, "flush", conn_flush, 0);
1202
+ rb_define_method(cConn, "close", conn_close, 0);
1203
+ rb_define_method(cConn, "closed?", conn_closed_p, 0);
1204
+ rb_define_method(cConn, "server?", conn_server_p, 0);
1205
+ rb_define_method(cConn, "message?", conn_message_p, 0);
1206
+
1207
+ /* SharedMemory */
1208
+ cSharedMemory = rb_define_class_under(mWinipc, "SharedMemory", rb_cObject);
1209
+ rb_define_alloc_func(cSharedMemory, shm_alloc);
1210
+ rb_define_singleton_method(cSharedMemory, "_create", shm_create, 4);
1211
+ rb_define_singleton_method(cSharedMemory, "_open", shm_open, 2);
1212
+ rb_define_method(cSharedMemory, "read", shm_read, 2);
1213
+ rb_define_method(cSharedMemory, "write", shm_write, 2);
1214
+ rb_define_method(cSharedMemory, "_flush", shm_flush, 2);
1215
+ rb_define_method(cSharedMemory, "size", shm_size, 0);
1216
+ rb_define_method(cSharedMemory, "close", shm_close, 0);
1217
+ rb_define_method(cSharedMemory, "closed?", shm_closed_p, 0);
1218
+
1219
+ /* Sync objects share the C constructors; subclasses pass their kind. */
1220
+ rb_define_const(mWinipc, "K_MUTEX", INT2FIX(K_MUTEX));
1221
+ rb_define_const(mWinipc, "K_EVENT", INT2FIX(K_EVENT));
1222
+ rb_define_const(mWinipc, "K_SEM", INT2FIX(K_SEM));
1223
+
1224
+ cMutex = rb_define_class_under(mWinipc, "Mutex", rb_cObject);
1225
+ cEvent = rb_define_class_under(mWinipc, "Event", rb_cObject);
1226
+ cSemaphore = rb_define_class_under(mWinipc, "Semaphore", rb_cObject);
1227
+
1228
+ {
1229
+ VALUE klasses[3]; int i;
1230
+ klasses[0] = cMutex; klasses[1] = cEvent; klasses[2] = cSemaphore;
1231
+ for (i = 0; i < 3; i++) {
1232
+ rb_define_alloc_func(klasses[i], sync_alloc);
1233
+ rb_define_singleton_method(klasses[i], "_create", sync_create, 5);
1234
+ rb_define_singleton_method(klasses[i], "_open", sync_open, 2);
1235
+ rb_define_method(klasses[i], "_wait", sync_do_wait, 1);
1236
+ rb_define_method(klasses[i], "created?", sync_created_p, 0);
1237
+ rb_define_method(klasses[i], "close", sync_close, 0);
1238
+ rb_define_method(klasses[i], "closed?", sync_closed_p, 0);
1239
+ }
1240
+ }
1241
+ rb_define_method(cMutex, "_unlock", mutex_unlock, 0);
1242
+ rb_define_method(cMutex, "abandoned?", sync_abandoned_p, 0);
1243
+ rb_define_method(cEvent, "signal", event_signal, 0);
1244
+ rb_define_method(cEvent, "reset", event_reset, 0);
1245
+ rb_define_method(cSemaphore, "_release", sem_release, 1);
1246
+ }