cool.io 1.9.4 → 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,851 @@
1
+ /* Windows notification support for ev_stat. Included by ev.c.
2
+ * Copyright (C) 2026. Distributed under the same terms as ev.c.
3
+ *
4
+ * Only the loop thread touches ev_stat or runs callbacks. The IOCP thread
5
+ * rearms directory reads and publishes dirty flags under a critical section.
6
+ * Directory events (including overflow) are hints to re-stat, not callbacks.
7
+ */
8
+
9
+ struct ev_win32_subscription
10
+ {
11
+ struct ev_win32_subscription *next, **prev;
12
+ struct ev_win32_dir *dir;
13
+ struct ev_win32_watch *watch;
14
+ };
15
+
16
+ struct ev_win32_name
17
+ {
18
+ struct ev_win32_name *next;
19
+ struct ev_win32_watch *watch;
20
+ wchar_t *key;
21
+ size_t length;
22
+ unsigned int hash;
23
+ };
24
+
25
+ struct ev_win32_dir
26
+ {
27
+ struct ev_win32_dir *next, **prev, *hash_next;
28
+ HANDLE handle;
29
+ OVERLAPPED overlapped;
30
+ wchar_t *path;
31
+ unsigned int refs, hash, namecnt, namemask;
32
+ ev_tstamp checked_at;
33
+ int pending, retired, failed, dirty;
34
+ struct ev_win32_subscription *files, *children;
35
+ struct ev_win32_name **names;
36
+ DWORD buffer[4096]; /* DWORD aligned; below the network 64 KiB limit */
37
+ };
38
+
39
+ struct ev_win32_watch
40
+ {
41
+ struct ev_win32_watch *dirty_next, **dirty_prev;
42
+ struct ev_win32_subscription parent, children;
43
+ struct ev_win32_name keys[3];
44
+ wchar_t *path;
45
+ const wchar_t *name;
46
+ ev_stat *watcher;
47
+ int dirty, names_dirty, names_failed;
48
+ };
49
+
50
+ struct ev_win32_fs
51
+ {
52
+ HANDLE port, thread;
53
+ CRITICAL_SECTION lock;
54
+ ev_async async;
55
+ struct ev_win32_dir *dirs, **dirtab;
56
+ unsigned int dircnt, dirmask;
57
+ struct ev_win32_watch **watches;
58
+ int watchcnt, watchmax;
59
+ struct ev_win32_watch *dirty;
60
+ int stopping;
61
+ #if EV_MULTIPLICITY
62
+ struct ev_loop *owner;
63
+ #endif
64
+ };
65
+
66
+ static void win32_fs_add_locked (EV_P_ ev_stat *w, int polling);
67
+ static int win32_dir_matches (struct ev_win32_dir *dir, const wchar_t *parent);
68
+ static size_t win32_fs_root (const wchar_t *path);
69
+
70
+ static unsigned int
71
+ win32_hash (const wchar_t *key, size_t length)
72
+ {
73
+ unsigned int hash = 2166136261u;
74
+ size_t i;
75
+ for (i = 0; i < length; ++i)
76
+ hash = (hash ^ (key[i] >= L'a' && key[i] <= L'z' ? key[i] - (L'a' - L'A') : key[i])) * 16777619u;
77
+ return hash;
78
+ }
79
+
80
+ static int
81
+ win32_fold (const wchar_t *name, size_t length, wchar_t *key)
82
+ {
83
+ return length && LCMapStringEx (LOCALE_NAME_INVARIANT, LCMAP_UPPERCASE, name, (int)length,
84
+ key, (int)length, 0, 0, 0) == (int)length;
85
+ }
86
+
87
+ static void
88
+ win32_name_link (struct ev_win32_dir *dir, struct ev_win32_name *node)
89
+ {
90
+ struct ev_win32_name **bucket = &dir->names[node->hash & dir->namemask];
91
+ node->next = *bucket;
92
+ *bucket = node;
93
+ if (++dir->namecnt > dir->namemask + 1)
94
+ {
95
+ unsigned int mask = dir->namemask * 2 + 1, i;
96
+ struct ev_win32_name **names = (struct ev_win32_name **)calloc (mask + 1, sizeof (*names));
97
+ if (!names)
98
+ return;
99
+ for (i = 0; i <= dir->namemask; ++i)
100
+ while ((node = dir->names[i]))
101
+ {
102
+ dir->names[i] = node->next;
103
+ node->next = names[node->hash & mask];
104
+ names[node->hash & mask] = node;
105
+ }
106
+ free (dir->names);
107
+ dir->names = names;
108
+ dir->namemask = mask;
109
+ }
110
+ }
111
+
112
+ static void
113
+ win32_name_unlink (struct ev_win32_dir *dir, struct ev_win32_name *node)
114
+ {
115
+ struct ev_win32_name **link = &dir->names[node->hash & dir->namemask];
116
+ while (*link != node)
117
+ link = &(*link)->next;
118
+ *link = node->next;
119
+ --dir->namecnt;
120
+ }
121
+
122
+ static int
123
+ win32_name_key (struct ev_win32_watch *watch, int index, const wchar_t *name)
124
+ {
125
+ struct ev_win32_name *node = &watch->keys[index];
126
+ int i;
127
+ node->watch = watch;
128
+ node->length = name ? wcslen (name) : 0;
129
+ node->key = node->length ? (wchar_t *)malloc (node->length * sizeof (wchar_t)) : 0;
130
+ if (!node->key)
131
+ return !node->length;
132
+ if (!win32_fold (name, node->length, node->key))
133
+ { free (node->key); node->key = 0; return 0; }
134
+ node->hash = win32_hash (node->key, node->length);
135
+ for (i = 0; i < index; ++i)
136
+ if (watch->keys[i].key && watch->keys[i].length == node->length
137
+ && !memcmp (watch->keys[i].key, node->key, node->length * sizeof (wchar_t)))
138
+ {
139
+ free (node->key);
140
+ node->key = 0;
141
+ break;
142
+ }
143
+ return 1;
144
+ }
145
+
146
+ static void
147
+ win32_watch_names (struct ev_win32_watch *watch)
148
+ {
149
+ struct ev_win32_dir *dir = watch->parent.dir;
150
+ WIN32_FIND_DATAW data;
151
+ HANDLE handle;
152
+ int i, keyed = 1;
153
+ for (i = 1; i < 3; ++i)
154
+ if (watch->keys[i].key)
155
+ {
156
+ if (dir)
157
+ win32_name_unlink (dir, &watch->keys[i]);
158
+ free (watch->keys[i].key);
159
+ watch->keys[i].key = 0;
160
+ }
161
+ watch->names_dirty = 0;
162
+ if (win32_fs_root (watch->path) < wcslen (watch->path) && !wcspbrk (watch->name, L"*?<>\""))
163
+ {
164
+ handle = FindFirstFileExW (watch->path, FindExInfoStandard, &data, FindExSearchNameMatch, 0, 0);
165
+ if (handle == INVALID_HANDLE_VALUE)
166
+ keyed = 0;
167
+ else
168
+ {
169
+ FindClose (handle);
170
+ keyed = win32_name_key (watch, 1, *data.cAlternateFileName ? data.cAlternateFileName : 0);
171
+ keyed &= win32_name_key (watch, 2, data.cFileName);
172
+ }
173
+ }
174
+ if (dir)
175
+ for (i = 1; i < 3; ++i)
176
+ if (watch->keys[i].key)
177
+ win32_name_link (dir, &watch->keys[i]);
178
+ watch->names_failed = !keyed;
179
+ }
180
+
181
+ /* Called with ctx->lock held. Queue each watcher at most once per batch. */
182
+ static void
183
+ win32_watch_dirty (struct ev_win32_fs *ctx, struct ev_win32_watch *watch)
184
+ {
185
+ if (!watch->dirty)
186
+ {
187
+ watch->dirty = 1;
188
+ watch->dirty_next = ctx->dirty;
189
+ watch->dirty_prev = &ctx->dirty;
190
+ if (ctx->dirty)
191
+ ctx->dirty->dirty_prev = &watch->dirty_next;
192
+ ctx->dirty = watch;
193
+ }
194
+ }
195
+
196
+ /* Name records only select paths to re-stat. Missing or malformed records
197
+ * (including buffer overflow) invalidate every subscriber to this directory. */
198
+ static void
199
+ win32_dir_notify (struct ev_win32_fs *ctx, struct ev_win32_dir *dir, DWORD bytes)
200
+ {
201
+ struct ev_win32_subscription *sub;
202
+ struct ev_win32_name *node;
203
+ wchar_t key[MAX_PATH];
204
+ DWORD offset = 0;
205
+ dir->dirty = 1;
206
+ for (sub = dir->children; sub; sub = sub->next)
207
+ win32_watch_dirty (ctx, sub->watch);
208
+ while (bytes && offset < bytes)
209
+ {
210
+ FILE_NOTIFY_INFORMATION *event = (FILE_NOTIFY_INFORMATION *)((char *)dir->buffer + offset);
211
+ DWORD remaining = bytes - offset;
212
+ DWORD header = offsetof (FILE_NOTIFY_INFORMATION, FileName);
213
+ size_t length;
214
+ unsigned int hash;
215
+ if (remaining < header || event->FileNameLength % sizeof (wchar_t)
216
+ || event->FileNameLength > remaining - header)
217
+ break;
218
+ length = event->FileNameLength / sizeof (wchar_t);
219
+ if (length && dir->namecnt)
220
+ {
221
+ if (length > MAX_PATH || !win32_fold (event->FileName, length, key))
222
+ break;
223
+ hash = win32_hash (key, length);
224
+ for (node = dir->names[hash & dir->namemask]; node; node = node->next)
225
+ if (node->hash == hash && node->length == length
226
+ && !memcmp (node->key, key, length * sizeof (wchar_t)))
227
+ {
228
+ /* Replacement can change aliases even with equal stat data. */
229
+ if (event->Action != FILE_ACTION_MODIFIED)
230
+ {
231
+ node->watch->names_dirty = 1;
232
+ if (node->watch->children.dir)
233
+ node->watch->children.dir->dirty = 1;
234
+ }
235
+ win32_watch_dirty (ctx, node->watch);
236
+ }
237
+ }
238
+ if (!event->NextEntryOffset)
239
+ return;
240
+ if (event->NextEntryOffset < header + event->FileNameLength
241
+ || event->NextEntryOffset >= remaining || event->NextEntryOffset % sizeof (DWORD))
242
+ break;
243
+ offset += event->NextEntryOffset;
244
+ }
245
+ for (sub = dir->files; sub; sub = sub->next)
246
+ {
247
+ sub->watch->names_dirty = 1; /* Overflow may have hidden a replacement. */
248
+ if (sub->watch->children.dir)
249
+ sub->watch->children.dir->dirty = 1;
250
+ win32_watch_dirty (ctx, sub->watch);
251
+ }
252
+ for (sub = dir->children; sub; sub = sub->next)
253
+ sub->watch->names_dirty = 1;
254
+ }
255
+
256
+ static int
257
+ win32_dir_read (struct ev_win32_dir *dir)
258
+ {
259
+ memset (&dir->overlapped, 0, sizeof (dir->overlapped));
260
+ dir->pending = ReadDirectoryChangesW (dir->handle, dir->buffer, sizeof (dir->buffer), FALSE,
261
+ FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME
262
+ | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE
263
+ | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_ACCESS
264
+ | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SECURITY,
265
+ 0, &dir->overlapped, 0) != 0;
266
+ return dir->pending;
267
+ }
268
+
269
+ /* Must hold ctx->lock. A retired directory is freed only after its completion
270
+ * packet has been dequeued: closing/cancelling a HANDLE alone is not enough. */
271
+ static void
272
+ win32_dir_free (struct ev_win32_fs *ctx, struct ev_win32_dir *dir)
273
+ {
274
+ struct ev_win32_dir **link = &ctx->dirtab[dir->hash & ctx->dirmask];
275
+ while (*link != dir)
276
+ link = &(*link)->hash_next;
277
+ *link = dir->hash_next;
278
+ --ctx->dircnt;
279
+ *dir->prev = dir->next;
280
+ if (dir->next)
281
+ dir->next->prev = dir->prev;
282
+ free (dir->names);
283
+ free (dir->path);
284
+ free (dir);
285
+ }
286
+
287
+ static void
288
+ win32_dir_retire (struct ev_win32_fs *ctx, struct ev_win32_dir *dir)
289
+ {
290
+ dir->retired = 1;
291
+ CancelIoEx (dir->handle, &dir->overlapped);
292
+ CloseHandle (dir->handle);
293
+ if (!dir->pending)
294
+ win32_dir_free (ctx, dir);
295
+ }
296
+
297
+ static unsigned int __stdcall
298
+ win32_fs_thread (void *arg)
299
+ {
300
+ struct ev_win32_fs *ctx = (struct ev_win32_fs *)arg;
301
+ for (;;)
302
+ {
303
+ DWORD bytes;
304
+ ULONG_PTR key;
305
+ OVERLAPPED *overlapped;
306
+ BOOL ok = GetQueuedCompletionStatus (ctx->port, &bytes, &key, &overlapped, INFINITE);
307
+ DWORD error = ok ? ERROR_SUCCESS : GetLastError ();
308
+
309
+ EnterCriticalSection (&ctx->lock);
310
+ if (overlapped)
311
+ {
312
+ struct ev_win32_dir *dir = (struct ev_win32_dir *)key;
313
+ dir->pending = 0;
314
+ if (dir->retired)
315
+ win32_dir_free (ctx, dir);
316
+ else
317
+ {
318
+ win32_dir_notify (ctx, dir, ok && bytes <= sizeof (dir->buffer) ? bytes : 0);
319
+
320
+ if ((!ok && error != ERROR_NOTIFY_ENUM_DIR) || !win32_dir_read (dir))
321
+ {
322
+ dir->failed = 1;
323
+ win32_dir_notify (ctx, dir, 0);
324
+ }
325
+
326
+ #if EV_MULTIPLICITY
327
+ ev_async_send (ctx->owner, &ctx->async);
328
+ #else
329
+ ev_async_send (&ctx->async);
330
+ #endif
331
+ }
332
+ }
333
+
334
+ if (ctx->stopping && !ctx->dirs)
335
+ {
336
+ LeaveCriticalSection (&ctx->lock);
337
+ CloseHandle (ctx->thread);
338
+ CloseHandle (ctx->port);
339
+ DeleteCriticalSection (&ctx->lock);
340
+ free (ctx->dirtab);
341
+ free (ctx);
342
+ return 0;
343
+ }
344
+ LeaveCriticalSection (&ctx->lock);
345
+ }
346
+ }
347
+
348
+ static void
349
+ win32_fs_cb (EV_P_ ev_async *async, int revents)
350
+ {
351
+ struct ev_win32_fs *ctx = (struct ev_win32_fs *)async->data;
352
+ struct ev_win32_dir *dir;
353
+ struct ev_win32_watch *watch;
354
+ struct win32_stat_batch
355
+ {
356
+ ev_stat *watcher;
357
+ int refresh;
358
+ } *batch;
359
+ size_t count = 0, i = 0;
360
+ int refresh;
361
+
362
+ EnterCriticalSection (&ctx->lock);
363
+ /* Check identity once per directory per batch, rather than once for every
364
+ * subscriber. A HANDLE can still refer to a directory moved elsewhere. */
365
+ for (dir = ctx->dirs; dir; dir = dir->next)
366
+ if (dir->dirty && !dir->retired)
367
+ {
368
+ dir->dirty = 0;
369
+ if (dir->failed || !win32_dir_matches (dir, dir->path))
370
+ {
371
+ dir->failed = 1;
372
+ win32_dir_notify (ctx, dir, 0);
373
+ }
374
+ }
375
+ for (watch = ctx->dirty; watch; watch = watch->dirty_next)
376
+ ++count;
377
+ batch = count ? (struct win32_stat_batch *)malloc (count * sizeof (*batch)) : 0;
378
+ if (batch)
379
+ while ((watch = ctx->dirty))
380
+ {
381
+ ctx->dirty = watch->dirty_next;
382
+ if (ctx->dirty)
383
+ ctx->dirty->dirty_prev = &ctx->dirty;
384
+ watch->dirty = 0;
385
+ refresh = watch->parent.dir->failed
386
+ || (watch->children.dir && watch->children.dir->failed)
387
+ || (watch->names_dirty && !watch->children.dir && watch->watcher->attr.st_nlink
388
+ && (watch->watcher->attr.st_mode & S_IFMT) == S_IFDIR);
389
+ if (watch->names_dirty && !refresh)
390
+ win32_watch_names (watch);
391
+ batch[i].watcher = watch->watcher;
392
+ batch[i++].refresh = refresh;
393
+ }
394
+ LeaveCriticalSection (&ctx->lock);
395
+
396
+ /* stat_timer_cb only queues user callbacks, so these ev_stat pointers stay
397
+ * valid for this batch. Notifications arriving meanwhile form the next one. */
398
+ if (batch)
399
+ for (i = 0; i < count; ++i)
400
+ {
401
+ if (batch[i].refresh)
402
+ {
403
+ EnterCriticalSection (&ctx->lock);
404
+ win32_fs_add_locked (EV_A_ batch[i].watcher, 0);
405
+ LeaveCriticalSection (&ctx->lock);
406
+ }
407
+ stat_timer_cb (EV_A_ &batch[i].watcher->timer, EV_STAT);
408
+ }
409
+ free (batch);
410
+ }
411
+
412
+ static int
413
+ win32_fs_init (EV_P)
414
+ {
415
+ struct ev_win32_fs *ctx;
416
+ if (win32_fs)
417
+ return 1;
418
+ if (win32_fs_disabled)
419
+ return 0;
420
+
421
+ ctx = (struct ev_win32_fs *)calloc (1, sizeof (*ctx));
422
+ if (!ctx)
423
+ return 0;
424
+ ctx->port = CreateIoCompletionPort (INVALID_HANDLE_VALUE, 0, 0, 1);
425
+ if (!ctx->port)
426
+ { free (ctx); return 0; }
427
+ /* Prepare the same pipe ev_async_start will use, with a recoverable failure
428
+ * path for optional notifications. Existing async/signal callers still retry. */
429
+ if (!evpipe_init_try (EV_A))
430
+ { CloseHandle (ctx->port); free (ctx); return 0; }
431
+ InitializeCriticalSection (&ctx->lock);
432
+ #if EV_MULTIPLICITY
433
+ ctx->owner = EV_A;
434
+ #endif
435
+ ev_async_init (&ctx->async, win32_fs_cb);
436
+ ctx->async.data = ctx;
437
+ ev_async_start (EV_A_ &ctx->async);
438
+ ev_unref (EV_A); /* internal watcher must not keep the loop running */
439
+ ctx->thread = (HANDLE)_beginthreadex (0, 0, win32_fs_thread, ctx, 0, 0);
440
+ if (!ctx->thread)
441
+ {
442
+ ev_ref (EV_A);
443
+ ev_async_stop (EV_A_ &ctx->async);
444
+ DeleteCriticalSection (&ctx->lock);
445
+ CloseHandle (ctx->port);
446
+ free (ctx);
447
+ return 0;
448
+ }
449
+ win32_fs = ctx;
450
+ return 1;
451
+ }
452
+
453
+ /* Include the separator in drive, UNC share and extended-length roots. */
454
+ static size_t
455
+ win32_fs_root (const wchar_t *path)
456
+ {
457
+ const wchar_t *start = path, *slash;
458
+ if (!wcsncmp (start, L"\\\\?\\", 4))
459
+ {
460
+ start += 4;
461
+ if (!_wcsnicmp (start, L"UNC\\", 4))
462
+ start += 4;
463
+ else
464
+ {
465
+ slash = wcschr (start, L'\\');
466
+ return slash ? slash - path + 1 : wcslen (path);
467
+ }
468
+ }
469
+ else if (start[0] == L'\\' && start[1] == L'\\')
470
+ start += 2;
471
+ else
472
+ return start[0] && start[1] == L':' && start[2] == L'\\' ? 3 : 0;
473
+
474
+ slash = wcschr (start, L'\\');
475
+ if (slash)
476
+ slash = wcschr (slash + 1, L'\\');
477
+ return slash ? slash - path + 1 : wcslen (path);
478
+ }
479
+
480
+ /* Snapshot an absolute path at registration. The configured codepage matches
481
+ * the stat wrapper (UTF-8 for Ruby); all HANDLE operations are wide. */
482
+ static wchar_t *
483
+ win32_fs_path (const char *path, int parent)
484
+ {
485
+ wchar_t *input, *absolute, *slash;
486
+ int length = MultiByteToWideChar (EV_WIN32_STAT_CODEPAGE, 0, path, -1, 0, 0);
487
+ DWORD needed, written;
488
+ size_t root;
489
+ if (!length)
490
+ return 0;
491
+ input = (wchar_t *)malloc (length * sizeof (wchar_t));
492
+ if (!input)
493
+ return 0;
494
+ MultiByteToWideChar (EV_WIN32_STAT_CODEPAGE, 0, path, -1, input, length);
495
+ needed = GetFullPathNameW (input, 0, 0, 0);
496
+ absolute = needed ? (wchar_t *)malloc (needed * sizeof (wchar_t)) : 0;
497
+ written = absolute ? GetFullPathNameW (input, needed, absolute, 0) : 0;
498
+ if (!written || written >= needed)
499
+ { free (input); free (absolute); return 0; }
500
+ free (input);
501
+ root = win32_fs_root (absolute);
502
+ length = (int)wcslen (absolute);
503
+ while ((size_t)length > root && (absolute[length - 1] == L'\\' || absolute[length - 1] == L'/'))
504
+ absolute[--length] = 0;
505
+ if (!parent || (size_t)length <= root)
506
+ return absolute;
507
+ slash = wcsrchr (absolute, L'\\');
508
+ if (!slash)
509
+ { free (absolute); return 0; }
510
+ if ((size_t)(slash - absolute) < root)
511
+ absolute[root] = 0;
512
+ else
513
+ *slash = 0;
514
+ return absolute;
515
+ }
516
+
517
+ static void
518
+ win32_subscription_add (struct ev_win32_subscription *sub, struct ev_win32_watch *watch,
519
+ struct ev_win32_dir *dir)
520
+ {
521
+ struct ev_win32_subscription **head;
522
+ int i;
523
+ sub->dir = dir;
524
+ sub->watch = watch;
525
+ if (!dir)
526
+ return;
527
+ head = sub == &watch->children ? &dir->children : &dir->files;
528
+ sub->next = *head;
529
+ sub->prev = head;
530
+ if (*head)
531
+ (*head)->prev = &sub->next;
532
+ *head = sub;
533
+ if (sub == &watch->parent)
534
+ for (i = 0; i < 3; ++i)
535
+ if (watch->keys[i].key)
536
+ win32_name_link (dir, &watch->keys[i]);
537
+ }
538
+
539
+ static void
540
+ win32_subscription_del (struct ev_win32_fs *ctx, struct ev_win32_subscription *sub)
541
+ {
542
+ struct ev_win32_dir *dir = sub->dir;
543
+ int i;
544
+ if (!dir)
545
+ return;
546
+ if (sub == &sub->watch->parent)
547
+ for (i = 0; i < 3; ++i)
548
+ if (sub->watch->keys[i].key)
549
+ win32_name_unlink (dir, &sub->watch->keys[i]);
550
+ *sub->prev = sub->next;
551
+ if (sub->next)
552
+ sub->next->prev = sub->prev;
553
+ if (!--dir->refs)
554
+ win32_dir_retire (ctx, dir);
555
+ }
556
+
557
+ static struct ev_win32_watch *
558
+ win32_fs_watch (struct ev_win32_fs *ctx, ev_stat *w)
559
+ {
560
+ if (w->wd >= 0 && w->wd < ctx->watchcnt && ctx->watches[w->wd]->watcher == w)
561
+ return ctx->watches[w->wd];
562
+ return 0;
563
+ }
564
+
565
+ static void
566
+ win32_watch_free (struct ev_win32_fs *ctx, struct ev_win32_watch *watch)
567
+ {
568
+ int i;
569
+ if (watch->dirty)
570
+ {
571
+ *watch->dirty_prev = watch->dirty_next;
572
+ if (watch->dirty_next)
573
+ watch->dirty_next->dirty_prev = watch->dirty_prev;
574
+ }
575
+ win32_subscription_del (ctx, &watch->parent);
576
+ win32_subscription_del (ctx, &watch->children);
577
+ for (i = 0; i < 3; ++i)
578
+ free (watch->keys[i].key);
579
+ free (watch->path);
580
+ free (watch);
581
+ }
582
+
583
+ /* A directory HANDLE follows renames. Periodically verify that it still names
584
+ * the directory at the watched path, including a changed current directory. */
585
+ static int
586
+ win32_dir_matches (struct ev_win32_dir *dir, const wchar_t *parent)
587
+ {
588
+ BY_HANDLE_FILE_INFORMATION previous, current;
589
+ HANDLE handle;
590
+ int same;
591
+ if (_wcsicmp (dir->path, parent))
592
+ return 0;
593
+ handle = CreateFileW (parent, FILE_READ_ATTRIBUTES,
594
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING,
595
+ FILE_FLAG_BACKUP_SEMANTICS, 0);
596
+ if (handle == INVALID_HANDLE_VALUE)
597
+ return 0;
598
+ same = GetFileInformationByHandle (dir->handle, &previous)
599
+ && GetFileInformationByHandle (handle, &current)
600
+ && previous.dwVolumeSerialNumber == current.dwVolumeSerialNumber
601
+ && previous.nFileIndexHigh == current.nFileIndexHigh
602
+ && previous.nFileIndexLow == current.nFileIndexLow;
603
+ CloseHandle (handle);
604
+ return same;
605
+ }
606
+
607
+ static void
608
+ win32_dirtab_grow (struct ev_win32_fs *ctx)
609
+ {
610
+ unsigned int mask = ctx->dirmask * 2 + 1, i;
611
+ struct ev_win32_dir *dir, **table = (struct ev_win32_dir **)calloc (mask + 1, sizeof (*table));
612
+ if (!table)
613
+ return;
614
+ for (i = 0; i <= ctx->dirmask; ++i)
615
+ while ((dir = ctx->dirtab[i]))
616
+ {
617
+ ctx->dirtab[i] = dir->hash_next;
618
+ dir->hash_next = table[dir->hash & mask];
619
+ table[dir->hash & mask] = dir;
620
+ }
621
+ free (ctx->dirtab);
622
+ ctx->dirtab = table;
623
+ ctx->dirmask = mask;
624
+ }
625
+
626
+ /* Consumes path on success or failure. All subscriptions to a directory share
627
+ * one non-recursive read; directory watchers also subscribe to their own path. */
628
+ static struct ev_win32_dir *
629
+ win32_dir_acquire (struct ev_win32_fs *ctx, wchar_t *path)
630
+ {
631
+ struct ev_win32_dir *dir;
632
+ unsigned int hash;
633
+ if (!path)
634
+ return 0;
635
+ if (!ctx->dirtab)
636
+ {
637
+ ctx->dirtab = (struct ev_win32_dir **)calloc (16, sizeof (*ctx->dirtab));
638
+ if (!ctx->dirtab)
639
+ goto fail;
640
+ ctx->dirmask = 15;
641
+ }
642
+ hash = win32_hash (path, wcslen (path));
643
+ for (dir = ctx->dirtab[hash & ctx->dirmask]; dir; dir = dir->hash_next)
644
+ if (dir->hash == hash && !dir->retired && !dir->failed && !_wcsicmp (dir->path, path))
645
+ {
646
+ if (win32_dir_matches (dir, path))
647
+ break;
648
+ /* An open HANDLE follows a renamed directory, even after another
649
+ * directory has been created at the same path. */
650
+ dir->failed = 1;
651
+ }
652
+
653
+ if (dir)
654
+ free (path);
655
+ else
656
+ {
657
+ dir = (struct ev_win32_dir *)calloc (1, sizeof (*dir));
658
+ if (dir)
659
+ dir->names = (struct ev_win32_name **)calloc (16, sizeof (*dir->names));
660
+ if (!dir || !dir->names)
661
+ { free (dir); goto fail; }
662
+ dir->path = path;
663
+ dir->hash = hash;
664
+ dir->namemask = 15;
665
+ dir->handle = CreateFileW (path, FILE_LIST_DIRECTORY,
666
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING,
667
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 0);
668
+ if (dir->handle == INVALID_HANDLE_VALUE)
669
+ { free (dir->names); free (dir); goto fail; }
670
+ if (!CreateIoCompletionPort (dir->handle, ctx->port, (ULONG_PTR)dir, 0)
671
+ || !win32_dir_read (dir))
672
+ { CloseHandle (dir->handle); free (dir->names); free (dir); goto fail; }
673
+ dir->next = ctx->dirs;
674
+ dir->prev = &ctx->dirs;
675
+ if (ctx->dirs)
676
+ ctx->dirs->prev = &dir->next;
677
+ ctx->dirs = dir;
678
+ dir->hash_next = ctx->dirtab[hash & ctx->dirmask];
679
+ ctx->dirtab[hash & ctx->dirmask] = dir;
680
+ if (++ctx->dircnt > ctx->dirmask + 1)
681
+ win32_dirtab_grow (ctx);
682
+ }
683
+
684
+ ++dir->refs;
685
+ return dir;
686
+
687
+ fail:
688
+ free (path);
689
+ return 0;
690
+ }
691
+
692
+ static void
693
+ win32_fs_add_locked (EV_P_ ev_stat *w, int polling)
694
+ {
695
+ struct ev_win32_fs *ctx;
696
+ struct ev_win32_watch *old, *watch;
697
+ struct ev_win32_dir *dir;
698
+ wchar_t *parent, *children, *path;
699
+ if (!win32_fs_init (EV_A))
700
+ return;
701
+ ctx = win32_fs;
702
+ parent = win32_fs_path (w->path, 1);
703
+ children = (w->attr.st_nlink && (w->attr.st_mode & S_IFMT) == S_IFDIR)
704
+ ? win32_fs_path (w->path, 0) : 0;
705
+ if (!parent)
706
+ { free (children); return; }
707
+ old = win32_fs_watch (ctx, w);
708
+ if (old)
709
+ {
710
+ int same_parent;
711
+ int same_children = !children && !old->children.dir;
712
+ /* Timer callbacks in one loop turn share the same timestamp. Validate
713
+ * shared handles once per turn; new registrations still check afresh. */
714
+ if (polling)
715
+ {
716
+ struct ev_win32_dir *dirs[2];
717
+ int i;
718
+ dirs[0] = old->parent.dir;
719
+ dirs[1] = old->children.dir;
720
+ for (i = 0; i < 2; ++i)
721
+ if (dirs[i] && !dirs[i]->failed && dirs[i]->checked_at != ev_rt_now)
722
+ {
723
+ dirs[i]->checked_at = ev_rt_now;
724
+ if (!win32_dir_matches (dirs[i], dirs[i]->path))
725
+ dirs[i]->failed = 1;
726
+ }
727
+ }
728
+ same_parent = !old->parent.dir->failed
729
+ && (polling ? !_wcsicmp (old->parent.dir->path, parent)
730
+ : win32_dir_matches (old->parent.dir, parent));
731
+ if (children && old->children.dir)
732
+ same_children = !old->children.dir->failed
733
+ && (polling ? !_wcsicmp (old->children.dir->path, children)
734
+ : win32_dir_matches (old->children.dir, children));
735
+ if (same_parent && same_children)
736
+ {
737
+ /* Appends leave aliases unchanged. Retry failures during polling,
738
+ * and refresh after name events or an explicit re-registration. */
739
+ if (!polling || old->names_dirty || old->names_failed)
740
+ win32_watch_names (old);
741
+ free (parent);
742
+ free (children);
743
+ return;
744
+ }
745
+ if (!same_parent && !_wcsicmp (old->parent.dir->path, parent))
746
+ old->parent.dir->failed = 1;
747
+ if (!same_children && children && old->children.dir
748
+ && !_wcsicmp (old->children.dir->path, children))
749
+ old->children.dir->failed = 1;
750
+ }
751
+ else if (ctx->watchcnt == ctx->watchmax)
752
+ {
753
+ int max = ctx->watchmax ? ctx->watchmax * 2 : 16;
754
+ struct ev_win32_watch **watches
755
+ = (struct ev_win32_watch **)realloc (ctx->watches, max * sizeof (*watches));
756
+ if (!watches)
757
+ { free (parent); free (children); return; }
758
+ ctx->watches = watches;
759
+ ctx->watchmax = max;
760
+ }
761
+
762
+ path = win32_fs_path (w->path, 0);
763
+ watch = path ? (struct ev_win32_watch *)calloc (1, sizeof (*watch)) : 0;
764
+ if (watch && !win32_name_key (watch, 0, wcsrchr (path, L'\\') + 1))
765
+ { free (watch); watch = 0; }
766
+ if (!watch)
767
+ { free (parent); free (children); free (path); return; }
768
+ dir = win32_dir_acquire (ctx, parent);
769
+ if (!dir)
770
+ { free (watch->keys[0].key); free (watch); free (children); free (path); return; }
771
+ watch->path = path;
772
+ watch->name = wcsrchr (path, L'\\') + 1;
773
+ win32_watch_names (watch);
774
+ win32_subscription_add (&watch->parent, watch, dir);
775
+ win32_subscription_add (&watch->children, watch, win32_dir_acquire (ctx, children));
776
+ watch->watcher = w;
777
+ /* Acquire the replacement first, retaining unchanged shared directory reads. */
778
+ if (old)
779
+ {
780
+ ctx->watches[w->wd] = watch;
781
+ win32_watch_free (ctx, old);
782
+ }
783
+ else
784
+ {
785
+ w->wd = ctx->watchcnt;
786
+ ctx->watches[ctx->watchcnt++] = watch;
787
+ }
788
+ }
789
+
790
+ static void
791
+ win32_fs_add (EV_P_ ev_stat *w, int polling)
792
+ {
793
+ struct ev_win32_fs *ctx;
794
+ if (!win32_fs_init (EV_A))
795
+ return;
796
+ ctx = win32_fs;
797
+ EnterCriticalSection (&ctx->lock);
798
+ win32_fs_add_locked (EV_A_ w, polling);
799
+ LeaveCriticalSection (&ctx->lock);
800
+ }
801
+
802
+ static void
803
+ win32_fs_del (EV_P_ ev_stat *w)
804
+ {
805
+ struct ev_win32_fs *ctx = win32_fs;
806
+ struct ev_win32_watch *watch;
807
+ if (!ctx)
808
+ return;
809
+ EnterCriticalSection (&ctx->lock);
810
+ watch = win32_fs_watch (ctx, w);
811
+ if (watch)
812
+ {
813
+ struct ev_win32_watch *last = ctx->watches[--ctx->watchcnt];
814
+ ctx->watches[w->wd] = last;
815
+ last->watcher->wd = w->wd;
816
+ w->wd = -1;
817
+ win32_watch_free (ctx, watch);
818
+ }
819
+ LeaveCriticalSection (&ctx->lock);
820
+ }
821
+
822
+ static void
823
+ win32_fs_destroy (EV_P)
824
+ {
825
+ struct ev_win32_fs *ctx = win32_fs;
826
+ struct ev_win32_dir *dir, *next;
827
+ if (!ctx)
828
+ return;
829
+ EnterCriticalSection (&ctx->lock);
830
+ ctx->stopping = 1;
831
+ while (ctx->watchcnt)
832
+ win32_watch_free (ctx, ctx->watches[--ctx->watchcnt]);
833
+ free (ctx->watches);
834
+ ctx->watches = 0;
835
+ for (dir = ctx->dirs; dir; dir = next)
836
+ {
837
+ next = dir->next;
838
+ if (!dir->retired)
839
+ win32_dir_retire (ctx, dir);
840
+ }
841
+ ev_ref (EV_A);
842
+ ev_async_stop (EV_A_ &ctx->async);
843
+ win32_fs = 0;
844
+ /* The worker owns ctx after this handoff. Pending cancellation completions
845
+ * still reference directory buffers, so retain them until they arrive, but
846
+ * do not hold up loop destruction (which can run during Ruby GC). All
847
+ * subscriptions are gone and the worker can no longer signal this loop.
848
+ * A driver that never completes cancellation retains these resources. */
849
+ PostQueuedCompletionStatus (ctx->port, 0, 0, 0);
850
+ LeaveCriticalSection (&ctx->lock);
851
+ }