@@ -245,13 +245,34 @@ const Os = switch (builtin.os.tag) {
245
245
handle_table : HandleTable ,
246
246
handle_extra : std .MultiArrayList (HandleExtra ),
247
247
248
- const HandleTable = std .AutoArrayHashMapUnmanaged ( windows . HANDLE , ReactionSet );
248
+ const HandleTable = std .ArrayHashMapUnmanaged ( FileId , ReactionSet , FileId . Adapter , false );
249
249
const HandleExtra = struct {
250
250
dir : * Directory ,
251
251
wait_handle : windows.HANDLE ,
252
252
};
253
+
254
+ const FileId = struct {
255
+ volumeSerialNumber : windows.ULONG ,
256
+ indexNumber : windows.LARGE_INTEGER ,
257
+
258
+ const Adapter = struct {
259
+ pub fn hash (self : Adapter , a : FileId ) u32 {
260
+ _ = self ;
261
+ var hasher = Hash .init (0 );
262
+ std .hash .autoHash (& hasher , a );
263
+ return @truncate (hasher .final ());
264
+ }
265
+ pub fn eql (self : Adapter , a : FileId , b : FileId , b_index : usize ) bool {
266
+ _ = self ;
267
+ _ = b_index ;
268
+ return a .volumeSerialNumber == b .volumeSerialNumber and a .indexNumber == b .indexNumber ;
269
+ }
270
+ };
271
+ };
272
+
253
273
const Directory = struct {
254
274
handle : windows.HANDLE ,
275
+ id : FileId ,
255
276
overlapped : windows.OVERLAPPED ,
256
277
buffer : [64512 ]u8 align (@alignOf (windows .FILE_NOTIFY_INFORMATION )) = undefined ,
257
278
@@ -286,7 +307,7 @@ const Os = switch (builtin.os.tag) {
286
307
return self .overlapped .hEvent .? ;
287
308
}
288
309
289
- fn init (gpa : Allocator , handle : windows.HANDLE ) ! * @This () {
310
+ fn init (gpa : Allocator , handle : windows.HANDLE , id : FileId ) ! * @This () {
290
311
const event = try windows .CreateEventExW (
291
312
null ,
292
313
null ,
@@ -297,6 +318,7 @@ const Os = switch (builtin.os.tag) {
297
318
const result = try gpa .create (@This ());
298
319
result .* = .{
299
320
.handle = handle ,
321
+ .id = id ,
300
322
.overlapped = std .mem .zeroInit (
301
323
windows .OVERLAPPED ,
302
324
.{
@@ -315,6 +337,35 @@ const Os = switch (builtin.os.tag) {
315
337
}
316
338
};
317
339
340
+ fn getFileId (handle : windows.HANDLE ) ! FileId {
341
+ var file_id : FileId = undefined ;
342
+ {
343
+ var io_status : windows.IO_STATUS_BLOCK = undefined ;
344
+ var volume_info : windows.FILE_FS_VOLUME_INFORMATION = undefined ;
345
+ const rc = windows .ntdll .NtQueryVolumeInformationFile (handle , & io_status , & volume_info , @sizeOf (windows .FILE_FS_VOLUME_INFORMATION ), .FileFsVolumeInformation );
346
+ switch (rc ) {
347
+ .SUCCESS = > {},
348
+ // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
349
+ // size provided. This is treated as success because the type of variable-length information that this would be relevant for
350
+ // (name, volume name, etc) we don't care about.
351
+ .BUFFER_OVERFLOW = > {},
352
+ else = > return windows .unexpectedStatus (rc ),
353
+ }
354
+ file_id .volumeSerialNumber = volume_info .VolumeSerialNumber ;
355
+ }
356
+ {
357
+ var io_status : windows.IO_STATUS_BLOCK = undefined ;
358
+ var internal_info : windows.FILE_INTERNAL_INFORMATION = undefined ;
359
+ const rc = windows .ntdll .NtQueryInformationFile (handle , & io_status , & internal_info , @sizeOf (windows .FILE_INTERNAL_INFORMATION ), .FileInternalInformation );
360
+ switch (rc ) {
361
+ .SUCCESS = > {},
362
+ else = > return windows .unexpectedStatus (rc ),
363
+ }
364
+ file_id .indexNumber = internal_info .IndexNumber ;
365
+ }
366
+ return file_id ;
367
+ }
368
+
318
369
fn markDirtySteps (w : * Watch , gpa : Allocator , dir : * Directory ) ! bool {
319
370
var any_dirty = false ;
320
371
const bytes_returned = try windows .GetOverlappedResult (dir .handle , & dir .overlapped , false );
@@ -331,7 +382,7 @@ const Os = switch (builtin.os.tag) {
331
382
const file_name_field : [* ]u16 = @ptrFromInt (@intFromPtr (notify ) + @sizeOf (windows .FILE_NOTIFY_INFORMATION ));
332
383
const file_name_len = std .unicode .wtf16LeToWtf8 (& file_name_buf , file_name_field [0 .. notify .FileNameLength / 2 ]);
333
384
const file_name = file_name_buf [0.. file_name_len ];
334
- if (w .os .handle_table .getIndex (dir .handle )) | reaction_set_i | {
385
+ if (w .os .handle_table .getIndex (dir .id )) | reaction_set_i | {
335
386
const reaction_set = w .os .handle_table .values ()[reaction_set_i ];
336
387
if (reaction_set .getPtr ("." )) | glob_set |
337
388
any_dirty = markStepSetDirty (gpa , glob_set , any_dirty );
@@ -358,71 +409,78 @@ const Os = switch (builtin.os.tag) {
358
409
if (! gop .found_existing ) {
359
410
// The following code is a drawn out NtCreateFile call. (mostly adapted from std.fs.Dir.makeOpenDirAccessMaskW)
360
411
// It's necessary in order to get the flags are required when calling ReadDirectoryChangesW.
361
- const root_fd = path .root_dir .handle .fd ;
362
- const sub_path = path .subPathOrDot ();
363
- const sub_path_w = try windows .sliceToPrefixedFileW (root_fd , sub_path );
364
- const path_len_bytes = std .math .cast (u16 , sub_path_w .len * 2 ) orelse return error .NameTooLong ;
365
-
366
- var nt_name = windows.UNICODE_STRING {
367
- .Length = @intCast (path_len_bytes ),
368
- .MaximumLength = @intCast (path_len_bytes ),
369
- .Buffer = @constCast (sub_path_w .span ().ptr ),
370
- };
371
- var attr = windows.OBJECT_ATTRIBUTES {
372
- .Length = @sizeOf (windows .OBJECT_ATTRIBUTES ),
373
- .RootDirectory = if (std .fs .path .isAbsoluteWindowsW (sub_path_w .span ())) null else root_fd ,
374
- .Attributes = 0 , // Note we do not use OBJ_CASE_INSENSITIVE here.
375
- .ObjectName = & nt_name ,
376
- .SecurityDescriptor = null ,
377
- .SecurityQualityOfService = null ,
378
- };
379
- var io : windows.IO_STATUS_BLOCK = undefined ;
380
412
var dir_handle : windows.HANDLE = undefined ;
381
- const rc = windows .ntdll .NtCreateFile (
382
- & dir_handle ,
383
- windows .SYNCHRONIZE | windows .GENERIC_READ | windows .FILE_LIST_DIRECTORY ,
384
- & attr ,
385
- & io ,
386
- null ,
387
- 0 ,
388
- windows .FILE_SHARE_READ | windows .FILE_SHARE_WRITE | windows .FILE_SHARE_DELETE ,
389
- windows .FILE_OPEN ,
390
- windows .FILE_DIRECTORY_FILE | windows .FILE_OPEN_FOR_BACKUP_INTENT ,
391
- null ,
392
- 0 ,
393
- );
394
-
395
- switch (rc ) {
396
- .SUCCESS = > {},
397
- .OBJECT_NAME_INVALID = > return error .BadPathName ,
398
- .OBJECT_NAME_NOT_FOUND = > return error .FileNotFound ,
399
- .OBJECT_NAME_COLLISION = > return error .PathAlreadyExists ,
400
- .OBJECT_PATH_NOT_FOUND = > return error .FileNotFound ,
401
- .NOT_A_DIRECTORY = > return error .NotDir ,
402
- // This can happen if the directory has 'List folder contents' permission set to 'Deny'
403
- .ACCESS_DENIED = > return error .AccessDenied ,
404
- .INVALID_PARAMETER = > unreachable ,
405
- else = > return windows .unexpectedStatus (rc ),
413
+ {
414
+ const root_fd = path .root_dir .handle .fd ;
415
+ const sub_path = path .subPathOrDot ();
416
+ const sub_path_w = try windows .sliceToPrefixedFileW (root_fd , sub_path );
417
+ const path_len_bytes = std .math .cast (u16 , sub_path_w .len * 2 ) orelse return error .NameTooLong ;
418
+
419
+ var nt_name = windows.UNICODE_STRING {
420
+ .Length = @intCast (path_len_bytes ),
421
+ .MaximumLength = @intCast (path_len_bytes ),
422
+ .Buffer = @constCast (sub_path_w .span ().ptr ),
423
+ };
424
+ var attr = windows.OBJECT_ATTRIBUTES {
425
+ .Length = @sizeOf (windows .OBJECT_ATTRIBUTES ),
426
+ .RootDirectory = if (std .fs .path .isAbsoluteWindowsW (sub_path_w .span ())) null else root_fd ,
427
+ .Attributes = 0 , // Note we do not use OBJ_CASE_INSENSITIVE here.
428
+ .ObjectName = & nt_name ,
429
+ .SecurityDescriptor = null ,
430
+ .SecurityQualityOfService = null ,
431
+ };
432
+ var io : windows.IO_STATUS_BLOCK = undefined ;
433
+ const rc = windows .ntdll .NtCreateFile (
434
+ & dir_handle ,
435
+ windows .SYNCHRONIZE | windows .GENERIC_READ | windows .FILE_LIST_DIRECTORY ,
436
+ & attr ,
437
+ & io ,
438
+ null ,
439
+ 0 ,
440
+ windows .FILE_SHARE_READ | windows .FILE_SHARE_WRITE | windows .FILE_SHARE_DELETE ,
441
+ windows .FILE_OPEN ,
442
+ windows .FILE_DIRECTORY_FILE | windows .FILE_OPEN_FOR_BACKUP_INTENT ,
443
+ null ,
444
+ 0 ,
445
+ );
446
+
447
+ switch (rc ) {
448
+ .SUCCESS = > {},
449
+ .OBJECT_NAME_INVALID = > return error .BadPathName ,
450
+ .OBJECT_NAME_NOT_FOUND = > return error .FileNotFound ,
451
+ .OBJECT_NAME_COLLISION = > return error .PathAlreadyExists ,
452
+ .OBJECT_PATH_NOT_FOUND = > return error .FileNotFound ,
453
+ .NOT_A_DIRECTORY = > return error .NotDir ,
454
+ // This can happen if the directory has 'List folder contents' permission set to 'Deny'
455
+ .ACCESS_DENIED = > return error .AccessDenied ,
456
+ .INVALID_PARAMETER = > unreachable ,
457
+ else = > return windows .unexpectedStatus (rc ),
458
+ }
459
+ assert (dir_handle != windows .INVALID_HANDLE_VALUE );
406
460
}
407
461
408
- assert (dir_handle != windows .INVALID_HANDLE_VALUE );
462
+ const dir_id = getFileId (dir_handle ) catch | err | {
463
+ windows .CloseHandle (dir_handle );
464
+ return err ;
465
+ };
409
466
410
- // `dir_handle ` may already be present in the table in
467
+ // `dir_id ` may already be present in the table in
411
468
// the case that we have multiple Cache.Path instances
412
469
// that compare inequal but ultimately point to the same
413
470
// directory on the file system.
414
471
// In such case, we must revert adding this directory, but keep
415
472
// the additions to the step set.
416
- const dh_gop = w .os .handle_table .getOrPut (gpa , dir_handle ) catch | err | {
473
+ const dh_gop = w .os .handle_table .getOrPut (gpa , dir_id ) catch | err | {
417
474
windows .CloseHandle (dir_handle );
418
475
return err ;
419
476
};
420
477
if (dh_gop .found_existing ) {
478
+ windows .CloseHandle (dir_handle );
421
479
_ = w .dir_table .pop ();
422
480
} else {
423
481
assert (dh_gop .index == gop .index );
424
482
dh_gop .value_ptr .* = .{};
425
- const dir = try Os .Directory .init (gpa , dir_handle );
483
+ const dir = try Os .Directory .init (gpa , dir_handle , dir_id );
426
484
errdefer dir .deinit (gpa );
427
485
try dir .readChanges ();
428
486
try w .os .handle_extra .insert (gpa , dh_gop .index , .{
0 commit comments