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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +29 -0
- data/LICENSE.txt +21 -0
- data/README.md +154 -0
- data/ext/winipc/extconf.rb +21 -0
- data/ext/winipc/winipc.c +1246 -0
- data/lib/winipc/version.rb +5 -0
- data/lib/winipc.rb +338 -0
- metadata +121 -0
data/ext/winipc/winipc.c
ADDED
|
@@ -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
|
+
}
|