From 9532f729371d8142fb65c61794b7bd765cd82396 Mon Sep 17 00:00:00 2001 From: Stephen Gregoratto Date: Tue, 12 Mar 2024 21:13:13 +1100 Subject: [PATCH 1/3] Windows: Replace CreatePipe with ntdll implementation This implementation is now a direct replacement for the `kernel32` one. New bitflags for named pipes and other generic ones were added based on browsing the ReactOS sources. `UNICODE_STRING.Buffer` has also been changed to be nullable, as this is what makes the implementation work. This required some changes to places accesssing the buffer after a `SUCCESS`ful return, most notably `QueryObjectName` which even referred to it being nullable. --- lib/std/Thread.zig | 2 +- lib/std/fs.zig | 4 +- lib/std/os/windows.zig | 177 +++++++++++++++++++++++++++++---- lib/std/os/windows/ntdll.zig | 17 ++++ lib/std/os/windows/test.zig | 2 +- lib/std/zig/system/windows.zig | 2 +- 6 files changed, 177 insertions(+), 27 deletions(-) diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 574e573c13..e895183331 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -208,7 +208,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co )) { .SUCCESS => { const string = @as(*const os.windows.UNICODE_STRING, @ptrCast(&buf)); - const len = std.unicode.wtf16LeToWtf8(buffer, string.Buffer[0 .. string.Length / 2]); + const len = std.unicode.wtf16LeToWtf8(buffer, string.Buffer.?[0 .. string.Length / 2]); return if (len > 0) buffer[0..len] else null; }, .NOT_IMPLEMENTED => return error.Unsupported, diff --git a/lib/std/fs.zig b/lib/std/fs.zig index c9294e727a..5c6e8c9c3a 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -493,7 +493,7 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File { // not the path that the symlink points to. However, because we are opening // the file, we can let the openFileW call follow the symlink for us. const image_path_unicode_string = &os.windows.peb().ProcessParameters.ImagePathName; - const image_path_name = image_path_unicode_string.Buffer[0 .. image_path_unicode_string.Length / 2 :0]; + const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; const prefixed_path_w = try os.windows.wToPrefixedFileW(null, image_path_name); return cwd().openFileW(prefixed_path_w.span(), flags); } @@ -664,7 +664,7 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { }, .windows => { const image_path_unicode_string = &os.windows.peb().ProcessParameters.ImagePathName; - const image_path_name = image_path_unicode_string.Buffer[0 .. image_path_unicode_string.Length / 2 :0]; + const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; // If ImagePathName is a symlink, then it will contain the path of the // symlink, not the path that the symlink points to. We want the path diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 5bd71487a2..24ea53b0a9 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -153,14 +153,131 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN } } -pub const CreatePipeError = error{Unexpected}; +pub const CreatePipeError = error{ Unexpected, SystemResources }; +var npfs: ?HANDLE = null; + +/// A Zig wrapper around `NtCreateNamedPipeFile` and `NtCreateFile` syscalls. +/// It implements similar behavior to `CreatePipe` and is meant to serve +/// as a direct substitute for that call. pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void { - if (kernel32.CreatePipe(rd, wr, sattr, 0) == 0) { - switch (kernel32.GetLastError()) { - else => |err| return unexpectedError(err), + // Up to NT 5.2 (Windows XP/Server 2003), `CreatePipe` would generate a pipe similar to: + // + // \??\pipe\Win32Pipes.{pid}.{count} + // + // where `pid` is the process id and count is a incrementing counter. + // The implementation was changed after NT 6.0 (Vista) to open a handle to the Named Pipe File System + // and use that as the root directory for `NtCreateNamedPipeFile`. + // This object is visible under the NPFS but has no filename attached to it. + // + // This implementation replicates how `CreatePipe` works in modern Windows versions. + const opt_dev_handle = @atomicLoad(?HANDLE, &npfs, .seq_cst); + const dev_handle = opt_dev_handle orelse blk: { + const str = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\NamedPipe\\"); + const len: u16 = @truncate(str.len * @sizeOf(u16)); + const name = UNICODE_STRING{ + .Length = len, + .MaximumLength = len, + .Buffer = @constCast(@ptrCast(str)), + }; + const attrs = OBJECT_ATTRIBUTES{ + .ObjectName = @constCast(&name), + .Length = @sizeOf(OBJECT_ATTRIBUTES), + .RootDirectory = null, + .Attributes = 0, + .SecurityDescriptor = null, + .SecurityQualityOfService = null, + }; + + var iosb: IO_STATUS_BLOCK = undefined; + var handle: HANDLE = undefined; + switch (ntdll.NtCreateFile( + &handle, + GENERIC_READ | SYNCHRONIZE, + @constCast(&attrs), + &iosb, + null, + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_SYNCHRONOUS_IO_NONALERT, + null, + 0, + )) { + .SUCCESS => {}, + // Judging from the ReactOS sources this is technically possible. + .INSUFFICIENT_RESOURCES => return error.SystemResources, + .INVALID_PARAMETER => unreachable, + else => |e| return unexpectedStatus(e), } + if (@cmpxchgStrong(?HANDLE, &npfs, null, handle, .seq_cst, .seq_cst)) |xchg| { + CloseHandle(handle); + break :blk xchg.?; + } else break :blk handle; + }; + + const name = UNICODE_STRING{ .Buffer = null, .Length = 0, .MaximumLength = 0 }; + var attrs = OBJECT_ATTRIBUTES{ + .ObjectName = @constCast(&name), + .Length = @sizeOf(OBJECT_ATTRIBUTES), + .RootDirectory = dev_handle, + .Attributes = OBJ_CASE_INSENSITIVE, + .SecurityDescriptor = sattr.lpSecurityDescriptor, + .SecurityQualityOfService = null, + }; + if (sattr.bInheritHandle != 0) attrs.Attributes |= OBJ_INHERIT; + + // 120 second relative timeout in 100ns units. + const default_timeout: LARGE_INTEGER = (-120 * std.time.ns_per_s) / 100; + var iosb: IO_STATUS_BLOCK = undefined; + var read: HANDLE = undefined; + switch (ntdll.NtCreateNamedPipeFile( + &read, + GENERIC_READ | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE, + &attrs, + &iosb, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_CREATE, + FILE_SYNCHRONOUS_IO_NONALERT, + FILE_PIPE_BYTE_STREAM_TYPE, + FILE_PIPE_BYTE_STREAM_MODE, + FILE_PIPE_QUEUE_OPERATION, + 1, + 4096, + 4096, + @constCast(&default_timeout), + )) { + .SUCCESS => {}, + .INVALID_PARAMETER => unreachable, + .INSUFFICIENT_RESOURCES => return error.SystemResources, + else => |e| return unexpectedStatus(e), } + errdefer CloseHandle(read); + + attrs.RootDirectory = read; + + var write: HANDLE = undefined; + switch (ntdll.NtCreateFile( + &write, + FILE_GENERIC_WRITE, + &attrs, + &iosb, + null, + 0, + FILE_SHARE_READ, + FILE_OPEN, + FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE, + null, + 0, + )) { + .SUCCESS => {}, + .INVALID_PARAMETER => unreachable, + .INSUFFICIENT_RESOURCES => return error.SystemResources, + else => |e| return unexpectedStatus(e), + } + + rd.* = read; + wr.* = write; } pub fn CreateEventEx(attributes: ?*SECURITY_ATTRIBUTES, name: []const u8, flags: DWORD, desired_access: DWORD) !HANDLE { @@ -1050,35 +1167,32 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 { return @as(u64, @bitCast(result)); } -pub fn QueryObjectName( - handle: HANDLE, - out_buffer: []u16, -) ![]u16 { +pub fn QueryObjectName(handle: HANDLE, out_buffer: []u16) ![]u16 { const out_buffer_aligned = mem.alignInSlice(out_buffer, @alignOf(OBJECT_NAME_INFORMATION)) orelse return error.NameTooLong; const info = @as(*OBJECT_NAME_INFORMATION, @ptrCast(out_buffer_aligned)); - //buffer size is specified in bytes + // buffer size is specified in bytes const out_buffer_len = std.math.cast(ULONG, out_buffer_aligned.len * 2) orelse std.math.maxInt(ULONG); - //last argument would return the length required for full_buffer, not exposed here - const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, info, out_buffer_len, null); - switch (rc) { - .SUCCESS => { + // last argument would return the length required for full_buffer, not exposed here + return switch (ntdll.NtQueryObject(handle, .ObjectNameInformation, info, out_buffer_len, null)) { + .SUCCESS => blk: { // info.Name.Buffer from ObQueryNameString is documented to be null (and MaximumLength == 0) // if the object was "unnamed", not sure if this can happen for file handles - if (info.Name.MaximumLength == 0) return error.Unexpected; + if (info.Name.MaximumLength == 0) break :blk error.Unexpected; // resulting string length is specified in bytes const path_length_unterminated = @divExact(info.Name.Length, 2); - return info.Name.Buffer[0..path_length_unterminated]; + break :blk info.Name.Buffer.?[0..path_length_unterminated]; }, - .ACCESS_DENIED => return error.AccessDenied, - .INVALID_HANDLE => return error.InvalidHandle, + .ACCESS_DENIED => error.AccessDenied, + .INVALID_HANDLE => error.InvalidHandle, // triggered when the buffer is too small for the OBJECT_NAME_INFORMATION object (.INFO_LENGTH_MISMATCH), // or if the buffer is too small for the file path returned (.BUFFER_OVERFLOW, .BUFFER_TOO_SMALL) - .INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => return error.NameTooLong, - else => |e| return unexpectedStatus(e), - } + .INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => error.NameTooLong, + else => |e| unexpectedStatus(e), + }; } -test "QueryObjectName" { + +test QueryObjectName { if (builtin.os.tag != .windows) return; @@ -3186,6 +3300,25 @@ pub const FILE_ATTRIBUTE_SYSTEM = 0x4; pub const FILE_ATTRIBUTE_TEMPORARY = 0x100; pub const FILE_ATTRIBUTE_VIRTUAL = 0x10000; +pub const FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1ff; +pub const FILE_GENERIC_READ = STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE; +pub const FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE; +pub const FILE_GENERIC_EXECUTE = STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE; + +// Flags for NtCreateNamedPipeFile +// NamedPipeType +pub const FILE_PIPE_BYTE_STREAM_TYPE = 0x0; +pub const FILE_PIPE_MESSAGE_TYPE = 0x1; +pub const FILE_PIPE_ACCEPT_REMOTE_CLIENTS = 0x0; +pub const FILE_PIPE_REJECT_REMOTE_CLIENTS = 0x2; +pub const FILE_PIPE_TYPE_VALID_MASK = 0x3; +// CompletionMode +pub const FILE_PIPE_QUEUE_OPERATION = 0x0; +pub const FILE_PIPE_COMPLETE_OPERATION = 0x1; +// ReadMode +pub const FILE_PIPE_BYTE_STREAM_MODE = 0x0; +pub const FILE_PIPE_MESSAGE_MODE = 0x1; + // flags for CreateEvent pub const CREATE_EVENT_INITIAL_SET = 0x00000002; pub const CREATE_EVENT_MANUAL_RESET = 0x00000001; @@ -4151,7 +4284,7 @@ pub const OBJ_VALID_ATTRIBUTES = 0x000003F2; pub const UNICODE_STRING = extern struct { Length: c_ushort, MaximumLength: c_ushort, - Buffer: [*]WCHAR, + Buffer: ?[*]WCHAR, }; pub const ACTIVATION_CONTEXT_DATA = opaque {}; diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index d43657f6f3..4683b1e887 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -341,3 +341,20 @@ pub extern "ntdll" fn NtProtectVirtualMemory( pub extern "ntdll" fn RtlExitUserProcess( ExitStatus: u32, ) callconv(WINAPI) noreturn; + +pub extern "ntdll" fn NtCreateNamedPipeFile( + FileHandle: *HANDLE, + DesiredAccess: ULONG, + ObjectAttributes: *OBJECT_ATTRIBUTES, + IoStatusBlock: *IO_STATUS_BLOCK, + ShareAccess: ULONG, + CreateDisposition: ULONG, + CreateOptions: ULONG, + NamedPipeType: ULONG, + ReadMode: ULONG, + CompletionMode: ULONG, + MaximumInstances: ULONG, + InboundQuota: ULONG, + OutboundQuota: ULONG, + DefaultTimeout: *LARGE_INTEGER, +) callconv(WINAPI) NTSTATUS; diff --git a/lib/std/os/windows/test.zig b/lib/std/os/windows/test.zig index 9936a922af..64d554c836 100644 --- a/lib/std/os/windows/test.zig +++ b/lib/std/os/windows/test.zig @@ -15,7 +15,7 @@ fn RtlDosPathNameToNtPathName_U(path: [:0]const u16) !windows.PathSpace { defer windows.ntdll.RtlFreeUnicodeString(&out); var path_space: windows.PathSpace = undefined; - const out_path = out.Buffer[0 .. out.Length / 2]; + const out_path = out.Buffer.?[0 .. out.Length / 2]; @memcpy(path_space.data[0..out_path.len], out_path); path_space.len = out.Length / 2; path_space.data[path_space.len] = 0; diff --git a/lib/std/zig/system/windows.zig b/lib/std/zig/system/windows.zig index 34f965a259..0da3bdcce8 100644 --- a/lib/std/zig/system/windows.zig +++ b/lib/std/zig/system/windows.zig @@ -160,7 +160,7 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void { => { var buf = @field(args, field.name).value_buf; const entry = @as(*align(1) const std.os.windows.UNICODE_STRING, @ptrCast(table[i + 1].EntryContext)); - const len = try std.unicode.utf16LeToUtf8(buf, entry.Buffer[0 .. entry.Length / 2]); + const len = try std.unicode.utf16LeToUtf8(buf, entry.Buffer.?[0 .. entry.Length / 2]); buf[len] = 0; }, From 69175ad62f531327e9fd693c13890ca1d472a7a3 Mon Sep 17 00:00:00 2001 From: Stephen Gregoratto Date: Tue, 12 Mar 2024 22:21:55 +1100 Subject: [PATCH 2/3] Windows: Add wrappers for `GetCurrent(Process|Thread)` via NT_TIB This is how they've been implemented in `kernel32` since NT 3.1. --- lib/std/child_process.zig | 2 +- lib/std/debug.zig | 2 +- lib/std/os/windows.zig | 47 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index edc97c2c3e..85fa3d294e 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -1420,7 +1420,7 @@ fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *cons const pipe_path = std.fmt.bufPrintZ( &tmp_buf, "\\\\.\\pipe\\zig-childprocess-{d}-{d}", - .{ windows.kernel32.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) }, + .{ windows.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) }, ) catch unreachable; const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable; tmp_bufw[len] = 0; diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 932fb4a730..13e2d95e2c 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -814,7 +814,7 @@ pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const w return windows.ntdll.RtlCaptureStackBackTrace(0, addresses.len, @as(**anyopaque, @ptrCast(addresses.ptr)), null); } - const tib = @as(*const windows.NT_TIB, @ptrCast(&windows.teb().Reserved1)); + const tib = &windows.teb().NtTib; var context: windows.CONTEXT = undefined; if (existing_context) |context_ptr| { diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 24ea53b0a9..147ad7a12b 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -153,6 +153,24 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN } } +pub fn GetCurrentProcess() HANDLE { + const process_pseudo_handle: usize = @bitCast(@as(isize, -1)); + return @ptrFromInt(process_pseudo_handle); +} + +pub fn GetCurrentProcessId() DWORD { + return @truncate(@intFromPtr(teb().ClientId.UniqueProcess)); +} + +pub fn GetCurrentThread() HANDLE { + const thread_pseudo_handle: usize = @bitCast(@as(isize, -2)); + return @ptrFromInt(thread_pseudo_handle); +} + +pub fn GetCurrentThreadId() DWORD { + return @truncate(@intFromPtr(teb().ClientId.UniqueThread)); +} + pub const CreatePipeError = error{ Unexpected, SystemResources }; var npfs: ?HANDLE = null; @@ -259,12 +277,12 @@ pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) C var write: HANDLE = undefined; switch (ntdll.NtCreateFile( &write, - FILE_GENERIC_WRITE, + GENERIC_WRITE | SYNCHRONIZE | FILE_READ_ATTRIBUTES, &attrs, &iosb, null, 0, - FILE_SHARE_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE, null, @@ -4309,7 +4327,11 @@ pub const THREAD_BASIC_INFORMATION = extern struct { }; pub const TEB = extern struct { - Reserved1: [12]PVOID, + NtTib: NT_TIB, + EnvironmentPointer: PVOID, + ClientId: CLIENT_ID, + ActiveRpcHandle: PVOID, + ThreadLocalStoragePointer: PVOID, ProcessEnvironmentBlock: *PEB, Reserved2: [399]PVOID, Reserved3: [1952]u8, @@ -4321,6 +4343,25 @@ pub const TEB = extern struct { TlsExpansionSlots: PVOID, }; +comptime { + // Offsets taken from WinDbg info and Geoff Chappell[1] (RIP) + // [1]: https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/teb/index.htm + assert(@offsetOf(TEB, "NtTib") == 0x00); + if (@sizeOf(usize) == 4) { + assert(@offsetOf(TEB, "EnvironmentPointer") == 0x1C); + assert(@offsetOf(TEB, "ClientId") == 0x20); + assert(@offsetOf(TEB, "ActiveRpcHandle") == 0x28); + assert(@offsetOf(TEB, "ThreadLocalStoragePointer") == 0x2C); + assert(@offsetOf(TEB, "ProcessEnvironmentBlock") == 0x30); + } else if (@sizeOf(usize) == 8) { + assert(@offsetOf(TEB, "EnvironmentPointer") == 0x38); + assert(@offsetOf(TEB, "ClientId") == 0x40); + assert(@offsetOf(TEB, "ActiveRpcHandle") == 0x50); + assert(@offsetOf(TEB, "ThreadLocalStoragePointer") == 0x58); + assert(@offsetOf(TEB, "ProcessEnvironmentBlock") == 0x60); + } +} + pub const EXCEPTION_REGISTRATION_RECORD = extern struct { Next: ?*EXCEPTION_REGISTRATION_RECORD, Handler: ?*EXCEPTION_DISPOSITION, From 67df3ded68a5fda9bdd7ae22d26199ca403409ec Mon Sep 17 00:00:00 2001 From: Stephen Gregoratto Date: Sat, 16 Mar 2024 15:10:39 +1100 Subject: [PATCH 3/3] Windows: make FILE_INFO_BY_HANDLE_CLASS a tagged enum Fixes a TODO referencing the ancient issue #305. --- lib/std/os/windows.zig | 46 +++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 147ad7a12b..dec5402609 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -3147,29 +3147,29 @@ pub const OVERLAPPED_ENTRY = extern struct { pub const MAX_PATH = 260; -// TODO issue #305 -pub const FILE_INFO_BY_HANDLE_CLASS = u32; -pub const FileBasicInfo = 0; -pub const FileStandardInfo = 1; -pub const FileNameInfo = 2; -pub const FileRenameInfo = 3; -pub const FileDispositionInfo = 4; -pub const FileAllocationInfo = 5; -pub const FileEndOfFileInfo = 6; -pub const FileStreamInfo = 7; -pub const FileCompressionInfo = 8; -pub const FileAttributeTagInfo = 9; -pub const FileIdBothDirectoryInfo = 10; -pub const FileIdBothDirectoryRestartInfo = 11; -pub const FileIoPriorityHintInfo = 12; -pub const FileRemoteProtocolInfo = 13; -pub const FileFullDirectoryInfo = 14; -pub const FileFullDirectoryRestartInfo = 15; -pub const FileStorageInfo = 16; -pub const FileAlignmentInfo = 17; -pub const FileIdInfo = 18; -pub const FileIdExtdDirectoryInfo = 19; -pub const FileIdExtdDirectoryRestartInfo = 20; +pub const FILE_INFO_BY_HANDLE_CLASS = enum(u32) { + FileBasicInfo = 0, + FileStandardInfo = 1, + FileNameInfo = 2, + FileRenameInfo = 3, + FileDispositionInfo = 4, + FileAllocationInfo = 5, + FileEndOfFileInfo = 6, + FileStreamInfo = 7, + FileCompressionInfo = 8, + FileAttributeTagInfo = 9, + FileIdBothDirectoryInfo = 10, + FileIdBothDirectoryRestartInfo = 11, + FileIoPriorityHintInfo = 12, + FileRemoteProtocolInfo = 13, + FileFullDirectoryInfo = 14, + FileFullDirectoryRestartInfo = 15, + FileStorageInfo = 16, + FileAlignmentInfo = 17, + FileIdInfo = 18, + FileIdExtdDirectoryInfo = 19, + FileIdExtdDirectoryRestartInfo = 20, +}; pub const BY_HANDLE_FILE_INFORMATION = extern struct { dwFileAttributes: DWORD,