Skip to content

Commit 6070094

Browse files
committed
Watch.zig: add FileId to uniquely identifiy handles
1 parent c34b0f9 commit 6070094

File tree

2 files changed

+118
-51
lines changed

2 files changed

+118
-51
lines changed

lib/std/Build/Watch.zig

Lines changed: 109 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,34 @@ const Os = switch (builtin.os.tag) {
245245
handle_table: HandleTable,
246246
handle_extra: std.MultiArrayList(HandleExtra),
247247

248-
const HandleTable = std.AutoArrayHashMapUnmanaged(windows.HANDLE, ReactionSet);
248+
const HandleTable = std.ArrayHashMapUnmanaged(FileId, ReactionSet, FileId.Adapter, false);
249249
const HandleExtra = struct {
250250
dir: *Directory,
251251
wait_handle: windows.HANDLE,
252252
};
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+
253273
const Directory = struct {
254274
handle: windows.HANDLE,
275+
id: FileId,
255276
overlapped: windows.OVERLAPPED,
256277
buffer: [64512]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined,
257278

@@ -286,7 +307,7 @@ const Os = switch (builtin.os.tag) {
286307
return self.overlapped.hEvent.?;
287308
}
288309

289-
fn init(gpa: Allocator, handle: windows.HANDLE) !*@This() {
310+
fn init(gpa: Allocator, handle: windows.HANDLE, id: FileId) !*@This() {
290311
const event = try windows.CreateEventExW(
291312
null,
292313
null,
@@ -297,6 +318,7 @@ const Os = switch (builtin.os.tag) {
297318
const result = try gpa.create(@This());
298319
result.* = .{
299320
.handle = handle,
321+
.id = id,
300322
.overlapped = std.mem.zeroInit(
301323
windows.OVERLAPPED,
302324
.{
@@ -315,6 +337,35 @@ const Os = switch (builtin.os.tag) {
315337
}
316338
};
317339

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+
318369
fn markDirtySteps(w: *Watch, gpa: Allocator, dir: *Directory) !bool {
319370
var any_dirty = false;
320371
const bytes_returned = try windows.GetOverlappedResult(dir.handle, &dir.overlapped, false);
@@ -331,7 +382,7 @@ const Os = switch (builtin.os.tag) {
331382
const file_name_field: [*]u16 = @ptrFromInt(@intFromPtr(notify) + @sizeOf(windows.FILE_NOTIFY_INFORMATION));
332383
const file_name_len = std.unicode.wtf16LeToWtf8(&file_name_buf, file_name_field[0 .. notify.FileNameLength / 2]);
333384
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| {
335386
const reaction_set = w.os.handle_table.values()[reaction_set_i];
336387
if (reaction_set.getPtr(".")) |glob_set|
337388
any_dirty = markStepSetDirty(gpa, glob_set, any_dirty);
@@ -358,71 +409,78 @@ const Os = switch (builtin.os.tag) {
358409
if (!gop.found_existing) {
359410
// The following code is a drawn out NtCreateFile call. (mostly adapted from std.fs.Dir.makeOpenDirAccessMaskW)
360411
// 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;
380412
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);
406460
}
407461

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+
};
409466

410-
// `dir_handle` may already be present in the table in
467+
// `dir_id` may already be present in the table in
411468
// the case that we have multiple Cache.Path instances
412469
// that compare inequal but ultimately point to the same
413470
// directory on the file system.
414471
// In such case, we must revert adding this directory, but keep
415472
// 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| {
417474
windows.CloseHandle(dir_handle);
418475
return err;
419476
};
420477
if (dh_gop.found_existing) {
478+
windows.CloseHandle(dir_handle);
421479
_ = w.dir_table.pop();
422480
} else {
423481
assert(dh_gop.index == gop.index);
424482
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);
426484
errdefer dir.deinit(gpa);
427485
try dir.readChanges();
428486
try w.os.handle_extra.insert(gpa, dh_gop.index, .{

lib/std/os/windows.zig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3206,6 +3206,15 @@ pub const FILE_FS_DEVICE_INFORMATION = extern struct {
32063206
Characteristics: ULONG,
32073207
};
32083208

3209+
pub const FILE_FS_VOLUME_INFORMATION = extern struct {
3210+
VolumeCreationTime: LARGE_INTEGER,
3211+
VolumeSerialNumber: ULONG,
3212+
VolumeLabelLength: ULONG,
3213+
SupportsObjects: BOOLEAN,
3214+
// Flexible array member
3215+
// VolumeLabel: [1]WCHAR,
3216+
};
3217+
32093218
pub const FS_INFORMATION_CLASS = enum(c_int) {
32103219
FileFsVolumeInformation = 1,
32113220
FileFsLabelInformation,

0 commit comments

Comments
 (0)