cool.io 1.9.5 → 1.10.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,1305 @@
1
+ /* Compile a private libev copy to count Windows API calls and inject failures
2
+ * without adding test hooks to the cool.io extension. */
3
+ #include <ruby.h>
4
+ #include <ruby/encoding.h>
5
+ #include <winsock2.h>
6
+ #include <windows.h>
7
+ #include <errno.h>
8
+
9
+ static int fail_socket, socket_calls, syserr_calls, changed, timed_out;
10
+ static HANDLE completion_entered, completion_release;
11
+ static volatile LONG delay_completion;
12
+ static int directory_opens, identity_calls, name_calls, fail_alias;
13
+
14
+ static HANDLE WINAPI
15
+ test_create_file (LPCWSTR path, DWORD access, DWORD share, LPSECURITY_ATTRIBUTES security,
16
+ DWORD disposition, DWORD attributes, HANDLE template)
17
+ {
18
+ ++directory_opens;
19
+ return CreateFileW (path, access, share, security, disposition, attributes, template);
20
+ }
21
+
22
+ static BOOL WINAPI
23
+ test_file_information (HANDLE handle, LPBY_HANDLE_FILE_INFORMATION information)
24
+ {
25
+ ++identity_calls;
26
+ return GetFileInformationByHandle (handle, information);
27
+ }
28
+
29
+ static const wchar_t *long_prefix;
30
+
31
+ static HANDLE WINAPI
32
+ test_find_file (LPCWSTR path, FINDEX_INFO_LEVELS level, LPVOID data, FINDEX_SEARCH_OPS search,
33
+ LPVOID filter, DWORD flags)
34
+ {
35
+ HANDLE handle;
36
+ wchar_t *name;
37
+ size_t extra;
38
+ ++name_calls;
39
+ if (fail_alias)
40
+ {
41
+ SetLastError (ERROR_ACCESS_DENIED);
42
+ return INVALID_HANDLE_VALUE;
43
+ }
44
+ handle = FindFirstFileExW (path, level, data, search, filter, flags);
45
+ if (handle != INVALID_HANDLE_VALUE && long_prefix)
46
+ {
47
+ name = ((WIN32_FIND_DATAW *)data)->cFileName;
48
+ extra = wcslen (long_prefix);
49
+ if (wcslen (name) + extra >= MAX_PATH)
50
+ {
51
+ FindClose (handle);
52
+ SetLastError (ERROR_INSUFFICIENT_BUFFER);
53
+ return INVALID_HANDLE_VALUE;
54
+ }
55
+ memmove (name + extra, name, (wcslen (name) + 1) * sizeof (wchar_t));
56
+ memcpy (name, long_prefix, extra * sizeof (wchar_t));
57
+ }
58
+ return handle;
59
+ }
60
+
61
+ static BOOL WINAPI
62
+ test_completion (HANDLE port, DWORD *bytes, ULONG_PTR *key,
63
+ OVERLAPPED **overlapped, DWORD timeout)
64
+ {
65
+ BOOL result = GetQueuedCompletionStatus (port, bytes, key, overlapped, timeout);
66
+ DWORD error = GetLastError ();
67
+ if (*overlapped && InterlockedExchange (&delay_completion, 0))
68
+ {
69
+ SetEvent (completion_entered);
70
+ WaitForSingleObject (completion_release, 5000);
71
+ }
72
+ SetLastError (error);
73
+ return result;
74
+ }
75
+
76
+ static SOCKET
77
+ test_socket (int af, int type, int protocol)
78
+ {
79
+ ++socket_calls;
80
+ if (fail_socket)
81
+ {
82
+ errno = EACCES;
83
+ WSASetLastError (WSAEACCES);
84
+ return INVALID_SOCKET;
85
+ }
86
+ return socket (af, type, protocol);
87
+ }
88
+
89
+ #define socket test_socket
90
+ #define GetQueuedCompletionStatus test_completion
91
+ #define CreateFileW test_create_file
92
+ #define GetFileInformationByHandle test_file_information
93
+ #define FindFirstFileExW test_find_file
94
+ #define EV_STANDALONE 1
95
+ #define EV_USE_REALTIME 0
96
+ #define EV_USE_MONOTONIC 0
97
+ #define EV_WIN32_STAT_CODEPAGE CP_UTF8
98
+ #include "ev.c"
99
+ #undef socket
100
+ #undef GetQueuedCompletionStatus
101
+ #undef CreateFileW
102
+ #undef GetFileInformationByHandle
103
+ #undef FindFirstFileExW
104
+
105
+ static void
106
+ recover_socket (const char *message)
107
+ {
108
+ ++syserr_calls;
109
+ fail_socket = 0;
110
+ }
111
+
112
+ static void
113
+ changed_cb (EV_P_ ev_stat *watcher, int events)
114
+ {
115
+ changed = watcher->attr.st_size == 5;
116
+ ev_break (EV_A_ EVBREAK_ALL);
117
+ }
118
+
119
+ static void
120
+ timeout_cb (EV_P_ ev_timer *watcher, int events)
121
+ {
122
+ timed_out = 1;
123
+ ev_break (EV_A_ EVBREAK_ALL);
124
+ }
125
+
126
+ static void async_cb (EV_P_ ev_async *watcher, int events) {}
127
+
128
+ static VALUE
129
+ stat_path (VALUE self, VALUE path, VALUE parent)
130
+ {
131
+ wchar_t *wide = win32_fs_path (StringValueCStr (path), RTEST (parent));
132
+ VALUE result;
133
+ if (!wide)
134
+ return Qnil;
135
+ result = rb_enc_str_new ((const char *)wide, wcslen (wide) * sizeof (wchar_t),
136
+ rb_enc_find ("UTF-16LE"));
137
+ free (wide);
138
+ return rb_str_export_to_enc (result, rb_utf8_encoding ());
139
+ }
140
+
141
+ static VALUE
142
+ check_pipe (VALUE self, VALUE path)
143
+ {
144
+ struct ev_loop *loop;
145
+ ev_stat watcher;
146
+ ev_timer timeout;
147
+ ev_async async;
148
+ ev_tstamp deadline;
149
+ WSADATA data;
150
+ FILE *file;
151
+ const char *error = 0;
152
+ const char *filename = StringValueCStr (path);
153
+ if (WSAStartup (MAKEWORD (2, 2), &data))
154
+ rb_raise (rb_eRuntimeError, "WSAStartup failed");
155
+ file = fopen (filename, "w");
156
+ if (!file)
157
+ { WSACleanup (); rb_sys_fail (filename); }
158
+ fputs ("a", file);
159
+ fclose (file);
160
+ loop = ev_loop_new (EVFLAG_NOENV | EVBACKEND_SELECT);
161
+ if (!loop)
162
+ { WSACleanup (); rb_raise (rb_eRuntimeError, "ev_loop_new failed"); }
163
+ ev_set_syserr_cb (recover_socket);
164
+ fail_socket = 1;
165
+ ev_stat_init (&watcher, changed_cb, filename, 0.1);
166
+ ev_stat_start (loop, &watcher);
167
+ if (loop->win32_fs || ev_is_active (&loop->pipe_w) || syserr_calls
168
+ || !ev_is_active (&watcher.timer))
169
+ { error = "pipe failure did not fall back to polling"; goto cleanup; }
170
+
171
+ /* With the default syserr handler, a failed retry must not abort either. */
172
+ ev_set_syserr_cb (0);
173
+ file = fopen (filename, "w");
174
+ if (!file)
175
+ { error = "could not update watched file"; goto cleanup; }
176
+ fputs ("final", file);
177
+ fclose (file);
178
+ ev_timer_init (&timeout, timeout_cb, 5, 0);
179
+ ev_timer_start (loop, &timeout);
180
+ ev_run (loop, 0);
181
+ ev_timer_stop (loop, &timeout);
182
+ if (!changed || timed_out || loop->win32_fs)
183
+ { error = "polling did not deliver the change"; goto cleanup; }
184
+
185
+ fail_socket = 0;
186
+ socket_calls = 0;
187
+ deadline = ev_time () + 5;
188
+ while (!loop->win32_fs && ev_time () < deadline)
189
+ {
190
+ ev_run (loop, EVRUN_ONCE); /* The next polling tick retries notification setup. */
191
+ }
192
+ /* One listener plus one client socket; ev_async_start must reuse this pair. */
193
+ if (!loop->win32_fs)
194
+ {
195
+ error = "polling did not retry notification setup";
196
+ }
197
+ else if (!ev_is_active (&loop->pipe_w) || socket_calls != 2)
198
+ {
199
+ error = "notification recovery did not reuse the prepared pipe";
200
+ }
201
+ cleanup:
202
+ ev_stat_stop (loop, &watcher);
203
+ ev_loop_destroy (loop);
204
+ if (error)
205
+ { WSACleanup (); rb_raise (rb_eRuntimeError, "%s", error); }
206
+
207
+ loop = ev_loop_new (EVFLAG_NOENV | EVBACKEND_SELECT);
208
+ if (!loop)
209
+ { WSACleanup (); rb_raise (rb_eRuntimeError, "ev_loop_new failed"); }
210
+ fail_socket = 1;
211
+ socket_calls = syserr_calls = 0;
212
+ ev_set_syserr_cb (recover_socket);
213
+ ev_async_init (&async, async_cb);
214
+ ev_async_start (loop, &async);
215
+ if (syserr_calls != 1 || socket_calls != 3 || !ev_is_active (&loop->pipe_w))
216
+ error = "existing async retry semantics changed";
217
+ ev_async_stop (loop, &async);
218
+ ev_loop_destroy (loop);
219
+ WSACleanup ();
220
+ if (error)
221
+ rb_raise (rb_eRuntimeError, "%s", error);
222
+ return Qtrue;
223
+ }
224
+
225
+ static VALUE
226
+ check_shutdown (VALUE self, VALUE path)
227
+ {
228
+ struct ev_loop *loop;
229
+ ev_stat watcher;
230
+ WSADATA data;
231
+ FILE *file;
232
+ HANDLE thread = 0;
233
+ DWORD started, elapsed, finished;
234
+ const char *filename = StringValueCStr (path);
235
+ const char *error = 0;
236
+ if (WSAStartup (MAKEWORD (2, 2), &data))
237
+ rb_raise (rb_eRuntimeError, "WSAStartup failed");
238
+ completion_entered = CreateEvent (0, TRUE, FALSE, 0);
239
+ completion_release = CreateEvent (0, TRUE, FALSE, 0);
240
+ loop = ev_loop_new (EVFLAG_NOENV | EVBACKEND_SELECT);
241
+ if (!loop || !completion_entered || !completion_release)
242
+ rb_raise (rb_eRuntimeError, "shutdown test setup failed");
243
+ file = fopen (filename, "w");
244
+ if (!file)
245
+ rb_sys_fail (filename);
246
+ fputs ("a", file);
247
+ fclose (file);
248
+ ev_stat_init (&watcher, changed_cb, filename, 60);
249
+ ev_stat_start (loop, &watcher);
250
+ if (!loop->win32_fs || !DuplicateHandle (GetCurrentProcess (), loop->win32_fs->thread,
251
+ GetCurrentProcess (), &thread, 0, FALSE, DUPLICATE_SAME_ACCESS))
252
+ { error = "notification worker did not start"; goto cleanup; }
253
+ InterlockedExchange (&delay_completion, 1);
254
+ file = fopen (filename, "w");
255
+ if (!file)
256
+ { error = "could not update shutdown test file"; goto cleanup; }
257
+ fputs ("final", file);
258
+ fclose (file);
259
+ if (WaitForSingleObject (completion_entered, 5000) != WAIT_OBJECT_0)
260
+ { error = "worker did not receive a completion"; goto cleanup; }
261
+ cleanup:
262
+ ev_stat_stop (loop, &watcher);
263
+ started = GetTickCount ();
264
+ ev_loop_destroy (loop);
265
+ elapsed = GetTickCount () - started;
266
+ SetEvent (completion_release);
267
+ finished = thread ? WaitForSingleObject (thread, 5000) : WAIT_OBJECT_0;
268
+ if (thread)
269
+ CloseHandle (thread);
270
+ CloseHandle (completion_entered);
271
+ CloseHandle (completion_release);
272
+ WSACleanup ();
273
+ if (error)
274
+ rb_raise (rb_eRuntimeError, "%s", error);
275
+ if (elapsed >= 1000 || finished != WAIT_OBJECT_0)
276
+ rb_raise (rb_eRuntimeError, "shutdown blocked or worker failed to exit");
277
+ return Qtrue;
278
+ }
279
+
280
+ static VALUE
281
+ notification_enabled (VALUE self, VALUE path, VALUE flags)
282
+ {
283
+ struct ev_loop *loop;
284
+ ev_stat watcher;
285
+ WSADATA data;
286
+ FILE *file;
287
+ int enabled;
288
+ const char *filename = StringValueCStr (path);
289
+ if (WSAStartup (MAKEWORD (2, 2), &data))
290
+ rb_raise (rb_eRuntimeError, "WSAStartup failed");
291
+ file = fopen (filename, "w");
292
+ if (!file)
293
+ { WSACleanup (); rb_sys_fail (filename); }
294
+ fputs ("a", file);
295
+ fclose (file);
296
+ loop = ev_loop_new (NUM2UINT (flags) | EVBACKEND_SELECT);
297
+ if (!loop)
298
+ { WSACleanup (); rb_raise (rb_eRuntimeError, "ev_loop_new failed"); }
299
+ ev_stat_init (&watcher, changed_cb, filename, 60);
300
+ ev_stat_start (loop, &watcher);
301
+ enabled = loop->win32_fs != 0;
302
+ ev_stat_stop (loop, &watcher);
303
+ ev_loop_destroy (loop);
304
+ WSACleanup ();
305
+ return enabled ? Qtrue : Qfalse;
306
+ }
307
+
308
+ static VALUE
309
+ path_work (VALUE self, VALUE directory, VALUE mode)
310
+ {
311
+ enum { count = 32 };
312
+ struct ev_loop *loop;
313
+ struct ev_win32_fs *ctx;
314
+ ev_stat watchers[count];
315
+ char paths[count][1024];
316
+ WSADATA data;
317
+ FILE *file;
318
+ VALUE result;
319
+ int i, valid = 1, notification = NUM2INT (mode) == 1;
320
+ const char *parent = StringValueCStr (directory);
321
+ if (WSAStartup (MAKEWORD (2, 2), &data))
322
+ rb_raise (rb_eRuntimeError, "WSAStartup failed");
323
+ /* Create everything before registration so setup cannot invalidate aliases. */
324
+ for (i = 0; i < count; ++i)
325
+ {
326
+ snprintf (paths[i], sizeof (paths[i]), "%s/work%d.txt", parent, i);
327
+ file = fopen (paths[i], "w");
328
+ if (!file)
329
+ rb_sys_fail (paths[i]);
330
+ fputs ("a", file);
331
+ fclose (file);
332
+ }
333
+ loop = ev_loop_new (EVFLAG_NOENV | EVBACKEND_SELECT);
334
+ if (!loop)
335
+ rb_raise (rb_eRuntimeError, "ev_loop_new failed");
336
+ for (i = 0; i < count; ++i)
337
+ {
338
+ ev_stat_init (&watchers[i], changed_cb, paths[i], 60);
339
+ ev_stat_start (loop, &watchers[i]);
340
+ }
341
+ ctx = loop->win32_fs;
342
+ if (!ctx)
343
+ rb_raise (rb_eRuntimeError, "notification worker did not start");
344
+ for (i = 0; i < count; ++i)
345
+ {
346
+ file = fopen (paths[i], "a");
347
+ if (!file)
348
+ rb_sys_fail (paths[i]);
349
+ fputs ("x", file);
350
+ fclose (file);
351
+ }
352
+ if (NUM2INT (mode) == 2)
353
+ {
354
+ struct ev_win32_dir event_dir;
355
+ FILE_NOTIFY_INFORMATION *event = (FILE_NOTIFY_INFORMATION *)event_dir.buffer;
356
+ const wchar_t name[] = L"unrelated.tmp";
357
+ memset (&event_dir, 0, sizeof (event_dir));
358
+ event->Action = FILE_ACTION_ADDED;
359
+ event->FileNameLength = sizeof (name) - sizeof (wchar_t);
360
+ memcpy (event->FileName, name, event->FileNameLength);
361
+ EnterCriticalSection (&ctx->lock);
362
+ memcpy (&event_dir, ctx->dirs, offsetof (struct ev_win32_dir, buffer));
363
+ win32_dir_notify (ctx, &event_dir,
364
+ offsetof (FILE_NOTIFY_INFORMATION, FileName) + event->FileNameLength);
365
+ LeaveCriticalSection (&ctx->lock);
366
+ }
367
+ if (notification)
368
+ {
369
+ /* Queue the whole changed set without depending on IOCP batch timing. */
370
+ EnterCriticalSection (&ctx->lock);
371
+ for (i = 0; i < ctx->watchcnt; ++i)
372
+ win32_watch_dirty (ctx, ctx->watches[i]);
373
+ ctx->dirs->dirty = 1;
374
+ LeaveCriticalSection (&ctx->lock);
375
+ }
376
+ directory_opens = identity_calls = name_calls = 0;
377
+ if (notification)
378
+ win32_fs_cb (loop, &ctx->async, EV_ASYNC);
379
+ else
380
+ for (i = 0; i < count; ++i)
381
+ stat_timer_cb (loop, &watchers[i].timer, EV_TIMER);
382
+ for (i = 0; i < count; ++i)
383
+ valid = valid && watchers[i].attr.st_size == 2;
384
+ result = rb_ary_new_from_args (4, INT2NUM (directory_opens), INT2NUM (identity_calls),
385
+ INT2NUM (name_calls), valid ? Qtrue : Qfalse);
386
+ for (i = 0; i < count; ++i)
387
+ ev_stat_stop (loop, &watchers[i]);
388
+ ev_loop_destroy (loop);
389
+ WSACleanup ();
390
+ return result;
391
+ }
392
+
393
+ static int
394
+ watch_index_consistent (struct ev_win32_fs *ctx, ev_stat *watchers, int count)
395
+ {
396
+ int i, active = 0;
397
+ for (i = 0; i < count; ++i)
398
+ {
399
+ struct ev_win32_watch *watch = win32_fs_watch (ctx, &watchers[i]);
400
+ if (ev_is_active (&watchers[i]))
401
+ {
402
+ if (!watch || watch->parent.dir->failed)
403
+ {
404
+ return 0;
405
+ }
406
+ ++active;
407
+ }
408
+ else if (watch)
409
+ {
410
+ return 0;
411
+ }
412
+ }
413
+ return ctx->watchcnt == active;
414
+ }
415
+
416
+ static void
417
+ poll_active (struct ev_loop *loop, ev_stat *watchers, int count)
418
+ {
419
+ int i;
420
+ for (i = 0; i < count; ++i)
421
+ {
422
+ if (ev_is_active (&watchers[i]))
423
+ {
424
+ stat_timer_cb (loop, &watchers[i].timer, EV_TIMER);
425
+ }
426
+ }
427
+ }
428
+
429
+ static VALUE
430
+ watch_index (VALUE self, VALUE directory)
431
+ {
432
+ enum { count = 4 };
433
+ struct ev_loop *loop;
434
+ struct ev_win32_fs *ctx;
435
+ ev_stat watchers[count];
436
+ char paths[count][1024];
437
+ WSADATA data;
438
+ FILE *file;
439
+ VALUE result;
440
+ int i, stopped, replaced;
441
+ const char *parent = StringValueCStr (directory);
442
+ if (WSAStartup (MAKEWORD (2, 2), &data))
443
+ {
444
+ rb_raise (rb_eRuntimeError, "WSAStartup failed");
445
+ }
446
+ for (i = 0; i < count; ++i)
447
+ {
448
+ snprintf (paths[i], sizeof (paths[i]), "%s/index%d.txt", parent, i);
449
+ file = fopen (paths[i], "w");
450
+ if (!file)
451
+ {
452
+ WSACleanup ();
453
+ rb_sys_fail (paths[i]);
454
+ }
455
+ fputs ("a", file);
456
+ fclose (file);
457
+ }
458
+ loop = ev_loop_new (EVFLAG_NOENV | EVBACKEND_SELECT);
459
+ if (!loop)
460
+ {
461
+ WSACleanup ();
462
+ rb_raise (rb_eRuntimeError, "ev_loop_new failed");
463
+ }
464
+ for (i = 0; i < count; ++i)
465
+ {
466
+ ev_stat_init (&watchers[i], changed_cb, paths[i], 60);
467
+ ev_stat_start (loop, &watchers[i]);
468
+ }
469
+ ctx = loop->win32_fs;
470
+ if (!ctx)
471
+ {
472
+ ev_loop_destroy (loop);
473
+ WSACleanup ();
474
+ rb_raise (rb_eRuntimeError, "notification worker did not start");
475
+ }
476
+ ev_stat_stop (loop, &watchers[1]);
477
+ directory_opens = identity_calls = name_calls = 0;
478
+ poll_active (loop, watchers, count);
479
+ result = rb_ary_new_from_args (3, INT2NUM (directory_opens), INT2NUM (identity_calls),
480
+ INT2NUM (name_calls));
481
+ stopped = watch_index_consistent (ctx, watchers, count);
482
+ ev_stat_stop (loop, ctx->watches[ctx->watchcnt - 1]->watcher);
483
+ ev_stat_start (loop, &watchers[1]);
484
+ ctx->watches[0]->parent.dir->failed = 1;
485
+ poll_active (loop, watchers, count);
486
+ replaced = watch_index_consistent (ctx, watchers, count);
487
+ rb_ary_push (result, stopped ? Qtrue : Qfalse);
488
+ rb_ary_push (result, replaced ? Qtrue : Qfalse);
489
+ for (i = 0; i < count; ++i)
490
+ {
491
+ ev_stat_stop (loop, &watchers[i]);
492
+ }
493
+ ev_loop_destroy (loop);
494
+ WSACleanup ();
495
+ return result;
496
+ }
497
+
498
+ static void
499
+ clear_dirty (struct ev_win32_fs *ctx)
500
+ {
501
+ struct ev_win32_watch *watch;
502
+ while ((watch = ctx->dirty))
503
+ {
504
+ ctx->dirty = watch->dirty_next;
505
+ if (ctx->dirty)
506
+ {
507
+ ctx->dirty->dirty_prev = &ctx->dirty;
508
+ }
509
+ watch->dirty = 0;
510
+ }
511
+ }
512
+
513
+ static void
514
+ notify_records (struct ev_win32_fs *ctx, struct ev_win32_dir *dir,
515
+ const wchar_t *const *names, int count, DWORD action)
516
+ {
517
+ struct ev_win32_dir event_dir;
518
+ char *record = (char *)event_dir.buffer;
519
+ int i;
520
+ memset (&event_dir, 0, sizeof (event_dir));
521
+ memcpy (&event_dir, dir, offsetof (struct ev_win32_dir, buffer));
522
+ for (i = 0; i < count; ++i)
523
+ {
524
+ FILE_NOTIFY_INFORMATION *event = (FILE_NOTIFY_INFORMATION *)record;
525
+ DWORD length = (DWORD)(wcslen (names[i]) * sizeof (wchar_t));
526
+ DWORD size = (DWORD)((offsetof (FILE_NOTIFY_INFORMATION, FileName) + length + 3) & ~3u);
527
+ event->Action = action;
528
+ event->FileNameLength = length;
529
+ memcpy (event->FileName, names[i], length);
530
+ event->NextEntryOffset = i + 1 < count ? size : 0;
531
+ record += size;
532
+ }
533
+ win32_dir_notify (ctx, &event_dir, (DWORD)(record - (char *)event_dir.buffer));
534
+ }
535
+
536
+ static void
537
+ notify_names (struct ev_win32_fs *ctx, struct ev_win32_dir *dir,
538
+ const wchar_t *const *names, int count)
539
+ {
540
+ notify_records (ctx, dir, names, count, FILE_ACTION_MODIFIED);
541
+ }
542
+
543
+ static VALUE
544
+ dirty_flags (const int *dirty, int count)
545
+ {
546
+ VALUE flags = rb_ary_new ();
547
+ int i;
548
+ for (i = 0; i < count; ++i)
549
+ {
550
+ rb_ary_push (flags, dirty[i] ? Qtrue : Qfalse);
551
+ }
552
+ return flags;
553
+ }
554
+
555
+ static void
556
+ make_dir (const char *parent, const char *relative)
557
+ {
558
+ char path[1024];
559
+ snprintf (path, sizeof (path), "%s/%s", parent, relative);
560
+ if (!CreateDirectoryA (path, 0) && GetLastError () != ERROR_ALREADY_EXISTS)
561
+ {
562
+ WSACleanup ();
563
+ rb_raise (rb_eRuntimeError, "CreateDirectoryA failed: %s", path);
564
+ }
565
+ }
566
+
567
+ static void
568
+ make_file (const char *parent, const char *relative)
569
+ {
570
+ char path[1024];
571
+ FILE *file;
572
+ snprintf (path, sizeof (path), "%s/%s", parent, relative);
573
+ file = fopen (path, "w");
574
+ if (!file)
575
+ {
576
+ WSACleanup ();
577
+ rb_sys_fail (path);
578
+ }
579
+ fputs ("a", file);
580
+ fclose (file);
581
+ }
582
+
583
+ static int
584
+ notified (struct ev_win32_fs *ctx, struct ev_win32_dir *dir, const wchar_t *name)
585
+ {
586
+ int dirty;
587
+ EnterCriticalSection (&ctx->lock);
588
+ clear_dirty (ctx);
589
+ notify_names (ctx, dir, &name, 1);
590
+ dirty = ctx->dirty != 0;
591
+ clear_dirty (ctx);
592
+ LeaveCriticalSection (&ctx->lock);
593
+ return dirty;
594
+ }
595
+
596
+ static VALUE
597
+ name_index (VALUE self, VALUE directory)
598
+ {
599
+ enum { count = 7 };
600
+ static const char *const files[] = { "a/x0.txt", "a/x1.txt", "a/x2.txt", "a/x3.txt", "b/y.txt" };
601
+ static const char *const watched[count] = {
602
+ "a/x0.txt", "A/X1.Txt", "a/x2.txt", "a/x3.txt", "b/y.txt", "a", "a/\xc3\x89.txt"
603
+ };
604
+ static const wchar_t *const records[] = {
605
+ L"", L"x1.txt", L"x2.txt", L"x3.txt", L"\u00e9.txt", L"unrelated.tmp"
606
+ };
607
+ struct ev_loop *loop;
608
+ struct ev_win32_fs *ctx;
609
+ struct ev_win32_dir *dir;
610
+ struct ev_win32_watch *watch;
611
+ ev_stat watchers[count];
612
+ char paths[count][1024];
613
+ WSADATA data;
614
+ VALUE result;
615
+ int i, shared, named[count], overflowed[count];
616
+ const char *parent = StringValueCStr (directory);
617
+ if (WSAStartup (MAKEWORD (2, 2), &data))
618
+ {
619
+ rb_raise (rb_eRuntimeError, "WSAStartup failed");
620
+ }
621
+ make_dir (parent, "a");
622
+ make_dir (parent, "b");
623
+ for (i = 0; i < (int)(sizeof (files) / sizeof (*files)); ++i)
624
+ {
625
+ make_file (parent, files[i]);
626
+ }
627
+ loop = ev_loop_new (EVFLAG_NOENV | EVBACKEND_SELECT);
628
+ if (!loop)
629
+ {
630
+ WSACleanup ();
631
+ rb_raise (rb_eRuntimeError, "ev_loop_new failed");
632
+ }
633
+ fail_alias = 1;
634
+ for (i = 0; i < count; ++i)
635
+ {
636
+ snprintf (paths[i], sizeof (paths[i]), "%s/%s", parent, watched[i]);
637
+ ev_stat_init (&watchers[i], changed_cb, paths[i], 60);
638
+ ev_stat_start (loop, &watchers[i]);
639
+ }
640
+ fail_alias = 0;
641
+ ctx = loop->win32_fs;
642
+ if (!ctx)
643
+ {
644
+ ev_loop_destroy (loop);
645
+ WSACleanup ();
646
+ rb_raise (rb_eRuntimeError, "notification worker did not start");
647
+ }
648
+ dir = win32_fs_watch (ctx, &watchers[0])->parent.dir;
649
+ shared = win32_fs_watch (ctx, &watchers[1])->parent.dir == dir
650
+ && win32_fs_watch (ctx, &watchers[2])->parent.dir == dir
651
+ && win32_fs_watch (ctx, &watchers[3])->parent.dir == dir
652
+ && win32_fs_watch (ctx, &watchers[4])->parent.dir != dir
653
+ && win32_fs_watch (ctx, &watchers[5])->children.dir == dir
654
+ && win32_fs_watch (ctx, &watchers[6])->parent.dir == dir;
655
+ ev_stat_stop (loop, &watchers[2]);
656
+ EnterCriticalSection (&ctx->lock);
657
+ clear_dirty (ctx);
658
+ notify_names (ctx, dir, records, sizeof (records) / sizeof (*records));
659
+ for (i = 0; i < count; ++i)
660
+ {
661
+ watch = win32_fs_watch (ctx, &watchers[i]);
662
+ named[i] = watch && watch->dirty;
663
+ }
664
+ clear_dirty (ctx);
665
+ notify_names (ctx, dir, records, 0);
666
+ for (i = 0; i < count; ++i)
667
+ {
668
+ watch = win32_fs_watch (ctx, &watchers[i]);
669
+ overflowed[i] = watch && watch->dirty;
670
+ }
671
+ clear_dirty (ctx);
672
+ LeaveCriticalSection (&ctx->lock);
673
+ result = rb_ary_new_from_args (3, shared ? Qtrue : Qfalse,
674
+ dirty_flags (named, count), dirty_flags (overflowed, count));
675
+ for (i = 0; i < count; ++i)
676
+ {
677
+ ev_stat_stop (loop, &watchers[i]);
678
+ }
679
+ ev_loop_destroy (loop);
680
+ WSACleanup ();
681
+ return result;
682
+ }
683
+
684
+ static VALUE
685
+ alias_index (VALUE self, VALUE directory)
686
+ {
687
+ struct ev_loop *loop;
688
+ struct ev_win32_fs *ctx;
689
+ struct ev_win32_dir *dir;
690
+ struct ev_win32_watch *watch;
691
+ ev_stat watched, keeper;
692
+ char watched_path[1024], keeper_path[1024];
693
+ WSADATA data;
694
+ int before, stale, renamed, plain, stopped;
695
+ const char *parent = StringValueCStr (directory);
696
+ if (WSAStartup (MAKEWORD (2, 2), &data))
697
+ {
698
+ rb_raise (rb_eRuntimeError, "WSAStartup failed");
699
+ }
700
+ make_dir (parent, "c");
701
+ make_file (parent, "c/z.txt");
702
+ make_file (parent, "c/keep.txt");
703
+ snprintf (watched_path, sizeof (watched_path), "%s/c/z.txt", parent);
704
+ snprintf (keeper_path, sizeof (keeper_path), "%s/c/keep.txt", parent);
705
+ loop = ev_loop_new (EVFLAG_NOENV | EVBACKEND_SELECT);
706
+ if (!loop)
707
+ {
708
+ WSACleanup ();
709
+ rb_raise (rb_eRuntimeError, "ev_loop_new failed");
710
+ }
711
+ long_prefix = L"long-";
712
+ ev_stat_init (&watched, changed_cb, watched_path, 60);
713
+ ev_stat_start (loop, &watched);
714
+ ev_stat_init (&keeper, changed_cb, keeper_path, 60);
715
+ ev_stat_start (loop, &keeper);
716
+ ctx = loop->win32_fs;
717
+ if (!ctx)
718
+ {
719
+ long_prefix = 0;
720
+ ev_loop_destroy (loop);
721
+ WSACleanup ();
722
+ rb_raise (rb_eRuntimeError, "notification worker did not start");
723
+ }
724
+ watch = win32_fs_watch (ctx, &watched);
725
+ dir = watch->parent.dir;
726
+ before = notified (ctx, dir, L"long-z.txt");
727
+ long_prefix = L"other-";
728
+ EnterCriticalSection (&ctx->lock);
729
+ win32_watch_names (watch);
730
+ LeaveCriticalSection (&ctx->lock);
731
+ long_prefix = 0;
732
+ stale = notified (ctx, dir, L"long-z.txt");
733
+ renamed = notified (ctx, dir, L"other-z.txt");
734
+ plain = notified (ctx, dir, L"z.txt");
735
+ ev_stat_stop (loop, &watched);
736
+ stopped = !notified (ctx, dir, L"other-z.txt") && !notified (ctx, dir, L"z.txt");
737
+ ev_stat_stop (loop, &keeper);
738
+ ev_loop_destroy (loop);
739
+ WSACleanup ();
740
+ return rb_ary_new_from_args (5, before ? Qtrue : Qfalse, stale ? Qtrue : Qfalse,
741
+ renamed ? Qtrue : Qfalse, plain ? Qtrue : Qfalse, stopped ? Qtrue : Qfalse);
742
+ }
743
+
744
+ static VALUE
745
+ index_growth (VALUE self, VALUE directory)
746
+ {
747
+ enum { names = 40, dirs = 20 };
748
+ struct ev_loop *loop;
749
+ struct ev_win32_fs *ctx;
750
+ struct ev_win32_dir *dir;
751
+ struct ev_win32_watch *watch;
752
+ ev_stat files[names], first[dirs], second[dirs];
753
+ char file_paths[names][1024], first_paths[dirs][1024], second_paths[dirs][1024], name[64];
754
+ wchar_t record_names[names][16];
755
+ const wchar_t *records[names];
756
+ WSADATA data;
757
+ int i, j, queued = 1, routed = 1, shared = 1, grown, count = 0;
758
+ const char *parent = StringValueCStr (directory);
759
+ if (WSAStartup (MAKEWORD (2, 2), &data))
760
+ {
761
+ rb_raise (rb_eRuntimeError, "WSAStartup failed");
762
+ }
763
+ make_dir (parent, "g");
764
+ for (i = 0; i < names; ++i)
765
+ {
766
+ snprintf (name, sizeof (name), "g/n%d.txt", i);
767
+ make_file (parent, name);
768
+ snprintf (file_paths[i], sizeof (file_paths[i]), "%s/%s", parent, name);
769
+ snprintf (name, sizeof (name), "n%d.txt", i);
770
+ for (j = 0; name[j]; ++j)
771
+ {
772
+ record_names[i][j] = (wchar_t)name[j];
773
+ }
774
+ record_names[i][j] = 0;
775
+ records[i] = record_names[i];
776
+ }
777
+ for (i = 0; i < dirs; ++i)
778
+ {
779
+ snprintf (name, sizeof (name), "h%d", i);
780
+ make_dir (parent, name);
781
+ snprintf (name, sizeof (name), "h%d/f.txt", i);
782
+ make_file (parent, name);
783
+ snprintf (first_paths[i], sizeof (first_paths[i]), "%s/h%d/f.txt", parent, i);
784
+ snprintf (second_paths[i], sizeof (second_paths[i]), "%s/H%d/F.TXT", parent, i);
785
+ }
786
+ loop = ev_loop_new (EVFLAG_NOENV | EVBACKEND_SELECT);
787
+ if (!loop)
788
+ {
789
+ WSACleanup ();
790
+ rb_raise (rb_eRuntimeError, "ev_loop_new failed");
791
+ }
792
+ fail_alias = 1;
793
+ for (i = 0; i < names; ++i)
794
+ {
795
+ ev_stat_init (&files[i], changed_cb, file_paths[i], 60);
796
+ ev_stat_start (loop, &files[i]);
797
+ }
798
+ for (i = 0; i < dirs; ++i)
799
+ {
800
+ ev_stat_init (&first[i], changed_cb, first_paths[i], 60);
801
+ ev_stat_start (loop, &first[i]);
802
+ ev_stat_init (&second[i], changed_cb, second_paths[i], 60);
803
+ ev_stat_start (loop, &second[i]);
804
+ }
805
+ fail_alias = 0;
806
+ ctx = loop->win32_fs;
807
+ if (!ctx)
808
+ {
809
+ ev_loop_destroy (loop);
810
+ WSACleanup ();
811
+ rb_raise (rb_eRuntimeError, "notification worker did not start");
812
+ }
813
+ for (i = 0; i < dirs; ++i)
814
+ {
815
+ shared = shared && win32_fs_watch (ctx, &first[i])->parent.dir
816
+ == win32_fs_watch (ctx, &second[i])->parent.dir;
817
+ }
818
+ dir = win32_fs_watch (ctx, &files[1])->parent.dir;
819
+ grown = dir->namemask > 15 && ctx->dirmask > 15;
820
+ EnterCriticalSection (&ctx->lock);
821
+ clear_dirty (ctx);
822
+ notify_names (ctx, dir, records, names);
823
+ LeaveCriticalSection (&ctx->lock);
824
+ for (i = 0; i < names; i += 3)
825
+ {
826
+ ev_stat_stop (loop, &files[i]);
827
+ }
828
+ EnterCriticalSection (&ctx->lock);
829
+ for (watch = ctx->dirty; watch; watch = watch->dirty_next)
830
+ {
831
+ queued = queued && watch->dirty && *watch->dirty_prev == watch;
832
+ if (watch->parent.dir == dir)
833
+ {
834
+ ++count;
835
+ }
836
+ }
837
+ queued = queued && count == names - (names + 2) / 3;
838
+ clear_dirty (ctx);
839
+ notify_names (ctx, dir, records, names);
840
+ for (i = 0; i < names; ++i)
841
+ {
842
+ watch = win32_fs_watch (ctx, &files[i]);
843
+ routed = routed && (i % 3 ? watch && watch->dirty : !watch);
844
+ }
845
+ clear_dirty (ctx);
846
+ LeaveCriticalSection (&ctx->lock);
847
+ for (i = 0; i < names; ++i)
848
+ {
849
+ ev_stat_stop (loop, &files[i]);
850
+ }
851
+ for (i = 0; i < dirs; ++i)
852
+ {
853
+ ev_stat_stop (loop, &first[i]);
854
+ ev_stat_stop (loop, &second[i]);
855
+ }
856
+ ev_loop_destroy (loop);
857
+ WSACleanup ();
858
+ return rb_ary_new_from_args (4, grown ? Qtrue : Qfalse, shared ? Qtrue : Qfalse,
859
+ queued ? Qtrue : Qfalse, routed ? Qtrue : Qfalse);
860
+ }
861
+
862
+ static wchar_t *
863
+ absolute_path (const char *path)
864
+ {
865
+ wchar_t *absolute = win32_fs_path (path, 0);
866
+ if (!absolute)
867
+ {
868
+ rb_raise (rb_eRuntimeError, "win32_fs_path failed: %s", path);
869
+ }
870
+ return absolute;
871
+ }
872
+
873
+ static int
874
+ lookup_skipped (wchar_t *path)
875
+ {
876
+ struct ev_win32_watch watch;
877
+ int skipped, i;
878
+ memset (&watch, 0, sizeof (watch));
879
+ watch.path = path;
880
+ watch.name = wcsrchr (path, L'\\') + 1;
881
+ name_calls = 0;
882
+ win32_watch_names (&watch);
883
+ skipped = !name_calls && !watch.keys[1].key && !watch.keys[2].key;
884
+ for (i = 1; i < 3; ++i)
885
+ {
886
+ free (watch.keys[i].key);
887
+ }
888
+ return skipped;
889
+ }
890
+
891
+ static VALUE
892
+ name_guards (VALUE self, VALUE directory)
893
+ {
894
+ static const wchar_t *const roots[] = {
895
+ L"C:\\", L"\\\\?\\C:", L"\\\\localhost\\share", L"\\\\localhost\\share\\",
896
+ L"\\\\?\\UNC\\localhost\\share"
897
+ };
898
+ wchar_t root[64], *path;
899
+ char pattern[1024];
900
+ VALUE result = rb_hash_new ();
901
+ int i, skipped = 1;
902
+ const char *parent = StringValueCStr (directory);
903
+ for (i = 0; i < (int)(sizeof (roots) / sizeof (*roots)); ++i)
904
+ {
905
+ wcscpy (root, roots[i]);
906
+ skipped = skipped && lookup_skipped (root);
907
+ }
908
+ rb_hash_aset (result, ID2SYM (rb_intern ("root")), skipped ? Qtrue : Qfalse);
909
+ make_dir (parent, "wild");
910
+ make_file (parent, "wild/x0.txt");
911
+ snprintf (pattern, sizeof (pattern), "%s/wild/x*.txt", parent);
912
+ path = absolute_path (pattern);
913
+ skipped = lookup_skipped (path);
914
+ free (path);
915
+ rb_hash_aset (result, ID2SYM (rb_intern ("wildcard")), skipped ? Qtrue : Qfalse);
916
+ return result;
917
+ }
918
+
919
+ static int
920
+ routed_to (struct ev_win32_fs *ctx, ev_stat *watcher, const wchar_t *name)
921
+ {
922
+ struct ev_win32_watch *watch = win32_fs_watch (ctx, watcher);
923
+ int dirty;
924
+ EnterCriticalSection (&ctx->lock);
925
+ clear_dirty (ctx);
926
+ notify_names (ctx, watch->parent.dir, &name, 1);
927
+ dirty = watch->dirty;
928
+ clear_dirty (ctx);
929
+ LeaveCriticalSection (&ctx->lock);
930
+ return dirty;
931
+ }
932
+
933
+ static VALUE
934
+ short_alias (VALUE self, VALUE directory)
935
+ {
936
+ struct ev_loop *loop;
937
+ struct ev_win32_fs *ctx;
938
+ ev_stat long_watcher, short_watcher;
939
+ char long_path[1024], short_path[1024];
940
+ wchar_t *path, *buffer, *short_name;
941
+ WSADATA data;
942
+ DWORD length, written;
943
+ VALUE result;
944
+ int prefix;
945
+ const char *parent = StringValueCStr (directory);
946
+ if (WSAStartup (MAKEWORD (2, 2), &data))
947
+ {
948
+ rb_raise (rb_eRuntimeError, "WSAStartup failed");
949
+ }
950
+ make_dir (parent, "short");
951
+ make_file (parent, "short/long-file-name.txt");
952
+ snprintf (long_path, sizeof (long_path), "%s/short/long-file-name.txt", parent);
953
+ path = absolute_path (long_path);
954
+ length = GetShortPathNameW (path, 0, 0);
955
+ buffer = length ? (wchar_t *)malloc (length * sizeof (wchar_t)) : 0;
956
+ written = buffer ? GetShortPathNameW (path, buffer, length) : 0;
957
+ free (path);
958
+ if (!written || written >= length)
959
+ {
960
+ free (buffer);
961
+ WSACleanup ();
962
+ rb_raise (rb_eRuntimeError, "GetShortPathNameW failed");
963
+ }
964
+ short_name = wcsrchr (buffer, L'\\') + 1;
965
+ if (!_wcsicmp (short_name, L"long-file-name.txt"))
966
+ {
967
+ free (buffer);
968
+ WSACleanup ();
969
+ return Qnil;
970
+ }
971
+ prefix = snprintf (short_path, sizeof (short_path), "%s/short/", parent);
972
+ if (!WideCharToMultiByte (CP_UTF8, 0, short_name, -1, short_path + prefix,
973
+ (int)sizeof (short_path) - prefix, 0, 0))
974
+ {
975
+ free (buffer);
976
+ WSACleanup ();
977
+ rb_raise (rb_eRuntimeError, "WideCharToMultiByte failed");
978
+ }
979
+ loop = ev_loop_new (EVFLAG_NOENV | EVBACKEND_SELECT);
980
+ if (!loop)
981
+ {
982
+ free (buffer);
983
+ WSACleanup ();
984
+ rb_raise (rb_eRuntimeError, "ev_loop_new failed");
985
+ }
986
+ ev_stat_init (&long_watcher, changed_cb, long_path, 60);
987
+ ev_stat_start (loop, &long_watcher);
988
+ ev_stat_init (&short_watcher, changed_cb, short_path, 60);
989
+ ev_stat_start (loop, &short_watcher);
990
+ ctx = loop->win32_fs;
991
+ if (!ctx)
992
+ {
993
+ free (buffer);
994
+ ev_loop_destroy (loop);
995
+ WSACleanup ();
996
+ rb_raise (rb_eRuntimeError, "notification worker did not start");
997
+ }
998
+ result = rb_hash_new ();
999
+ rb_hash_aset (result, ID2SYM (rb_intern ("short")),
1000
+ routed_to (ctx, &long_watcher, short_name) ? Qtrue : Qfalse);
1001
+ rb_hash_aset (result, ID2SYM (rb_intern ("long")),
1002
+ routed_to (ctx, &short_watcher, L"long-file-name.txt") ? Qtrue : Qfalse);
1003
+ ev_stat_stop (loop, &long_watcher);
1004
+ ev_stat_stop (loop, &short_watcher);
1005
+ ev_loop_destroy (loop);
1006
+ free (buffer);
1007
+ WSACleanup ();
1008
+ return result;
1009
+ }
1010
+
1011
+ static VALUE
1012
+ children_cwd (VALUE self, VALUE first, VALUE second, VALUE polling)
1013
+ {
1014
+ struct ev_loop *loop;
1015
+ struct ev_win32_fs *ctx;
1016
+ struct ev_win32_dir *shared_dir, *children_dir;
1017
+ ev_stat dir_watcher, file_watcher;
1018
+ WSADATA data;
1019
+ wchar_t original[MAX_PATH], *first_dir, *second_dir, *second_rel;
1020
+ VALUE file = rb_sprintf ("%s/rel/f.txt", StringValueCStr (first));
1021
+ VALUE moved_rel = rb_sprintf ("%s/rel", StringValueCStr (second));
1022
+ VALUE result;
1023
+ DWORD length;
1024
+ int shared, failed, moved, followed, cwd_changed;
1025
+ length = GetCurrentDirectoryW (MAX_PATH, original);
1026
+ if (!length || length >= MAX_PATH)
1027
+ {
1028
+ rb_raise (rb_eRuntimeError, "GetCurrentDirectoryW failed");
1029
+ }
1030
+ first_dir = absolute_path (StringValueCStr (first));
1031
+ second_dir = absolute_path (StringValueCStr (second));
1032
+ second_rel = absolute_path (StringValueCStr (moved_rel));
1033
+ if (!SetCurrentDirectoryW (first_dir) || WSAStartup (MAKEWORD (2, 2), &data))
1034
+ {
1035
+ SetCurrentDirectoryW (original);
1036
+ free (first_dir);
1037
+ free (second_dir);
1038
+ free (second_rel);
1039
+ rb_raise (rb_eRuntimeError, "could not prepare the current directory and Winsock");
1040
+ }
1041
+ loop = ev_loop_new (EVFLAG_NOENV | EVBACKEND_SELECT);
1042
+ if (!loop)
1043
+ {
1044
+ SetCurrentDirectoryW (original);
1045
+ WSACleanup ();
1046
+ free (first_dir);
1047
+ free (second_dir);
1048
+ free (second_rel);
1049
+ rb_raise (rb_eRuntimeError, "ev_loop_new failed");
1050
+ }
1051
+ ev_stat_init (&dir_watcher, changed_cb, "rel", 60);
1052
+ ev_stat_start (loop, &dir_watcher);
1053
+ ev_stat_init (&file_watcher, changed_cb, StringValueCStr (file), 60);
1054
+ ev_stat_start (loop, &file_watcher);
1055
+ ctx = loop->win32_fs;
1056
+ if (!ctx || !win32_fs_watch (ctx, &dir_watcher) || !win32_fs_watch (ctx, &file_watcher))
1057
+ {
1058
+ ev_stat_stop (loop, &dir_watcher);
1059
+ ev_stat_stop (loop, &file_watcher);
1060
+ ev_loop_destroy (loop);
1061
+ SetCurrentDirectoryW (original);
1062
+ WSACleanup ();
1063
+ free (first_dir);
1064
+ free (second_dir);
1065
+ free (second_rel);
1066
+ rb_raise (rb_eRuntimeError, "the watchers were not registered");
1067
+ }
1068
+ EnterCriticalSection (&ctx->lock);
1069
+ shared_dir = win32_fs_watch (ctx, &file_watcher)->parent.dir;
1070
+ shared = win32_fs_watch (ctx, &dir_watcher)->children.dir == shared_dir;
1071
+ LeaveCriticalSection (&ctx->lock);
1072
+ cwd_changed = SetCurrentDirectoryW (second_dir);
1073
+ if (NUM2INT (polling))
1074
+ {
1075
+ stat_timer_cb (loop, &dir_watcher.timer, EV_TIMER);
1076
+ }
1077
+ else
1078
+ {
1079
+ win32_fs_add (loop, &dir_watcher, 0);
1080
+ }
1081
+ EnterCriticalSection (&ctx->lock);
1082
+ failed = shared_dir->failed;
1083
+ children_dir = win32_fs_watch (ctx, &dir_watcher)->children.dir;
1084
+ followed = children_dir && !_wcsicmp (children_dir->path, second_rel);
1085
+ LeaveCriticalSection (&ctx->lock);
1086
+ stat_timer_cb (loop, &file_watcher.timer, EV_TIMER);
1087
+ EnterCriticalSection (&ctx->lock);
1088
+ moved = win32_fs_watch (ctx, &file_watcher)->parent.dir != shared_dir;
1089
+ LeaveCriticalSection (&ctx->lock);
1090
+ SetCurrentDirectoryW (original);
1091
+ ev_stat_stop (loop, &dir_watcher);
1092
+ ev_stat_stop (loop, &file_watcher);
1093
+ ev_loop_destroy (loop);
1094
+ WSACleanup ();
1095
+ free (first_dir);
1096
+ free (second_dir);
1097
+ free (second_rel);
1098
+ RB_GC_GUARD (file);
1099
+ RB_GC_GUARD (moved_rel);
1100
+ if (!cwd_changed)
1101
+ {
1102
+ rb_raise (rb_eRuntimeError, "could not change the current directory");
1103
+ }
1104
+ result = rb_hash_new ();
1105
+ rb_hash_aset (result, ID2SYM (rb_intern ("shared")), shared ? Qtrue : Qfalse);
1106
+ rb_hash_aset (result, ID2SYM (rb_intern ("failed")), failed ? Qtrue : Qfalse);
1107
+ rb_hash_aset (result, ID2SYM (rb_intern ("moved")), moved ? Qtrue : Qfalse);
1108
+ rb_hash_aset (result, ID2SYM (rb_intern ("followed")), followed ? Qtrue : Qfalse);
1109
+ return result;
1110
+ }
1111
+
1112
+ static int
1113
+ follows (struct ev_win32_fs *ctx, struct ev_win32_dir *dir, const wchar_t *path)
1114
+ {
1115
+ int same;
1116
+ EnterCriticalSection (&ctx->lock);
1117
+ same = dir && win32_dir_matches (dir, path);
1118
+ LeaveCriticalSection (&ctx->lock);
1119
+ return same;
1120
+ }
1121
+
1122
+ static VALUE
1123
+ replaced_dir (VALUE self, VALUE directory, VALUE mode)
1124
+ {
1125
+ static const wchar_t *const watched_name[] = { L"P" };
1126
+ struct ev_loop *loop;
1127
+ struct ev_win32_fs *ctx;
1128
+ struct ev_win32_watch *watch;
1129
+ ev_stat dir_watcher, file_watcher;
1130
+ WSADATA data;
1131
+ int kind = NUM2INT (mode);
1132
+ int with_file = kind != 2;
1133
+ const char *parent = StringValueCStr (directory);
1134
+ VALUE root = rb_sprintf ("replaced-%d", kind);
1135
+ VALUE watched_rel = rb_sprintf ("replaced-%d/P", kind);
1136
+ VALUE file_rel = rb_sprintf ("replaced-%d/P/f.txt", kind);
1137
+ VALUE watched = rb_sprintf ("%s/%s", parent, StringValueCStr (watched_rel));
1138
+ VALUE moved = rb_sprintf ("%s/replaced-%d/P.old", parent, kind);
1139
+ VALUE file = rb_sprintf ("%s/%s", parent, StringValueCStr (file_rel));
1140
+ VALUE result;
1141
+ wchar_t *watched_dir = absolute_path (StringValueCStr (watched));
1142
+ wchar_t *moved_dir = absolute_path (StringValueCStr (moved));
1143
+ int replaced, directory_followed, file_followed = 0;
1144
+ if (WSAStartup (MAKEWORD (2, 2), &data))
1145
+ {
1146
+ free (watched_dir);
1147
+ free (moved_dir);
1148
+ rb_raise (rb_eRuntimeError, "WSAStartup failed");
1149
+ }
1150
+ make_dir (parent, StringValueCStr (root));
1151
+ make_dir (parent, StringValueCStr (watched_rel));
1152
+ make_file (parent, StringValueCStr (file_rel));
1153
+ loop = ev_loop_new (EVFLAG_NOENV | EVBACKEND_SELECT);
1154
+ if (!loop)
1155
+ {
1156
+ free (watched_dir);
1157
+ free (moved_dir);
1158
+ WSACleanup ();
1159
+ rb_raise (rb_eRuntimeError, "ev_loop_new failed");
1160
+ }
1161
+ ev_stat_init (&dir_watcher, changed_cb, StringValueCStr (watched), 60);
1162
+ ev_stat_start (loop, &dir_watcher);
1163
+ ev_stat_init (&file_watcher, changed_cb, StringValueCStr (file), 60);
1164
+ if (with_file)
1165
+ {
1166
+ ev_stat_start (loop, &file_watcher);
1167
+ }
1168
+ ctx = loop->win32_fs;
1169
+ if (!ctx || !win32_fs_watch (ctx, &dir_watcher) || !win32_fs_watch (ctx, &dir_watcher)->children.dir
1170
+ || (with_file && !win32_fs_watch (ctx, &file_watcher)))
1171
+ {
1172
+ ev_stat_stop (loop, &dir_watcher);
1173
+ ev_stat_stop (loop, &file_watcher);
1174
+ ev_loop_destroy (loop);
1175
+ free (watched_dir);
1176
+ free (moved_dir);
1177
+ WSACleanup ();
1178
+ rb_raise (rb_eRuntimeError, "the watchers were not registered");
1179
+ }
1180
+ replaced = MoveFileW (watched_dir, moved_dir);
1181
+ if (replaced && kind == 2)
1182
+ {
1183
+ win32_fs_add (loop, &dir_watcher, 0);
1184
+ }
1185
+ replaced = replaced && CreateDirectoryW (watched_dir, 0);
1186
+ EnterCriticalSection (&ctx->lock);
1187
+ clear_dirty (ctx);
1188
+ watch = win32_fs_watch (ctx, &dir_watcher);
1189
+ if (kind == 1)
1190
+ {
1191
+ notify_records (ctx, watch->parent.dir, watched_name, 1, FILE_ACTION_RENAMED_OLD_NAME);
1192
+ }
1193
+ notify_records (ctx, watch->parent.dir, watched_name, 1, FILE_ACTION_ADDED);
1194
+ LeaveCriticalSection (&ctx->lock);
1195
+ win32_fs_cb (loop, &ctx->async, EV_ASYNC);
1196
+ directory_followed = follows (ctx, win32_fs_watch (ctx, &dir_watcher)->children.dir, watched_dir);
1197
+ if (with_file)
1198
+ {
1199
+ file_followed = follows (ctx, win32_fs_watch (ctx, &file_watcher)->parent.dir, watched_dir);
1200
+ }
1201
+ ev_stat_stop (loop, &dir_watcher);
1202
+ ev_stat_stop (loop, &file_watcher);
1203
+ ev_loop_destroy (loop);
1204
+ free (watched_dir);
1205
+ free (moved_dir);
1206
+ WSACleanup ();
1207
+ RB_GC_GUARD (root);
1208
+ RB_GC_GUARD (watched_rel);
1209
+ RB_GC_GUARD (file_rel);
1210
+ RB_GC_GUARD (watched);
1211
+ RB_GC_GUARD (moved);
1212
+ RB_GC_GUARD (file);
1213
+ if (!replaced)
1214
+ {
1215
+ rb_raise (rb_eRuntimeError, "could not replace the watched directory");
1216
+ }
1217
+ result = rb_hash_new ();
1218
+ rb_hash_aset (result, ID2SYM (rb_intern ("directory")), directory_followed ? Qtrue : Qfalse);
1219
+ rb_hash_aset (result, ID2SYM (rb_intern ("file")),
1220
+ with_file ? (file_followed ? Qtrue : Qfalse) : Qnil);
1221
+ return result;
1222
+ }
1223
+
1224
+ static VALUE
1225
+ alias_recovery (VALUE self, VALUE path)
1226
+ {
1227
+ struct ev_loop *loop;
1228
+ struct ev_win32_fs *ctx;
1229
+ struct ev_win32_watch *watch;
1230
+ struct ev_win32_dir event_dir;
1231
+ FILE_NOTIFY_INFORMATION *event = (FILE_NOTIFY_INFORMATION *)event_dir.buffer;
1232
+ ev_stat watcher;
1233
+ WSADATA data;
1234
+ FILE *file;
1235
+ size_t length;
1236
+ int initial, invalidated, retried, cached;
1237
+ const char *filename = StringValueCStr (path);
1238
+ if (WSAStartup (MAKEWORD (2, 2), &data))
1239
+ rb_raise (rb_eRuntimeError, "WSAStartup failed");
1240
+ file = fopen (filename, "w");
1241
+ if (!file)
1242
+ rb_sys_fail (filename);
1243
+ fputs ("a", file);
1244
+ fclose (file);
1245
+ loop = ev_loop_new (EVFLAG_NOENV | EVBACKEND_SELECT);
1246
+ if (!loop)
1247
+ rb_raise (rb_eRuntimeError, "ev_loop_new failed");
1248
+ ev_stat_init (&watcher, changed_cb, filename, 60);
1249
+ fail_alias = 1;
1250
+ ev_stat_start (loop, &watcher);
1251
+ ctx = loop->win32_fs;
1252
+ if (!ctx)
1253
+ rb_raise (rb_eRuntimeError, "notification worker did not start");
1254
+ watch = ctx->watches[0];
1255
+ initial = watch->names_failed;
1256
+ fail_alias = 0;
1257
+ stat_timer_cb (loop, &watcher.timer, EV_TIMER);
1258
+ initial = initial && !watch->names_failed;
1259
+
1260
+ /* A replacement name record must refresh aliases even with equal stat data.
1261
+ * Use a separate buffer: the real directory buffer belongs to pending I/O. */
1262
+ memset (&event_dir, 0, sizeof (event_dir));
1263
+ length = wcslen (watch->name) * sizeof (wchar_t);
1264
+ event->Action = FILE_ACTION_RENAMED_NEW_NAME;
1265
+ event->FileNameLength = (DWORD)length;
1266
+ memcpy (event->FileName, watch->name, length);
1267
+ EnterCriticalSection (&ctx->lock);
1268
+ memcpy (&event_dir, watch->parent.dir, offsetof (struct ev_win32_dir, buffer));
1269
+ win32_dir_notify (ctx, &event_dir, (DWORD)(offsetof (FILE_NOTIFY_INFORMATION, FileName) + length));
1270
+ LeaveCriticalSection (&ctx->lock);
1271
+ fail_alias = 1;
1272
+ win32_fs_cb (loop, &ctx->async, EV_ASYNC);
1273
+ invalidated = watch->names_failed;
1274
+ fail_alias = 0;
1275
+ stat_timer_cb (loop, &watcher.timer, EV_TIMER);
1276
+ retried = !watch->names_failed;
1277
+ name_calls = 0;
1278
+ stat_timer_cb (loop, &watcher.timer, EV_TIMER);
1279
+ cached = !name_calls;
1280
+ ev_stat_stop (loop, &watcher);
1281
+ ev_loop_destroy (loop);
1282
+ WSACleanup ();
1283
+ return rb_ary_new_from_args (4, initial ? Qtrue : Qfalse, invalidated ? Qtrue : Qfalse,
1284
+ retried ? Qtrue : Qfalse, cached ? Qtrue : Qfalse);
1285
+ }
1286
+
1287
+ RUBY_FUNC_EXPORTED void
1288
+ Init_coolio_stat_native (void)
1289
+ {
1290
+ VALUE module = rb_define_module ("CoolioStatNativeSpec");
1291
+ rb_define_singleton_method (module, "path", stat_path, 2);
1292
+ rb_define_singleton_method (module, "check_pipe", check_pipe, 1);
1293
+ rb_define_singleton_method (module, "check_shutdown", check_shutdown, 1);
1294
+ rb_define_singleton_method (module, "notification_enabled", notification_enabled, 2);
1295
+ rb_define_singleton_method (module, "path_work", path_work, 2);
1296
+ rb_define_singleton_method (module, "watch_index", watch_index, 1);
1297
+ rb_define_singleton_method (module, "name_index", name_index, 1);
1298
+ rb_define_singleton_method (module, "alias_index", alias_index, 1);
1299
+ rb_define_singleton_method (module, "name_guards", name_guards, 1);
1300
+ rb_define_singleton_method (module, "short_alias", short_alias, 1);
1301
+ rb_define_singleton_method (module, "index_growth", index_growth, 1);
1302
+ rb_define_singleton_method (module, "children_cwd", children_cwd, 3);
1303
+ rb_define_singleton_method (module, "replaced_dir", replaced_dir, 2);
1304
+ rb_define_singleton_method (module, "alias_recovery", alias_recovery, 1);
1305
+ }