master
1//! This file contains thin wrappers around Windows-specific APIs, with these
2//! specific goals in mind:
3//! * Convert "errno"-style error codes into Zig errors.
4//! * When null-terminated or WTF16LE byte buffers are required, provide APIs which accept
5//! slices as well as APIs which accept null-terminated WTF16LE byte buffers.
6
7const builtin = @import("builtin");
8const native_arch = builtin.cpu.arch;
9
10const std = @import("../std.zig");
11const Io = std.Io;
12const mem = std.mem;
13const assert = std.debug.assert;
14const math = std.math;
15const maxInt = std.math.maxInt;
16const UnexpectedError = std.posix.UnexpectedError;
17
18test {
19 if (builtin.os.tag == .windows) {
20 _ = @import("windows/test.zig");
21 }
22}
23
24pub const advapi32 = @import("windows/advapi32.zig");
25pub const kernel32 = @import("windows/kernel32.zig");
26pub const ntdll = @import("windows/ntdll.zig");
27pub const ws2_32 = @import("windows/ws2_32.zig");
28pub const crypt32 = @import("windows/crypt32.zig");
29pub const nls = @import("windows/nls.zig");
30
31pub const self_process_handle = @as(HANDLE, @ptrFromInt(maxInt(usize)));
32
33const Self = @This();
34
35pub const OpenError = error{
36 IsDir,
37 NotDir,
38 FileNotFound,
39 NoDevice,
40 AccessDenied,
41 PipeBusy,
42 PathAlreadyExists,
43 Unexpected,
44 NameTooLong,
45 WouldBlock,
46 NetworkNotFound,
47 AntivirusInterference,
48 BadPathName,
49};
50
51pub const OpenFileOptions = struct {
52 access_mask: ACCESS_MASK,
53 dir: ?HANDLE = null,
54 sa: ?*SECURITY_ATTRIBUTES = null,
55 share_access: ULONG = FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
56 creation: ULONG,
57 /// If true, tries to open path as a directory.
58 /// Defaults to false.
59 filter: Filter = .file_only,
60 /// If false, tries to open path as a reparse point without dereferencing it.
61 /// Defaults to true.
62 follow_symlinks: bool = true,
63
64 pub const Filter = enum {
65 /// Causes `OpenFile` to return `error.IsDir` if the opened handle would be a directory.
66 file_only,
67 /// Causes `OpenFile` to return `error.NotDir` if the opened handle would be a file.
68 dir_only,
69 /// `OpenFile` does not discriminate between opening files and directories.
70 any,
71 };
72};
73
74pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE {
75 if (mem.eql(u16, sub_path_w, &[_]u16{'.'}) and options.filter == .file_only) {
76 return error.IsDir;
77 }
78 if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and options.filter == .file_only) {
79 return error.IsDir;
80 }
81
82 var result: HANDLE = undefined;
83
84 const path_len_bytes = math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong;
85 var nt_name = UNICODE_STRING{
86 .Length = path_len_bytes,
87 .MaximumLength = path_len_bytes,
88 .Buffer = @constCast(sub_path_w.ptr),
89 };
90 var attr = OBJECT_ATTRIBUTES{
91 .Length = @sizeOf(OBJECT_ATTRIBUTES),
92 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir,
93 .Attributes = if (options.sa) |ptr| blk: { // Note we do not use OBJ_CASE_INSENSITIVE here.
94 const inherit: ULONG = if (ptr.bInheritHandle == TRUE) OBJ_INHERIT else 0;
95 break :blk inherit;
96 } else 0,
97 .ObjectName = &nt_name,
98 .SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null,
99 .SecurityQualityOfService = null,
100 };
101 var io: IO_STATUS_BLOCK = undefined;
102 const blocking_flag: ULONG = FILE_SYNCHRONOUS_IO_NONALERT;
103 const file_or_dir_flag: ULONG = switch (options.filter) {
104 .file_only => FILE_NON_DIRECTORY_FILE,
105 .dir_only => FILE_DIRECTORY_FILE,
106 .any => 0,
107 };
108 // If we're not following symlinks, we need to ensure we don't pass in any synchronization flags such as FILE_SYNCHRONOUS_IO_NONALERT.
109 const flags: ULONG = if (options.follow_symlinks) file_or_dir_flag | blocking_flag else file_or_dir_flag | FILE_OPEN_REPARSE_POINT;
110
111 while (true) {
112 const rc = ntdll.NtCreateFile(
113 &result,
114 options.access_mask,
115 &attr,
116 &io,
117 null,
118 FILE_ATTRIBUTE_NORMAL,
119 options.share_access,
120 options.creation,
121 flags,
122 null,
123 0,
124 );
125 switch (rc) {
126 .SUCCESS => return result,
127 .OBJECT_NAME_INVALID => return error.BadPathName,
128 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
129 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
130 .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found
131 .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't
132 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
133 .INVALID_PARAMETER => unreachable,
134 .SHARING_VIOLATION => return error.AccessDenied,
135 .ACCESS_DENIED => return error.AccessDenied,
136 .PIPE_BUSY => return error.PipeBusy,
137 .PIPE_NOT_AVAILABLE => return error.NoDevice,
138 .OBJECT_PATH_SYNTAX_BAD => unreachable,
139 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
140 .FILE_IS_A_DIRECTORY => return error.IsDir,
141 .NOT_A_DIRECTORY => return error.NotDir,
142 .USER_MAPPED_FILE => return error.AccessDenied,
143 .INVALID_HANDLE => unreachable,
144 .DELETE_PENDING => {
145 // This error means that there *was* a file in this location on
146 // the file system, but it was deleted. However, the OS is not
147 // finished with the deletion operation, and so this CreateFile
148 // call has failed. There is not really a sane way to handle
149 // this other than retrying the creation after the OS finishes
150 // the deletion.
151 _ = kernel32.SleepEx(1, TRUE);
152 continue;
153 },
154 .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
155 else => return unexpectedStatus(rc),
156 }
157 }
158}
159
160pub fn GetCurrentProcess() HANDLE {
161 const process_pseudo_handle: usize = @bitCast(@as(isize, -1));
162 return @ptrFromInt(process_pseudo_handle);
163}
164
165pub fn GetCurrentProcessId() DWORD {
166 return @truncate(@intFromPtr(teb().ClientId.UniqueProcess));
167}
168
169pub fn GetCurrentThread() HANDLE {
170 const thread_pseudo_handle: usize = @bitCast(@as(isize, -2));
171 return @ptrFromInt(thread_pseudo_handle);
172}
173
174pub fn GetCurrentThreadId() DWORD {
175 return @truncate(@intFromPtr(teb().ClientId.UniqueThread));
176}
177
178pub fn GetLastError() Win32Error {
179 return @enumFromInt(teb().LastErrorValue);
180}
181
182pub const CreatePipeError = error{ Unexpected, SystemResources };
183
184var npfs: ?HANDLE = null;
185
186/// A Zig wrapper around `NtCreateNamedPipeFile` and `NtCreateFile` syscalls.
187/// It implements similar behavior to `CreatePipe` and is meant to serve
188/// as a direct substitute for that call.
189pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void {
190 // Up to NT 5.2 (Windows XP/Server 2003), `CreatePipe` would generate a pipe similar to:
191 //
192 // \??\pipe\Win32Pipes.{pid}.{count}
193 //
194 // where `pid` is the process id and count is a incrementing counter.
195 // The implementation was changed after NT 6.0 (Vista) to open a handle to the Named Pipe File System
196 // and use that as the root directory for `NtCreateNamedPipeFile`.
197 // This object is visible under the NPFS but has no filename attached to it.
198 //
199 // This implementation replicates how `CreatePipe` works in modern Windows versions.
200 const opt_dev_handle = @atomicLoad(?HANDLE, &npfs, .seq_cst);
201 const dev_handle = opt_dev_handle orelse blk: {
202 const str = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\NamedPipe\\");
203 const len: u16 = @truncate(str.len * @sizeOf(u16));
204 const name = UNICODE_STRING{
205 .Length = len,
206 .MaximumLength = len,
207 .Buffer = @ptrCast(@constCast(str)),
208 };
209 const attrs = OBJECT_ATTRIBUTES{
210 .ObjectName = @constCast(&name),
211 .Length = @sizeOf(OBJECT_ATTRIBUTES),
212 .RootDirectory = null,
213 .Attributes = 0,
214 .SecurityDescriptor = null,
215 .SecurityQualityOfService = null,
216 };
217
218 var iosb: IO_STATUS_BLOCK = undefined;
219 var handle: HANDLE = undefined;
220 switch (ntdll.NtCreateFile(
221 &handle,
222 GENERIC_READ | SYNCHRONIZE,
223 @constCast(&attrs),
224 &iosb,
225 null,
226 0,
227 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
228 FILE_OPEN,
229 FILE_SYNCHRONOUS_IO_NONALERT,
230 null,
231 0,
232 )) {
233 .SUCCESS => {},
234 // Judging from the ReactOS sources this is technically possible.
235 .INSUFFICIENT_RESOURCES => return error.SystemResources,
236 .INVALID_PARAMETER => unreachable,
237 else => |e| return unexpectedStatus(e),
238 }
239 if (@cmpxchgStrong(?HANDLE, &npfs, null, handle, .seq_cst, .seq_cst)) |xchg| {
240 CloseHandle(handle);
241 break :blk xchg.?;
242 } else break :blk handle;
243 };
244
245 const name = UNICODE_STRING{ .Buffer = null, .Length = 0, .MaximumLength = 0 };
246 var attrs = OBJECT_ATTRIBUTES{
247 .ObjectName = @constCast(&name),
248 .Length = @sizeOf(OBJECT_ATTRIBUTES),
249 .RootDirectory = dev_handle,
250 .Attributes = OBJ_CASE_INSENSITIVE,
251 .SecurityDescriptor = sattr.lpSecurityDescriptor,
252 .SecurityQualityOfService = null,
253 };
254 if (sattr.bInheritHandle != 0) attrs.Attributes |= OBJ_INHERIT;
255
256 // 120 second relative timeout in 100ns units.
257 const default_timeout: LARGE_INTEGER = (-120 * std.time.ns_per_s) / 100;
258 var iosb: IO_STATUS_BLOCK = undefined;
259 var read: HANDLE = undefined;
260 switch (ntdll.NtCreateNamedPipeFile(
261 &read,
262 GENERIC_READ | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
263 &attrs,
264 &iosb,
265 FILE_SHARE_READ | FILE_SHARE_WRITE,
266 FILE_CREATE,
267 FILE_SYNCHRONOUS_IO_NONALERT,
268 FILE_PIPE_BYTE_STREAM_TYPE,
269 FILE_PIPE_BYTE_STREAM_MODE,
270 FILE_PIPE_QUEUE_OPERATION,
271 1,
272 4096,
273 4096,
274 @constCast(&default_timeout),
275 )) {
276 .SUCCESS => {},
277 .INVALID_PARAMETER => unreachable,
278 .INSUFFICIENT_RESOURCES => return error.SystemResources,
279 else => |e| return unexpectedStatus(e),
280 }
281 errdefer CloseHandle(read);
282
283 attrs.RootDirectory = read;
284
285 var write: HANDLE = undefined;
286 switch (ntdll.NtCreateFile(
287 &write,
288 GENERIC_WRITE | SYNCHRONIZE | FILE_READ_ATTRIBUTES,
289 &attrs,
290 &iosb,
291 null,
292 0,
293 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
294 FILE_OPEN,
295 FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE,
296 null,
297 0,
298 )) {
299 .SUCCESS => {},
300 .INVALID_PARAMETER => unreachable,
301 .INSUFFICIENT_RESOURCES => return error.SystemResources,
302 else => |e| return unexpectedStatus(e),
303 }
304
305 rd.* = read;
306 wr.* = write;
307}
308
309pub const DeviceIoControlError = error{
310 AccessDenied,
311 /// The volume does not contain a recognized file system. File system
312 /// drivers might not be loaded, or the volume may be corrupt.
313 UnrecognizedVolume,
314 Unexpected,
315};
316
317/// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls.
318/// It implements similar behavior to `DeviceIoControl` and is meant to serve
319/// as a direct substitute for that call.
320/// TODO work out if we need to expose other arguments to the underlying syscalls.
321pub fn DeviceIoControl(
322 h: HANDLE,
323 ioControlCode: ULONG,
324 in: ?[]const u8,
325 out: ?[]u8,
326) DeviceIoControlError!void {
327 // Logic from: https://doxygen.reactos.org/d3/d74/deviceio_8c.html
328 const is_fsctl = (ioControlCode >> 16) == FILE_DEVICE_FILE_SYSTEM;
329
330 var io: IO_STATUS_BLOCK = undefined;
331 const in_ptr = if (in) |i| i.ptr else null;
332 const in_len = if (in) |i| @as(ULONG, @intCast(i.len)) else 0;
333 const out_ptr = if (out) |o| o.ptr else null;
334 const out_len = if (out) |o| @as(ULONG, @intCast(o.len)) else 0;
335
336 const rc = blk: {
337 if (is_fsctl) {
338 break :blk ntdll.NtFsControlFile(
339 h,
340 null,
341 null,
342 null,
343 &io,
344 ioControlCode,
345 in_ptr,
346 in_len,
347 out_ptr,
348 out_len,
349 );
350 } else {
351 break :blk ntdll.NtDeviceIoControlFile(
352 h,
353 null,
354 null,
355 null,
356 &io,
357 ioControlCode,
358 in_ptr,
359 in_len,
360 out_ptr,
361 out_len,
362 );
363 }
364 };
365 switch (rc) {
366 .SUCCESS => {},
367 .PRIVILEGE_NOT_HELD => return error.AccessDenied,
368 .ACCESS_DENIED => return error.AccessDenied,
369 .INVALID_DEVICE_REQUEST => return error.AccessDenied, // Not supported by the underlying filesystem
370 .INVALID_PARAMETER => unreachable,
371 .UNRECOGNIZED_VOLUME => return error.UnrecognizedVolume,
372 else => return unexpectedStatus(rc),
373 }
374}
375
376pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWORD {
377 var bytes: DWORD = undefined;
378 if (kernel32.GetOverlappedResult(h, overlapped, &bytes, @intFromBool(wait)) == 0) {
379 switch (GetLastError()) {
380 .IO_INCOMPLETE => if (!wait) return error.WouldBlock else unreachable,
381 else => |err| return unexpectedError(err),
382 }
383 }
384 return bytes;
385}
386
387pub const SetHandleInformationError = error{Unexpected};
388
389pub fn SetHandleInformation(h: HANDLE, mask: DWORD, flags: DWORD) SetHandleInformationError!void {
390 if (kernel32.SetHandleInformation(h, mask, flags) == 0) {
391 switch (GetLastError()) {
392 else => |err| return unexpectedError(err),
393 }
394 }
395}
396
397pub const RtlGenRandomError = error{
398 /// `RtlGenRandom` has been known to fail in situations where the system is under heavy load.
399 /// Unfortunately, it does not call `SetLastError`, so it is not possible to get more specific
400 /// error information; it could actually be due to an out-of-memory condition, for example.
401 SystemResources,
402};
403
404/// Call RtlGenRandom() instead of CryptGetRandom() on Windows
405/// https://github.com/rust-lang-nursery/rand/issues/111
406/// https://bugzilla.mozilla.org/show_bug.cgi?id=504270
407pub fn RtlGenRandom(output: []u8) RtlGenRandomError!void {
408 var total_read: usize = 0;
409 var buff: []u8 = output[0..];
410 const max_read_size: ULONG = maxInt(ULONG);
411
412 while (total_read < output.len) {
413 const to_read: ULONG = @min(buff.len, max_read_size);
414
415 if (advapi32.RtlGenRandom(buff.ptr, to_read) == 0) {
416 return error.SystemResources;
417 }
418
419 total_read += to_read;
420 buff = buff[to_read..];
421 }
422}
423
424pub const WaitForSingleObjectError = error{
425 WaitAbandoned,
426 WaitTimeOut,
427 Unexpected,
428};
429
430pub fn WaitForSingleObject(handle: HANDLE, milliseconds: DWORD) WaitForSingleObjectError!void {
431 return WaitForSingleObjectEx(handle, milliseconds, false);
432}
433
434pub fn WaitForSingleObjectEx(handle: HANDLE, milliseconds: DWORD, alertable: bool) WaitForSingleObjectError!void {
435 switch (kernel32.WaitForSingleObjectEx(handle, milliseconds, @intFromBool(alertable))) {
436 WAIT_ABANDONED => return error.WaitAbandoned,
437 WAIT_OBJECT_0 => return,
438 WAIT_TIMEOUT => return error.WaitTimeOut,
439 WAIT_FAILED => switch (GetLastError()) {
440 else => |err| return unexpectedError(err),
441 },
442 else => return error.Unexpected,
443 }
444}
445
446pub fn WaitForMultipleObjectsEx(handles: []const HANDLE, waitAll: bool, milliseconds: DWORD, alertable: bool) !u32 {
447 assert(handles.len > 0 and handles.len <= MAXIMUM_WAIT_OBJECTS);
448 const nCount: DWORD = @as(DWORD, @intCast(handles.len));
449 switch (kernel32.WaitForMultipleObjectsEx(
450 nCount,
451 handles.ptr,
452 @intFromBool(waitAll),
453 milliseconds,
454 @intFromBool(alertable),
455 )) {
456 WAIT_OBJECT_0...WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS => |n| {
457 const handle_index = n - WAIT_OBJECT_0;
458 assert(handle_index < nCount);
459 return handle_index;
460 },
461 WAIT_ABANDONED_0...WAIT_ABANDONED_0 + MAXIMUM_WAIT_OBJECTS => |n| {
462 const handle_index = n - WAIT_ABANDONED_0;
463 assert(handle_index < nCount);
464 return error.WaitAbandoned;
465 },
466 WAIT_TIMEOUT => return error.WaitTimeOut,
467 WAIT_FAILED => switch (GetLastError()) {
468 else => |err| return unexpectedError(err),
469 },
470 else => return error.Unexpected,
471 }
472}
473
474pub const CreateIoCompletionPortError = error{Unexpected};
475
476pub fn CreateIoCompletionPort(
477 file_handle: HANDLE,
478 existing_completion_port: ?HANDLE,
479 completion_key: usize,
480 concurrent_thread_count: DWORD,
481) CreateIoCompletionPortError!HANDLE {
482 const handle = kernel32.CreateIoCompletionPort(file_handle, existing_completion_port, completion_key, concurrent_thread_count) orelse {
483 switch (GetLastError()) {
484 .INVALID_PARAMETER => unreachable,
485 else => |err| return unexpectedError(err),
486 }
487 };
488 return handle;
489}
490
491pub const PostQueuedCompletionStatusError = error{Unexpected};
492
493pub fn PostQueuedCompletionStatus(
494 completion_port: HANDLE,
495 bytes_transferred_count: DWORD,
496 completion_key: usize,
497 lpOverlapped: ?*OVERLAPPED,
498) PostQueuedCompletionStatusError!void {
499 if (kernel32.PostQueuedCompletionStatus(completion_port, bytes_transferred_count, completion_key, lpOverlapped) == 0) {
500 switch (GetLastError()) {
501 else => |err| return unexpectedError(err),
502 }
503 }
504}
505
506pub const GetQueuedCompletionStatusResult = enum {
507 Normal,
508 Aborted,
509 Canceled,
510 EOF,
511 Timeout,
512};
513
514pub fn GetQueuedCompletionStatus(
515 completion_port: HANDLE,
516 bytes_transferred_count: *DWORD,
517 lpCompletionKey: *usize,
518 lpOverlapped: *?*OVERLAPPED,
519 dwMilliseconds: DWORD,
520) GetQueuedCompletionStatusResult {
521 if (kernel32.GetQueuedCompletionStatus(
522 completion_port,
523 bytes_transferred_count,
524 lpCompletionKey,
525 lpOverlapped,
526 dwMilliseconds,
527 ) == FALSE) {
528 switch (GetLastError()) {
529 .ABANDONED_WAIT_0 => return GetQueuedCompletionStatusResult.Aborted,
530 .OPERATION_ABORTED => return GetQueuedCompletionStatusResult.Canceled,
531 .HANDLE_EOF => return GetQueuedCompletionStatusResult.EOF,
532 .WAIT_TIMEOUT => return GetQueuedCompletionStatusResult.Timeout,
533 else => |err| {
534 if (std.debug.runtime_safety) {
535 @setEvalBranchQuota(2500);
536 std.debug.panic("unexpected error: {}\n", .{err});
537 }
538 },
539 }
540 }
541 return GetQueuedCompletionStatusResult.Normal;
542}
543
544pub const GetQueuedCompletionStatusError = error{
545 Aborted,
546 Canceled,
547 EOF,
548 Timeout,
549} || UnexpectedError;
550
551pub fn GetQueuedCompletionStatusEx(
552 completion_port: HANDLE,
553 completion_port_entries: []OVERLAPPED_ENTRY,
554 timeout_ms: ?DWORD,
555 alertable: bool,
556) GetQueuedCompletionStatusError!u32 {
557 var num_entries_removed: u32 = 0;
558
559 const success = kernel32.GetQueuedCompletionStatusEx(
560 completion_port,
561 completion_port_entries.ptr,
562 @as(ULONG, @intCast(completion_port_entries.len)),
563 &num_entries_removed,
564 timeout_ms orelse INFINITE,
565 @intFromBool(alertable),
566 );
567
568 if (success == FALSE) {
569 return switch (GetLastError()) {
570 .ABANDONED_WAIT_0 => error.Aborted,
571 .OPERATION_ABORTED => error.Canceled,
572 .HANDLE_EOF => error.EOF,
573 .WAIT_TIMEOUT => error.Timeout,
574 else => |err| unexpectedError(err),
575 };
576 }
577
578 return num_entries_removed;
579}
580
581pub fn CloseHandle(hObject: HANDLE) void {
582 assert(ntdll.NtClose(hObject) == .SUCCESS);
583}
584
585pub const ReadFileError = error{
586 BrokenPipe,
587 /// The specified network name is no longer available.
588 ConnectionResetByPeer,
589 Canceled,
590 /// Unable to read file due to lock.
591 LockViolation,
592 /// Known to be possible when:
593 /// - Unable to read from disconnected virtual com port (Windows)
594 AccessDenied,
595 NotOpenForReading,
596 Unexpected,
597};
598
599/// If buffer's length exceeds what a Windows DWORD integer can hold, it will be broken into
600/// multiple non-atomic reads.
601pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usize {
602 while (true) {
603 const want_read_count: DWORD = @min(@as(DWORD, maxInt(DWORD)), buffer.len);
604 var amt_read: DWORD = undefined;
605 var overlapped_data: OVERLAPPED = undefined;
606 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
607 overlapped_data = .{
608 .Internal = 0,
609 .InternalHigh = 0,
610 .DUMMYUNIONNAME = .{
611 .DUMMYSTRUCTNAME = .{
612 .Offset = @as(u32, @truncate(off)),
613 .OffsetHigh = @as(u32, @truncate(off >> 32)),
614 },
615 },
616 .hEvent = null,
617 };
618 break :blk &overlapped_data;
619 } else null;
620 if (kernel32.ReadFile(in_hFile, buffer.ptr, want_read_count, &amt_read, overlapped) == 0) {
621 switch (GetLastError()) {
622 .IO_PENDING => unreachable,
623 .OPERATION_ABORTED => continue,
624 .BROKEN_PIPE => return 0,
625 .HANDLE_EOF => return 0,
626 .NETNAME_DELETED => return error.ConnectionResetByPeer,
627 .LOCK_VIOLATION => return error.LockViolation,
628 .ACCESS_DENIED => return error.AccessDenied,
629 .INVALID_HANDLE => return error.NotOpenForReading,
630 else => |err| return unexpectedError(err),
631 }
632 }
633 return amt_read;
634 }
635}
636
637pub const WriteFileError = error{
638 SystemResources,
639 Canceled,
640 BrokenPipe,
641 NotOpenForWriting,
642 /// The process cannot access the file because another process has locked
643 /// a portion of the file.
644 LockViolation,
645 /// The specified network name is no longer available.
646 ConnectionResetByPeer,
647 /// Known to be possible when:
648 /// - Unable to write to disconnected virtual com port (Windows)
649 AccessDenied,
650 Unexpected,
651};
652
653pub fn WriteFile(
654 handle: HANDLE,
655 bytes: []const u8,
656 offset: ?u64,
657) WriteFileError!usize {
658 var bytes_written: DWORD = undefined;
659 var overlapped_data: OVERLAPPED = undefined;
660 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
661 overlapped_data = .{
662 .Internal = 0,
663 .InternalHigh = 0,
664 .DUMMYUNIONNAME = .{
665 .DUMMYSTRUCTNAME = .{
666 .Offset = @truncate(off),
667 .OffsetHigh = @truncate(off >> 32),
668 },
669 },
670 .hEvent = null,
671 };
672 break :blk &overlapped_data;
673 } else null;
674 const adjusted_len = math.cast(u32, bytes.len) orelse maxInt(u32);
675 if (kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, overlapped) == 0) {
676 switch (GetLastError()) {
677 .INVALID_USER_BUFFER => return error.SystemResources,
678 .NOT_ENOUGH_MEMORY => return error.SystemResources,
679 .OPERATION_ABORTED => return error.Canceled,
680 .NOT_ENOUGH_QUOTA => return error.SystemResources,
681 .IO_PENDING => unreachable,
682 .NO_DATA => return error.BrokenPipe,
683 .INVALID_HANDLE => return error.NotOpenForWriting,
684 .LOCK_VIOLATION => return error.LockViolation,
685 .NETNAME_DELETED => return error.ConnectionResetByPeer,
686 .ACCESS_DENIED => return error.AccessDenied,
687 .WORKING_SET_QUOTA => return error.SystemResources,
688 else => |err| return unexpectedError(err),
689 }
690 }
691 return bytes_written;
692}
693
694pub const SetCurrentDirectoryError = error{
695 NameTooLong,
696 FileNotFound,
697 NotDir,
698 AccessDenied,
699 NoDevice,
700 BadPathName,
701 Unexpected,
702};
703
704pub fn SetCurrentDirectory(path_name: []const u16) SetCurrentDirectoryError!void {
705 const path_len_bytes = math.cast(u16, path_name.len * 2) orelse return error.NameTooLong;
706
707 var nt_name = UNICODE_STRING{
708 .Length = path_len_bytes,
709 .MaximumLength = path_len_bytes,
710 .Buffer = @constCast(path_name.ptr),
711 };
712
713 const rc = ntdll.RtlSetCurrentDirectory_U(&nt_name);
714 switch (rc) {
715 .SUCCESS => {},
716 .OBJECT_NAME_INVALID => return error.BadPathName,
717 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
718 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
719 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
720 .INVALID_PARAMETER => unreachable,
721 .ACCESS_DENIED => return error.AccessDenied,
722 .OBJECT_PATH_SYNTAX_BAD => unreachable,
723 .NOT_A_DIRECTORY => return error.NotDir,
724 else => return unexpectedStatus(rc),
725 }
726}
727
728pub const GetCurrentDirectoryError = error{
729 NameTooLong,
730 Unexpected,
731};
732
733/// The result is a slice of `buffer`, indexed from 0.
734/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
735pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 {
736 var wtf16le_buf: [PATH_MAX_WIDE:0]u16 = undefined;
737 const result = kernel32.GetCurrentDirectoryW(wtf16le_buf.len + 1, &wtf16le_buf);
738 if (result == 0) {
739 switch (GetLastError()) {
740 else => |err| return unexpectedError(err),
741 }
742 }
743 assert(result <= wtf16le_buf.len);
744 const wtf16le_slice = wtf16le_buf[0..result];
745 var end_index: usize = 0;
746 var it = std.unicode.Wtf16LeIterator.init(wtf16le_slice);
747 while (it.nextCodepoint()) |codepoint| {
748 const seq_len = std.unicode.utf8CodepointSequenceLength(codepoint) catch unreachable;
749 if (end_index + seq_len >= buffer.len)
750 return error.NameTooLong;
751 end_index += std.unicode.wtf8Encode(codepoint, buffer[end_index..]) catch unreachable;
752 }
753 return buffer[0..end_index];
754}
755
756pub const CreateSymbolicLinkError = error{
757 AccessDenied,
758 PathAlreadyExists,
759 FileNotFound,
760 NameTooLong,
761 NoDevice,
762 NetworkNotFound,
763 BadPathName,
764 /// The volume does not contain a recognized file system. File system
765 /// drivers might not be loaded, or the volume may be corrupt.
766 UnrecognizedVolume,
767 Unexpected,
768};
769
770/// Needs either:
771/// - `SeCreateSymbolicLinkPrivilege` privilege
772/// or
773/// - Developer mode on Windows 10
774/// otherwise fails with `error.AccessDenied`. In which case `sym_link_path` may still
775/// be created on the file system but will lack reparse processing data applied to it.
776pub fn CreateSymbolicLink(
777 dir: ?HANDLE,
778 sym_link_path: []const u16,
779 target_path: [:0]const u16,
780 is_directory: bool,
781) CreateSymbolicLinkError!void {
782 const SYMLINK_DATA = extern struct {
783 ReparseTag: ULONG,
784 ReparseDataLength: USHORT,
785 Reserved: USHORT,
786 SubstituteNameOffset: USHORT,
787 SubstituteNameLength: USHORT,
788 PrintNameOffset: USHORT,
789 PrintNameLength: USHORT,
790 Flags: ULONG,
791 };
792
793 const symlink_handle = OpenFile(sym_link_path, .{
794 .access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
795 .dir = dir,
796 .creation = FILE_CREATE,
797 .filter = if (is_directory) .dir_only else .file_only,
798 }) catch |err| switch (err) {
799 error.IsDir => return error.PathAlreadyExists,
800 error.NotDir => return error.Unexpected,
801 error.WouldBlock => return error.Unexpected,
802 error.PipeBusy => return error.Unexpected,
803 error.NoDevice => return error.Unexpected,
804 error.AntivirusInterference => return error.Unexpected,
805 else => |e| return e,
806 };
807 defer CloseHandle(symlink_handle);
808
809 // Relevant portions of the documentation:
810 // > Relative links are specified using the following conventions:
811 // > - Root relative—for example, "\Windows\System32" resolves to "current drive:\Windows\System32".
812 // > - Current working directory–relative—for example, if the current working directory is
813 // > C:\Windows\System32, "C:File.txt" resolves to "C:\Windows\System32\File.txt".
814 // > Note: If you specify a current working directory–relative link, it is created as an absolute
815 // > link, due to the way the current working directory is processed based on the user and the thread.
816 // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw
817 var is_target_absolute = false;
818 const final_target_path = target_path: {
819 if (hasCommonNtPrefix(u16, target_path)) {
820 // Already an NT path, no need to do anything to it
821 break :target_path target_path;
822 } else {
823 switch (getWin32PathType(u16, target_path)) {
824 // Rooted paths need to avoid getting put through wToPrefixedFileW
825 // (and they are treated as relative in this context)
826 // Note: It seems that rooted paths in symbolic links are relative to
827 // the drive that the symbolic exists on, not to the CWD's drive.
828 // So, if the symlink is on C:\ and the CWD is on D:\,
829 // it will still resolve the path relative to the root of
830 // the C:\ drive.
831 .rooted => break :target_path target_path,
832 // Keep relative paths relative, but anything else needs to get NT-prefixed.
833 else => if (!std.fs.path.isAbsoluteWindowsWtf16(target_path))
834 break :target_path target_path,
835 }
836 }
837 var prefixed_target_path = try wToPrefixedFileW(dir, target_path);
838 // We do this after prefixing to ensure that drive-relative paths are treated as absolute
839 is_target_absolute = std.fs.path.isAbsoluteWindowsWtf16(prefixed_target_path.span());
840 break :target_path prefixed_target_path.span();
841 };
842
843 // prepare reparse data buffer
844 var buffer: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
845 const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4;
846 const header_len = @sizeOf(ULONG) + @sizeOf(USHORT) * 2;
847 const target_is_absolute = std.fs.path.isAbsoluteWindowsWtf16(final_target_path);
848 const symlink_data = SYMLINK_DATA{
849 .ReparseTag = IO_REPARSE_TAG_SYMLINK,
850 .ReparseDataLength = @intCast(buf_len - header_len),
851 .Reserved = 0,
852 .SubstituteNameOffset = @intCast(final_target_path.len * 2),
853 .SubstituteNameLength = @intCast(final_target_path.len * 2),
854 .PrintNameOffset = 0,
855 .PrintNameLength = @intCast(final_target_path.len * 2),
856 .Flags = if (!target_is_absolute) SYMLINK_FLAG_RELATIVE else 0,
857 };
858
859 @memcpy(buffer[0..@sizeOf(SYMLINK_DATA)], std.mem.asBytes(&symlink_data));
860 @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path)));
861 const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2;
862 @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path)));
863 _ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null);
864}
865
866pub const ReadLinkError = error{
867 FileNotFound,
868 NetworkNotFound,
869 AccessDenied,
870 Unexpected,
871 NameTooLong,
872 BadPathName,
873 AntivirusInterference,
874 UnsupportedReparsePointType,
875};
876
877/// `sub_path_w` will never be accessed after `out_buffer` has been written to, so it
878/// is safe to reuse a single buffer for both.
879pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u16) ReadLinkError![]u16 {
880 const result_handle = OpenFile(sub_path_w, .{
881 .access_mask = FILE_READ_ATTRIBUTES | SYNCHRONIZE,
882 .dir = dir,
883 .creation = FILE_OPEN,
884 .follow_symlinks = false,
885 .filter = .any,
886 }) catch |err| switch (err) {
887 error.IsDir, error.NotDir => return error.Unexpected, // filter = .any
888 error.PathAlreadyExists => return error.Unexpected, // FILE_OPEN
889 error.WouldBlock => return error.Unexpected,
890 error.NoDevice => return error.FileNotFound,
891 error.PipeBusy => return error.AccessDenied,
892 else => |e| return e,
893 };
894 defer CloseHandle(result_handle);
895
896 var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(REPARSE_DATA_BUFFER)) = undefined;
897 _ = DeviceIoControl(result_handle, FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]) catch |err| switch (err) {
898 error.AccessDenied => return error.Unexpected,
899 error.UnrecognizedVolume => return error.Unexpected,
900 else => |e| return e,
901 };
902
903 const reparse_struct: *const REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0]));
904 switch (reparse_struct.ReparseTag) {
905 IO_REPARSE_TAG_SYMLINK => {
906 const buf: *const SYMBOLIC_LINK_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0]));
907 const offset = buf.SubstituteNameOffset >> 1;
908 const len = buf.SubstituteNameLength >> 1;
909 const path_buf = @as([*]const u16, &buf.PathBuffer);
910 const is_relative = buf.Flags & SYMLINK_FLAG_RELATIVE != 0;
911 return parseReadLinkPath(path_buf[offset..][0..len], is_relative, out_buffer);
912 },
913 IO_REPARSE_TAG_MOUNT_POINT => {
914 const buf: *const MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0]));
915 const offset = buf.SubstituteNameOffset >> 1;
916 const len = buf.SubstituteNameLength >> 1;
917 const path_buf = @as([*]const u16, &buf.PathBuffer);
918 return parseReadLinkPath(path_buf[offset..][0..len], false, out_buffer);
919 },
920 else => {
921 return error.UnsupportedReparsePointType;
922 },
923 }
924}
925
926fn parseReadLinkPath(path: []const u16, is_relative: bool, out_buffer: []u16) error{NameTooLong}![]u16 {
927 path: {
928 if (is_relative) break :path;
929 return ntToWin32Namespace(path, out_buffer) catch |err| switch (err) {
930 error.NameTooLong => |e| return e,
931 error.NotNtPath => break :path,
932 };
933 }
934 if (out_buffer.len < path.len) return error.NameTooLong;
935 const dest = out_buffer[0..path.len];
936 @memcpy(dest, path);
937 return dest;
938}
939
940pub const DeleteFileError = error{
941 FileNotFound,
942 AccessDenied,
943 NameTooLong,
944 /// Also known as sharing violation.
945 FileBusy,
946 Unexpected,
947 NotDir,
948 IsDir,
949 DirNotEmpty,
950 NetworkNotFound,
951};
952
953pub const DeleteFileOptions = struct {
954 dir: ?HANDLE,
955 remove_dir: bool = false,
956};
957
958pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFileError!void {
959 const create_options_flags: ULONG = if (options.remove_dir)
960 FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT
961 else
962 FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT; // would we ever want to delete the target instead?
963
964 const path_len_bytes = @as(u16, @intCast(sub_path_w.len * 2));
965 var nt_name = UNICODE_STRING{
966 .Length = path_len_bytes,
967 .MaximumLength = path_len_bytes,
968 // The Windows API makes this mutable, but it will not mutate here.
969 .Buffer = @constCast(sub_path_w.ptr),
970 };
971
972 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
973 // Windows does not recognize this, but it does work with empty string.
974 nt_name.Length = 0;
975 }
976 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
977 // Can't remove the parent directory with an open handle.
978 return error.FileBusy;
979 }
980
981 var attr = OBJECT_ATTRIBUTES{
982 .Length = @sizeOf(OBJECT_ATTRIBUTES),
983 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir,
984 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
985 .ObjectName = &nt_name,
986 .SecurityDescriptor = null,
987 .SecurityQualityOfService = null,
988 };
989 var io: IO_STATUS_BLOCK = undefined;
990 var tmp_handle: HANDLE = undefined;
991 var rc = ntdll.NtCreateFile(
992 &tmp_handle,
993 SYNCHRONIZE | DELETE,
994 &attr,
995 &io,
996 null,
997 0,
998 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
999 FILE_OPEN,
1000 create_options_flags,
1001 null,
1002 0,
1003 );
1004 switch (rc) {
1005 .SUCCESS => {},
1006 .OBJECT_NAME_INVALID => unreachable,
1007 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
1008 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
1009 .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found
1010 .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't
1011 .INVALID_PARAMETER => unreachable,
1012 .FILE_IS_A_DIRECTORY => return error.IsDir,
1013 .NOT_A_DIRECTORY => return error.NotDir,
1014 .SHARING_VIOLATION => return error.FileBusy,
1015 .ACCESS_DENIED => return error.AccessDenied,
1016 .DELETE_PENDING => return,
1017 else => return unexpectedStatus(rc),
1018 }
1019 defer CloseHandle(tmp_handle);
1020
1021 // FileDispositionInformationEx has varying levels of support:
1022 // - FILE_DISPOSITION_INFORMATION_EX requires >= win10_rs1
1023 // (INVALID_INFO_CLASS is returned if not supported)
1024 // - Requires the NTFS filesystem
1025 // (on filesystems like FAT32, INVALID_PARAMETER is returned)
1026 // - FILE_DISPOSITION_POSIX_SEMANTICS requires >= win10_rs1
1027 // - FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5
1028 // (NOT_SUPPORTED is returned if a flag is unsupported)
1029 //
1030 // The strategy here is just to try using FileDispositionInformationEx and fall back to
1031 // FileDispositionInformation if the return value lets us know that some aspect of it is not supported.
1032 const need_fallback = need_fallback: {
1033 // Deletion with posix semantics if the filesystem supports it.
1034 var info = FILE_DISPOSITION_INFORMATION_EX{
1035 .Flags = FILE_DISPOSITION_DELETE |
1036 FILE_DISPOSITION_POSIX_SEMANTICS |
1037 FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE,
1038 };
1039
1040 rc = ntdll.NtSetInformationFile(
1041 tmp_handle,
1042 &io,
1043 &info,
1044 @sizeOf(FILE_DISPOSITION_INFORMATION_EX),
1045 .FileDispositionInformationEx,
1046 );
1047 switch (rc) {
1048 .SUCCESS => return,
1049 // The filesystem does not support FileDispositionInformationEx
1050 .INVALID_PARAMETER,
1051 // The operating system does not support FileDispositionInformationEx
1052 .INVALID_INFO_CLASS,
1053 // The operating system does not support one of the flags
1054 .NOT_SUPPORTED,
1055 => break :need_fallback true,
1056 // For all other statuses, fall down to the switch below to handle them.
1057 else => break :need_fallback false,
1058 }
1059 };
1060
1061 if (need_fallback) {
1062 // Deletion with file pending semantics, which requires waiting or moving
1063 // files to get them removed (from here).
1064 var file_dispo = FILE_DISPOSITION_INFORMATION{
1065 .DeleteFile = TRUE,
1066 };
1067
1068 rc = ntdll.NtSetInformationFile(
1069 tmp_handle,
1070 &io,
1071 &file_dispo,
1072 @sizeOf(FILE_DISPOSITION_INFORMATION),
1073 .FileDispositionInformation,
1074 );
1075 }
1076 switch (rc) {
1077 .SUCCESS => {},
1078 .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,
1079 .INVALID_PARAMETER => unreachable,
1080 .CANNOT_DELETE => return error.AccessDenied,
1081 .MEDIA_WRITE_PROTECTED => return error.AccessDenied,
1082 .ACCESS_DENIED => return error.AccessDenied,
1083 else => return unexpectedStatus(rc),
1084 }
1085}
1086
1087pub const RenameError = error{
1088 IsDir,
1089 NotDir,
1090 FileNotFound,
1091 NoDevice,
1092 AccessDenied,
1093 PipeBusy,
1094 PathAlreadyExists,
1095 Unexpected,
1096 NameTooLong,
1097 NetworkNotFound,
1098 AntivirusInterference,
1099 BadPathName,
1100 RenameAcrossMountPoints,
1101} || UnexpectedError;
1102
1103pub fn RenameFile(
1104 /// May only be `null` if `old_path_w` is a fully-qualified absolute path.
1105 old_dir_fd: ?HANDLE,
1106 old_path_w: []const u16,
1107 /// May only be `null` if `new_path_w` is a fully-qualified absolute path,
1108 /// or if the file is not being moved to a different directory.
1109 new_dir_fd: ?HANDLE,
1110 new_path_w: []const u16,
1111 replace_if_exists: bool,
1112) RenameError!void {
1113 const src_fd = OpenFile(old_path_w, .{
1114 .dir = old_dir_fd,
1115 .access_mask = SYNCHRONIZE | GENERIC_WRITE | DELETE,
1116 .creation = FILE_OPEN,
1117 .filter = .any, // This function is supposed to rename both files and directories.
1118 .follow_symlinks = false,
1119 }) catch |err| switch (err) {
1120 error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.
1121 else => |e| return e,
1122 };
1123 defer CloseHandle(src_fd);
1124
1125 var rc: NTSTATUS = undefined;
1126 // FileRenameInformationEx has varying levels of support:
1127 // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1
1128 // (INVALID_INFO_CLASS is returned if not supported)
1129 // - Requires the NTFS filesystem
1130 // (on filesystems like FAT32, INVALID_PARAMETER is returned)
1131 // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1
1132 // - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5
1133 // (NOT_SUPPORTED is returned if a flag is unsupported)
1134 //
1135 // The strategy here is just to try using FileRenameInformationEx and fall back to
1136 // FileRenameInformation if the return value lets us know that some aspect of it is not supported.
1137 const need_fallback = need_fallback: {
1138 const struct_buf_len = @sizeOf(FILE_RENAME_INFORMATION_EX) + (PATH_MAX_WIDE * 2);
1139 var rename_info_buf: [struct_buf_len]u8 align(@alignOf(FILE_RENAME_INFORMATION_EX)) = undefined;
1140 const struct_len = @sizeOf(FILE_RENAME_INFORMATION_EX) + new_path_w.len * 2;
1141 if (struct_len > struct_buf_len) return error.NameTooLong;
1142
1143 const rename_info: *FILE_RENAME_INFORMATION_EX = @ptrCast(&rename_info_buf);
1144 var io_status_block: IO_STATUS_BLOCK = undefined;
1145
1146 var flags: ULONG = FILE_RENAME_POSIX_SEMANTICS | FILE_RENAME_IGNORE_READONLY_ATTRIBUTE;
1147 if (replace_if_exists) flags |= FILE_RENAME_REPLACE_IF_EXISTS;
1148 rename_info.* = .{
1149 .Flags = flags,
1150 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir_fd,
1151 .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong
1152 .FileName = undefined,
1153 };
1154 @memcpy((&rename_info.FileName).ptr, new_path_w);
1155 rc = ntdll.NtSetInformationFile(
1156 src_fd,
1157 &io_status_block,
1158 rename_info,
1159 @intCast(struct_len), // already checked for error.NameTooLong
1160 .FileRenameInformationEx,
1161 );
1162 switch (rc) {
1163 .SUCCESS => return,
1164 // The filesystem does not support FileDispositionInformationEx
1165 .INVALID_PARAMETER,
1166 // The operating system does not support FileDispositionInformationEx
1167 .INVALID_INFO_CLASS,
1168 // The operating system does not support one of the flags
1169 .NOT_SUPPORTED,
1170 => break :need_fallback true,
1171 // For all other statuses, fall down to the switch below to handle them.
1172 else => break :need_fallback false,
1173 }
1174 };
1175
1176 if (need_fallback) {
1177 const struct_buf_len = @sizeOf(FILE_RENAME_INFORMATION) + (PATH_MAX_WIDE * 2);
1178 var rename_info_buf: [struct_buf_len]u8 align(@alignOf(FILE_RENAME_INFORMATION)) = undefined;
1179 const struct_len = @sizeOf(FILE_RENAME_INFORMATION) + new_path_w.len * 2;
1180 if (struct_len > struct_buf_len) return error.NameTooLong;
1181
1182 const rename_info: *FILE_RENAME_INFORMATION = @ptrCast(&rename_info_buf);
1183 var io_status_block: IO_STATUS_BLOCK = undefined;
1184
1185 rename_info.* = .{
1186 .Flags = @intFromBool(replace_if_exists),
1187 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir_fd,
1188 .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong
1189 .FileName = undefined,
1190 };
1191 @memcpy((&rename_info.FileName).ptr, new_path_w);
1192
1193 rc = ntdll.NtSetInformationFile(
1194 src_fd,
1195 &io_status_block,
1196 rename_info,
1197 @intCast(struct_len), // already checked for error.NameTooLong
1198 .FileRenameInformation,
1199 );
1200 }
1201
1202 switch (rc) {
1203 .SUCCESS => {},
1204 .INVALID_HANDLE => unreachable,
1205 .INVALID_PARAMETER => unreachable,
1206 .OBJECT_PATH_SYNTAX_BAD => unreachable,
1207 .ACCESS_DENIED => return error.AccessDenied,
1208 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
1209 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
1210 .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints,
1211 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
1212 .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists,
1213 .FILE_IS_A_DIRECTORY => return error.IsDir,
1214 .NOT_A_DIRECTORY => return error.NotDir,
1215 else => return unexpectedStatus(rc),
1216 }
1217}
1218
1219pub const GetStdHandleError = error{
1220 NoStandardHandleAttached,
1221 Unexpected,
1222};
1223
1224pub fn GetStdHandle(handle_id: DWORD) GetStdHandleError!HANDLE {
1225 const handle = kernel32.GetStdHandle(handle_id) orelse return error.NoStandardHandleAttached;
1226 if (handle == INVALID_HANDLE_VALUE) {
1227 switch (GetLastError()) {
1228 else => |err| return unexpectedError(err),
1229 }
1230 }
1231 return handle;
1232}
1233
1234pub const SetFilePointerError = error{
1235 Unseekable,
1236 Unexpected,
1237};
1238
1239/// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_BEGIN`.
1240pub fn SetFilePointerEx_BEGIN(handle: HANDLE, offset: u64) SetFilePointerError!void {
1241 // "The starting point is zero or the beginning of the file. If [FILE_BEGIN]
1242 // is specified, then the liDistanceToMove parameter is interpreted as an unsigned value."
1243 // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointerex
1244 const ipos = @as(LARGE_INTEGER, @bitCast(offset));
1245 if (kernel32.SetFilePointerEx(handle, ipos, null, FILE_BEGIN) == 0) {
1246 switch (GetLastError()) {
1247 .INVALID_FUNCTION => return error.Unseekable,
1248 .NEGATIVE_SEEK => return error.Unseekable,
1249 .INVALID_PARAMETER => unreachable,
1250 .INVALID_HANDLE => unreachable,
1251 else => |err| return unexpectedError(err),
1252 }
1253 }
1254}
1255
1256/// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_CURRENT`.
1257pub fn SetFilePointerEx_CURRENT(handle: HANDLE, offset: i64) SetFilePointerError!void {
1258 if (kernel32.SetFilePointerEx(handle, offset, null, FILE_CURRENT) == 0) {
1259 switch (GetLastError()) {
1260 .INVALID_FUNCTION => return error.Unseekable,
1261 .NEGATIVE_SEEK => return error.Unseekable,
1262 .INVALID_PARAMETER => unreachable,
1263 .INVALID_HANDLE => unreachable,
1264 else => |err| return unexpectedError(err),
1265 }
1266 }
1267}
1268
1269/// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_END`.
1270pub fn SetFilePointerEx_END(handle: HANDLE, offset: i64) SetFilePointerError!void {
1271 if (kernel32.SetFilePointerEx(handle, offset, null, FILE_END) == 0) {
1272 switch (GetLastError()) {
1273 .INVALID_FUNCTION => return error.Unseekable,
1274 .NEGATIVE_SEEK => return error.Unseekable,
1275 .INVALID_PARAMETER => unreachable,
1276 .INVALID_HANDLE => unreachable,
1277 else => |err| return unexpectedError(err),
1278 }
1279 }
1280}
1281
1282/// The SetFilePointerEx function with parameters to get the current offset.
1283pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 {
1284 var result: LARGE_INTEGER = undefined;
1285 if (kernel32.SetFilePointerEx(handle, 0, &result, FILE_CURRENT) == 0) {
1286 switch (GetLastError()) {
1287 .INVALID_FUNCTION => return error.Unseekable,
1288 .NEGATIVE_SEEK => return error.Unseekable,
1289 .INVALID_PARAMETER => unreachable,
1290 .INVALID_HANDLE => unreachable,
1291 else => |err| return unexpectedError(err),
1292 }
1293 }
1294 // Based on the docs for FILE_BEGIN, it seems that the returned signed integer
1295 // should be interpreted as an unsigned integer.
1296 return @as(u64, @bitCast(result));
1297}
1298
1299pub const QueryObjectNameError = error{
1300 AccessDenied,
1301 InvalidHandle,
1302 NameTooLong,
1303 Unexpected,
1304};
1305
1306pub fn QueryObjectName(handle: HANDLE, out_buffer: []u16) QueryObjectNameError![]u16 {
1307 const out_buffer_aligned = mem.alignInSlice(out_buffer, @alignOf(OBJECT_NAME_INFORMATION)) orelse return error.NameTooLong;
1308
1309 const info = @as(*OBJECT_NAME_INFORMATION, @ptrCast(out_buffer_aligned));
1310 // buffer size is specified in bytes
1311 const out_buffer_len = std.math.cast(ULONG, out_buffer_aligned.len * 2) orelse std.math.maxInt(ULONG);
1312 // last argument would return the length required for full_buffer, not exposed here
1313 return switch (ntdll.NtQueryObject(handle, .ObjectNameInformation, info, out_buffer_len, null)) {
1314 .SUCCESS => blk: {
1315 // info.Name.Buffer from ObQueryNameString is documented to be null (and MaximumLength == 0)
1316 // if the object was "unnamed", not sure if this can happen for file handles
1317 if (info.Name.MaximumLength == 0) break :blk error.Unexpected;
1318 // resulting string length is specified in bytes
1319 const path_length_unterminated = @divExact(info.Name.Length, 2);
1320 break :blk info.Name.Buffer.?[0..path_length_unterminated];
1321 },
1322 .ACCESS_DENIED => error.AccessDenied,
1323 .INVALID_HANDLE => error.InvalidHandle,
1324 // triggered when the buffer is too small for the OBJECT_NAME_INFORMATION object (.INFO_LENGTH_MISMATCH),
1325 // or if the buffer is too small for the file path returned (.BUFFER_OVERFLOW, .BUFFER_TOO_SMALL)
1326 .INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => error.NameTooLong,
1327 else => |e| unexpectedStatus(e),
1328 };
1329}
1330
1331test QueryObjectName {
1332 if (builtin.os.tag != .windows)
1333 return;
1334
1335 //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths.
1336 var tmp = std.testing.tmpDir(.{});
1337 defer tmp.cleanup();
1338 const handle = tmp.dir.fd;
1339 var out_buffer: [PATH_MAX_WIDE]u16 = undefined;
1340
1341 const result_path = try QueryObjectName(handle, &out_buffer);
1342 const required_len_in_u16 = result_path.len + @divExact(@intFromPtr(result_path.ptr) - @intFromPtr(&out_buffer), 2) + 1;
1343 //insufficient size
1344 try std.testing.expectError(error.NameTooLong, QueryObjectName(handle, out_buffer[0 .. required_len_in_u16 - 1]));
1345 //exactly-sufficient size
1346 _ = try QueryObjectName(handle, out_buffer[0..required_len_in_u16]);
1347}
1348
1349pub const GetFinalPathNameByHandleError = error{
1350 AccessDenied,
1351 BadPathName,
1352 FileNotFound,
1353 NameTooLong,
1354 /// The volume does not contain a recognized file system. File system
1355 /// drivers might not be loaded, or the volume may be corrupt.
1356 UnrecognizedVolume,
1357 Unexpected,
1358};
1359
1360/// Specifies how to format volume path in the result of `GetFinalPathNameByHandle`.
1361/// Defaults to DOS volume names.
1362pub const GetFinalPathNameByHandleFormat = struct {
1363 volume_name: enum {
1364 /// Format as DOS volume name
1365 Dos,
1366 /// Format as NT volume name
1367 Nt,
1368 } = .Dos,
1369};
1370
1371/// Returns canonical (normalized) path of handle.
1372/// Use `GetFinalPathNameByHandleFormat` to specify whether the path is meant to include
1373/// NT or DOS volume name (e.g., `\Device\HarddiskVolume0\foo.txt` versus `C:\foo.txt`).
1374/// If DOS volume name format is selected, note that this function does *not* prepend
1375/// `\\?\` prefix to the resultant path.
1376pub fn GetFinalPathNameByHandle(
1377 hFile: HANDLE,
1378 fmt: GetFinalPathNameByHandleFormat,
1379 out_buffer: []u16,
1380) GetFinalPathNameByHandleError![]u16 {
1381 const final_path = QueryObjectName(hFile, out_buffer) catch |err| switch (err) {
1382 // we assume InvalidHandle is close enough to FileNotFound in semantics
1383 // to not further complicate the error set
1384 error.InvalidHandle => return error.FileNotFound,
1385 else => |e| return e,
1386 };
1387
1388 switch (fmt.volume_name) {
1389 .Nt => {
1390 // the returned path is already in .Nt format
1391 return final_path;
1392 },
1393 .Dos => {
1394 // parse the string to separate volume path from file path
1395 const device_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\");
1396
1397 // We aren't entirely sure of the structure of the path returned by
1398 // QueryObjectName in all contexts/environments.
1399 // This code is written to cover the various cases that have
1400 // been encountered and solved appropriately. But note that there's
1401 // no easy way to verify that they have all been tackled!
1402 // (Unless you, the reader knows of one then please do action that!)
1403 if (!mem.startsWith(u16, final_path, device_prefix)) {
1404 // Wine seems to return NT namespaced paths starting with \??\ from QueryObjectName
1405 // (e.g. `\??\Z:\some\path\to\a\file.txt`), in which case we can just strip the
1406 // prefix to turn it into an absolute path.
1407 // https://github.com/ziglang/zig/issues/26029
1408 // https://bugs.winehq.org/show_bug.cgi?id=39569
1409 return ntToWin32Namespace(final_path, out_buffer) catch |err| switch (err) {
1410 error.NotNtPath => return error.Unexpected,
1411 error.NameTooLong => |e| return e,
1412 };
1413 }
1414
1415 const file_path_begin_index = mem.indexOfPos(u16, final_path, device_prefix.len, &[_]u16{'\\'}) orelse unreachable;
1416 const volume_name_u16 = final_path[0..file_path_begin_index];
1417 const device_name_u16 = volume_name_u16[device_prefix.len..];
1418 const file_name_u16 = final_path[file_path_begin_index..];
1419
1420 // MUP is Multiple UNC Provider, and indicates that the path is a UNC
1421 // path. In this case, the canonical UNC path can be gotten by just
1422 // dropping the \Device\Mup\ and making sure the path begins with \\
1423 if (mem.eql(u16, device_name_u16, std.unicode.utf8ToUtf16LeStringLiteral("Mup"))) {
1424 out_buffer[0] = '\\';
1425 @memmove(out_buffer[1..][0..file_name_u16.len], file_name_u16);
1426 return out_buffer[0 .. 1 + file_name_u16.len];
1427 }
1428
1429 // Get DOS volume name. DOS volume names are actually symbolic link objects to the
1430 // actual NT volume. For example:
1431 // (NT) \Device\HarddiskVolume4 => (DOS) \DosDevices\C: == (DOS) C:
1432 const MIN_SIZE = @sizeOf(MOUNTMGR_MOUNT_POINT) + MAX_PATH;
1433 // We initialize the input buffer to all zeros for convenience since
1434 // `DeviceIoControl` with `IOCTL_MOUNTMGR_QUERY_POINTS` expects this.
1435 var input_buf: [MIN_SIZE]u8 align(@alignOf(MOUNTMGR_MOUNT_POINT)) = [_]u8{0} ** MIN_SIZE;
1436 var output_buf: [MIN_SIZE * 4]u8 align(@alignOf(MOUNTMGR_MOUNT_POINTS)) = undefined;
1437
1438 // This surprising path is a filesystem path to the mount manager on Windows.
1439 // Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points
1440 // This is the NT namespaced version of \\.\MountPointManager
1441 const mgmt_path_u16 = std.unicode.utf8ToUtf16LeStringLiteral("\\??\\MountPointManager");
1442 const mgmt_handle = OpenFile(mgmt_path_u16, .{
1443 .access_mask = SYNCHRONIZE,
1444 .share_access = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1445 .creation = FILE_OPEN,
1446 }) catch |err| switch (err) {
1447 error.IsDir => return error.Unexpected,
1448 error.NotDir => return error.Unexpected,
1449 error.NoDevice => return error.Unexpected,
1450 error.AccessDenied => return error.Unexpected,
1451 error.PipeBusy => return error.Unexpected,
1452 error.PathAlreadyExists => return error.Unexpected,
1453 error.WouldBlock => return error.Unexpected,
1454 error.NetworkNotFound => return error.Unexpected,
1455 error.AntivirusInterference => return error.Unexpected,
1456 else => |e| return e,
1457 };
1458 defer CloseHandle(mgmt_handle);
1459
1460 var input_struct: *MOUNTMGR_MOUNT_POINT = @ptrCast(&input_buf[0]);
1461 input_struct.DeviceNameOffset = @sizeOf(MOUNTMGR_MOUNT_POINT);
1462 input_struct.DeviceNameLength = @intCast(volume_name_u16.len * 2);
1463 @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..][0 .. volume_name_u16.len * 2], @as([*]const u8, @ptrCast(volume_name_u16.ptr)));
1464
1465 DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, &input_buf, &output_buf) catch |err| switch (err) {
1466 error.AccessDenied => return error.Unexpected,
1467 else => |e| return e,
1468 };
1469 const mount_points_struct: *const MOUNTMGR_MOUNT_POINTS = @ptrCast(&output_buf[0]);
1470
1471 const mount_points = @as(
1472 [*]const MOUNTMGR_MOUNT_POINT,
1473 @ptrCast(&mount_points_struct.MountPoints[0]),
1474 )[0..mount_points_struct.NumberOfMountPoints];
1475
1476 for (mount_points) |mount_point| {
1477 const symlink = @as(
1478 [*]const u16,
1479 @ptrCast(@alignCast(&output_buf[mount_point.SymbolicLinkNameOffset])),
1480 )[0 .. mount_point.SymbolicLinkNameLength / 2];
1481
1482 // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks
1483 // with traditional DOS drive letters, so pick the first one available.
1484 var prefix_buf = std.unicode.utf8ToUtf16LeStringLiteral("\\DosDevices\\");
1485 const prefix = prefix_buf[0..prefix_buf.len];
1486
1487 if (mem.startsWith(u16, symlink, prefix)) {
1488 const drive_letter = symlink[prefix.len..];
1489
1490 if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong;
1491
1492 @memcpy(out_buffer[0..drive_letter.len], drive_letter);
1493 @memmove(out_buffer[drive_letter.len..][0..file_name_u16.len], file_name_u16);
1494 const total_len = drive_letter.len + file_name_u16.len;
1495
1496 // Validate that DOS does not contain any spurious nul bytes.
1497 if (mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| {
1498 return error.BadPathName;
1499 }
1500
1501 return out_buffer[0..total_len];
1502 } else if (mountmgrIsVolumeName(symlink)) {
1503 // If the symlink is a volume GUID like \??\Volume{383da0b0-717f-41b6-8c36-00500992b58d},
1504 // then it is a volume mounted as a path rather than a drive letter. We need to
1505 // query the mount manager again to get the DOS path for the volume.
1506
1507 // 49 is the maximum length accepted by mountmgrIsVolumeName
1508 const vol_input_size = @sizeOf(MOUNTMGR_TARGET_NAME) + (49 * 2);
1509 var vol_input_buf: [vol_input_size]u8 align(@alignOf(MOUNTMGR_TARGET_NAME)) = [_]u8{0} ** vol_input_size;
1510 // Note: If the path exceeds MAX_PATH, the Disk Management GUI doesn't accept the full path,
1511 // and instead if must be specified using a shortened form (e.g. C:\FOO~1\BAR~1\<...>).
1512 // However, just to be sure we can handle any path length, we use PATH_MAX_WIDE here.
1513 const min_output_size = @sizeOf(MOUNTMGR_VOLUME_PATHS) + (PATH_MAX_WIDE * 2);
1514 var vol_output_buf: [min_output_size]u8 align(@alignOf(MOUNTMGR_VOLUME_PATHS)) = undefined;
1515
1516 var vol_input_struct: *MOUNTMGR_TARGET_NAME = @ptrCast(&vol_input_buf[0]);
1517 vol_input_struct.DeviceNameLength = @intCast(symlink.len * 2);
1518 @memcpy(@as([*]WCHAR, &vol_input_struct.DeviceName)[0..symlink.len], symlink);
1519
1520 DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH, &vol_input_buf, &vol_output_buf) catch |err| switch (err) {
1521 error.AccessDenied => return error.Unexpected,
1522 else => |e| return e,
1523 };
1524 const volume_paths_struct: *const MOUNTMGR_VOLUME_PATHS = @ptrCast(&vol_output_buf[0]);
1525 const volume_path = std.mem.sliceTo(@as(
1526 [*]const u16,
1527 &volume_paths_struct.MultiSz,
1528 )[0 .. volume_paths_struct.MultiSzLength / 2], 0);
1529
1530 if (out_buffer.len < volume_path.len + file_name_u16.len) return error.NameTooLong;
1531
1532 // `out_buffer` currently contains the memory of `file_name_u16`, so it can overlap with where
1533 // we want to place the filename before returning. Here are the possible overlapping cases:
1534 //
1535 // out_buffer: [filename]
1536 // dest: [___(a)___] [___(b)___]
1537 //
1538 // In the case of (a), we need to copy forwards, and in the case of (b) we need
1539 // to copy backwards. We also need to do this before copying the volume path because
1540 // it could overwrite the file_name_u16 memory.
1541 const file_name_dest = out_buffer[volume_path.len..][0..file_name_u16.len];
1542 @memmove(file_name_dest, file_name_u16);
1543 @memcpy(out_buffer[0..volume_path.len], volume_path);
1544 const total_len = volume_path.len + file_name_u16.len;
1545
1546 // Validate that DOS does not contain any spurious nul bytes.
1547 if (mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| {
1548 return error.BadPathName;
1549 }
1550
1551 return out_buffer[0..total_len];
1552 }
1553 }
1554
1555 // If we've ended up here, then something went wrong/is corrupted in the OS,
1556 // so error out!
1557 return error.FileNotFound;
1558 },
1559 }
1560}
1561
1562/// Equivalent to the MOUNTMGR_IS_VOLUME_NAME macro in mountmgr.h
1563fn mountmgrIsVolumeName(name: []const u16) bool {
1564 return (name.len == 48 or (name.len == 49 and name[48] == mem.nativeToLittle(u16, '\\'))) and
1565 name[0] == mem.nativeToLittle(u16, '\\') and
1566 (name[1] == mem.nativeToLittle(u16, '?') or name[1] == mem.nativeToLittle(u16, '\\')) and
1567 name[2] == mem.nativeToLittle(u16, '?') and
1568 name[3] == mem.nativeToLittle(u16, '\\') and
1569 mem.startsWith(u16, name[4..], std.unicode.utf8ToUtf16LeStringLiteral("Volume{")) and
1570 name[19] == mem.nativeToLittle(u16, '-') and
1571 name[24] == mem.nativeToLittle(u16, '-') and
1572 name[29] == mem.nativeToLittle(u16, '-') and
1573 name[34] == mem.nativeToLittle(u16, '-') and
1574 name[47] == mem.nativeToLittle(u16, '}');
1575}
1576
1577test mountmgrIsVolumeName {
1578 @setEvalBranchQuota(2000);
1579 const L = std.unicode.utf8ToUtf16LeStringLiteral;
1580 try std.testing.expect(mountmgrIsVolumeName(L("\\\\?\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}")));
1581 try std.testing.expect(mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}")));
1582 try std.testing.expect(mountmgrIsVolumeName(L("\\\\?\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\")));
1583 try std.testing.expect(mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\")));
1584 try std.testing.expect(!mountmgrIsVolumeName(L("\\\\.\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}")));
1585 try std.testing.expect(!mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\foo")));
1586 try std.testing.expect(!mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58}")));
1587}
1588
1589test GetFinalPathNameByHandle {
1590 if (builtin.os.tag != .windows)
1591 return;
1592
1593 //any file will do
1594 var tmp = std.testing.tmpDir(.{});
1595 defer tmp.cleanup();
1596 const handle = tmp.dir.fd;
1597 var buffer: [PATH_MAX_WIDE]u16 = undefined;
1598
1599 //check with sufficient size
1600 const nt_path = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, &buffer);
1601 _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, &buffer);
1602
1603 const required_len_in_u16 = nt_path.len + @divExact(@intFromPtr(nt_path.ptr) - @intFromPtr(&buffer), 2) + 1;
1604 //check with insufficient size
1605 try std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0 .. required_len_in_u16 - 1]));
1606 try std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0 .. required_len_in_u16 - 1]));
1607
1608 //check with exactly-sufficient size
1609 _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0..required_len_in_u16]);
1610 _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..required_len_in_u16]);
1611}
1612
1613pub const GetFileSizeError = error{Unexpected};
1614
1615pub fn GetFileSizeEx(hFile: HANDLE) GetFileSizeError!u64 {
1616 var file_size: LARGE_INTEGER = undefined;
1617 if (kernel32.GetFileSizeEx(hFile, &file_size) == 0) {
1618 switch (GetLastError()) {
1619 else => |err| return unexpectedError(err),
1620 }
1621 }
1622 return @as(u64, @bitCast(file_size));
1623}
1624
1625pub fn getpeername(s: ws2_32.SOCKET, name: *ws2_32.sockaddr, namelen: *ws2_32.socklen_t) i32 {
1626 return ws2_32.getpeername(s, name, @as(*i32, @ptrCast(namelen)));
1627}
1628
1629pub fn sendmsg(
1630 s: ws2_32.SOCKET,
1631 msg: *ws2_32.WSAMSG_const,
1632 flags: u32,
1633) i32 {
1634 var bytes_send: DWORD = undefined;
1635 if (ws2_32.WSASendMsg(s, msg, flags, &bytes_send, null, null) == ws2_32.SOCKET_ERROR) {
1636 return ws2_32.SOCKET_ERROR;
1637 } else {
1638 return @as(i32, @as(u31, @intCast(bytes_send)));
1639 }
1640}
1641
1642pub fn sendto(s: ws2_32.SOCKET, buf: [*]const u8, len: usize, flags: u32, to: ?*const ws2_32.sockaddr, to_len: ws2_32.socklen_t) i32 {
1643 var buffer = ws2_32.WSABUF{ .len = @as(u31, @truncate(len)), .buf = @constCast(buf) };
1644 var bytes_send: DWORD = undefined;
1645 if (ws2_32.WSASendTo(s, @as([*]ws2_32.WSABUF, @ptrCast(&buffer)), 1, &bytes_send, flags, to, @as(i32, @intCast(to_len)), null, null) == ws2_32.SOCKET_ERROR) {
1646 return ws2_32.SOCKET_ERROR;
1647 } else {
1648 return @as(i32, @as(u31, @intCast(bytes_send)));
1649 }
1650}
1651
1652pub fn recvfrom(s: ws2_32.SOCKET, buf: [*]u8, len: usize, flags: u32, from: ?*ws2_32.sockaddr, from_len: ?*ws2_32.socklen_t) i32 {
1653 var buffer = ws2_32.WSABUF{ .len = @as(u31, @truncate(len)), .buf = buf };
1654 var bytes_received: DWORD = undefined;
1655 var flags_inout = flags;
1656 if (ws2_32.WSARecvFrom(s, @as([*]ws2_32.WSABUF, @ptrCast(&buffer)), 1, &bytes_received, &flags_inout, from, @as(?*i32, @ptrCast(from_len)), null, null) == ws2_32.SOCKET_ERROR) {
1657 return ws2_32.SOCKET_ERROR;
1658 } else {
1659 return @as(i32, @as(u31, @intCast(bytes_received)));
1660 }
1661}
1662
1663pub fn poll(fds: [*]ws2_32.pollfd, n: c_ulong, timeout: i32) i32 {
1664 return ws2_32.WSAPoll(fds, n, timeout);
1665}
1666
1667pub fn WSAIoctl(
1668 s: ws2_32.SOCKET,
1669 dwIoControlCode: DWORD,
1670 inBuffer: ?[]const u8,
1671 outBuffer: []u8,
1672 overlapped: ?*OVERLAPPED,
1673 completionRoutine: ?ws2_32.LPWSAOVERLAPPED_COMPLETION_ROUTINE,
1674) !DWORD {
1675 var bytes: DWORD = undefined;
1676 switch (ws2_32.WSAIoctl(
1677 s,
1678 dwIoControlCode,
1679 if (inBuffer) |i| i.ptr else null,
1680 if (inBuffer) |i| @as(DWORD, @intCast(i.len)) else 0,
1681 outBuffer.ptr,
1682 @as(DWORD, @intCast(outBuffer.len)),
1683 &bytes,
1684 overlapped,
1685 completionRoutine,
1686 )) {
1687 0 => {},
1688 ws2_32.SOCKET_ERROR => switch (ws2_32.WSAGetLastError()) {
1689 else => |err| return unexpectedWSAError(err),
1690 },
1691 else => unreachable,
1692 }
1693 return bytes;
1694}
1695
1696const GetModuleFileNameError = error{Unexpected};
1697
1698pub fn GetModuleFileNameW(hModule: ?HMODULE, buf_ptr: [*]u16, buf_len: DWORD) GetModuleFileNameError![:0]u16 {
1699 const rc = kernel32.GetModuleFileNameW(hModule, buf_ptr, buf_len);
1700 if (rc == 0) {
1701 switch (GetLastError()) {
1702 else => |err| return unexpectedError(err),
1703 }
1704 }
1705 return buf_ptr[0..rc :0];
1706}
1707
1708pub const TerminateProcessError = error{ AccessDenied, Unexpected };
1709
1710pub fn TerminateProcess(hProcess: HANDLE, uExitCode: UINT) TerminateProcessError!void {
1711 if (kernel32.TerminateProcess(hProcess, uExitCode) == 0) {
1712 switch (GetLastError()) {
1713 Win32Error.ACCESS_DENIED => return error.AccessDenied,
1714 else => |err| return unexpectedError(err),
1715 }
1716 }
1717}
1718
1719pub const NtAllocateVirtualMemoryError = error{
1720 AccessDenied,
1721 InvalidParameter,
1722 NoMemory,
1723 Unexpected,
1724};
1725
1726pub fn NtAllocateVirtualMemory(hProcess: HANDLE, addr: ?*PVOID, zero_bits: ULONG_PTR, size: ?*SIZE_T, alloc_type: ULONG, protect: ULONG) NtAllocateVirtualMemoryError!void {
1727 return switch (ntdll.NtAllocateVirtualMemory(hProcess, addr, zero_bits, size, alloc_type, protect)) {
1728 .SUCCESS => return,
1729 .ACCESS_DENIED => NtAllocateVirtualMemoryError.AccessDenied,
1730 .INVALID_PARAMETER => NtAllocateVirtualMemoryError.InvalidParameter,
1731 .NO_MEMORY => NtAllocateVirtualMemoryError.NoMemory,
1732 else => |st| unexpectedStatus(st),
1733 };
1734}
1735
1736pub const NtFreeVirtualMemoryError = error{
1737 AccessDenied,
1738 InvalidParameter,
1739 Unexpected,
1740};
1741
1742pub fn NtFreeVirtualMemory(hProcess: HANDLE, addr: ?*PVOID, size: *SIZE_T, free_type: ULONG) NtFreeVirtualMemoryError!void {
1743 // TODO: If the return value is .INVALID_PAGE_PROTECTION, call RtlFlushSecureMemoryCache and try again.
1744 return switch (ntdll.NtFreeVirtualMemory(hProcess, addr, size, free_type)) {
1745 .SUCCESS => return,
1746 .ACCESS_DENIED => NtFreeVirtualMemoryError.AccessDenied,
1747 .INVALID_PARAMETER => NtFreeVirtualMemoryError.InvalidParameter,
1748 else => NtFreeVirtualMemoryError.Unexpected,
1749 };
1750}
1751
1752pub const VirtualProtectError = error{
1753 InvalidAddress,
1754 Unexpected,
1755};
1756
1757pub fn VirtualProtect(lpAddress: ?LPVOID, dwSize: SIZE_T, flNewProtect: DWORD, lpflOldProtect: *DWORD) VirtualProtectError!void {
1758 // ntdll takes an extra level of indirection here
1759 var addr = lpAddress;
1760 var size = dwSize;
1761 switch (ntdll.NtProtectVirtualMemory(self_process_handle, &addr, &size, flNewProtect, lpflOldProtect)) {
1762 .SUCCESS => {},
1763 .INVALID_ADDRESS => return error.InvalidAddress,
1764 else => |st| return unexpectedStatus(st),
1765 }
1766}
1767
1768pub fn VirtualProtectEx(handle: HANDLE, addr: ?LPVOID, size: SIZE_T, new_prot: DWORD) VirtualProtectError!DWORD {
1769 var old_prot: DWORD = undefined;
1770 var out_addr = addr;
1771 var out_size = size;
1772 switch (ntdll.NtProtectVirtualMemory(
1773 handle,
1774 &out_addr,
1775 &out_size,
1776 new_prot,
1777 &old_prot,
1778 )) {
1779 .SUCCESS => return old_prot,
1780 .INVALID_ADDRESS => return error.InvalidAddress,
1781 // TODO: map errors
1782 else => |rc| return unexpectedStatus(rc),
1783 }
1784}
1785
1786pub const SetConsoleTextAttributeError = error{Unexpected};
1787
1788pub fn SetConsoleTextAttribute(hConsoleOutput: HANDLE, wAttributes: WORD) SetConsoleTextAttributeError!void {
1789 if (kernel32.SetConsoleTextAttribute(hConsoleOutput, wAttributes) == 0) {
1790 switch (GetLastError()) {
1791 else => |err| return unexpectedError(err),
1792 }
1793 }
1794}
1795
1796pub fn SetConsoleCtrlHandler(handler_routine: ?HANDLER_ROUTINE, add: bool) !void {
1797 const success = kernel32.SetConsoleCtrlHandler(
1798 handler_routine,
1799 if (add) TRUE else FALSE,
1800 );
1801
1802 if (success == FALSE) {
1803 return switch (GetLastError()) {
1804 else => |err| unexpectedError(err),
1805 };
1806 }
1807}
1808
1809pub fn SetFileCompletionNotificationModes(handle: HANDLE, flags: UCHAR) !void {
1810 const success = kernel32.SetFileCompletionNotificationModes(handle, flags);
1811 if (success == FALSE) {
1812 return switch (GetLastError()) {
1813 else => |err| unexpectedError(err),
1814 };
1815 }
1816}
1817
1818pub const CreateProcessError = error{
1819 FileNotFound,
1820 AccessDenied,
1821 InvalidName,
1822 NameTooLong,
1823 InvalidExe,
1824 SystemResources,
1825 FileBusy,
1826 Unexpected,
1827};
1828
1829pub const CreateProcessFlags = packed struct(u32) {
1830 debug_process: bool = false,
1831 debug_only_this_process: bool = false,
1832 create_suspended: bool = false,
1833 detached_process: bool = false,
1834 create_new_console: bool = false,
1835 normal_priority_class: bool = false,
1836 idle_priority_class: bool = false,
1837 high_priority_class: bool = false,
1838 realtime_priority_class: bool = false,
1839 create_new_process_group: bool = false,
1840 create_unicode_environment: bool = false,
1841 create_separate_wow_vdm: bool = false,
1842 create_shared_wow_vdm: bool = false,
1843 create_forcedos: bool = false,
1844 below_normal_priority_class: bool = false,
1845 above_normal_priority_class: bool = false,
1846 inherit_parent_affinity: bool = false,
1847 inherit_caller_priority: bool = false,
1848 create_protected_process: bool = false,
1849 extended_startupinfo_present: bool = false,
1850 process_mode_background_begin: bool = false,
1851 process_mode_background_end: bool = false,
1852 create_secure_process: bool = false,
1853 _reserved: bool = false,
1854 create_breakaway_from_job: bool = false,
1855 create_preserve_code_authz_level: bool = false,
1856 create_default_error_mode: bool = false,
1857 create_no_window: bool = false,
1858 profile_user: bool = false,
1859 profile_kernel: bool = false,
1860 profile_server: bool = false,
1861 create_ignore_system_default: bool = false,
1862};
1863
1864pub fn CreateProcessW(
1865 lpApplicationName: ?LPCWSTR,
1866 lpCommandLine: ?LPWSTR,
1867 lpProcessAttributes: ?*SECURITY_ATTRIBUTES,
1868 lpThreadAttributes: ?*SECURITY_ATTRIBUTES,
1869 bInheritHandles: BOOL,
1870 dwCreationFlags: CreateProcessFlags,
1871 lpEnvironment: ?*anyopaque,
1872 lpCurrentDirectory: ?LPCWSTR,
1873 lpStartupInfo: *STARTUPINFOW,
1874 lpProcessInformation: *PROCESS_INFORMATION,
1875) CreateProcessError!void {
1876 if (kernel32.CreateProcessW(
1877 lpApplicationName,
1878 lpCommandLine,
1879 lpProcessAttributes,
1880 lpThreadAttributes,
1881 bInheritHandles,
1882 dwCreationFlags,
1883 lpEnvironment,
1884 lpCurrentDirectory,
1885 lpStartupInfo,
1886 lpProcessInformation,
1887 ) == 0) {
1888 switch (GetLastError()) {
1889 .FILE_NOT_FOUND => return error.FileNotFound,
1890 .PATH_NOT_FOUND => return error.FileNotFound,
1891 .DIRECTORY => return error.FileNotFound,
1892 .ACCESS_DENIED => return error.AccessDenied,
1893 .INVALID_PARAMETER => unreachable,
1894 .INVALID_NAME => return error.InvalidName,
1895 .FILENAME_EXCED_RANGE => return error.NameTooLong,
1896 .SHARING_VIOLATION => return error.FileBusy,
1897 // These are all the system errors that are mapped to ENOEXEC by
1898 // the undocumented _dosmaperr (old CRT) or __acrt_errno_map_os_error
1899 // (newer CRT) functions. Their code can be found in crt/src/dosmap.c (old SDK)
1900 // or urt/misc/errno.cpp (newer SDK) in the Windows SDK.
1901 .BAD_FORMAT,
1902 .INVALID_STARTING_CODESEG, // MIN_EXEC_ERROR in errno.cpp
1903 .INVALID_STACKSEG,
1904 .INVALID_MODULETYPE,
1905 .INVALID_EXE_SIGNATURE,
1906 .EXE_MARKED_INVALID,
1907 .BAD_EXE_FORMAT,
1908 .ITERATED_DATA_EXCEEDS_64k,
1909 .INVALID_MINALLOCSIZE,
1910 .DYNLINK_FROM_INVALID_RING,
1911 .IOPL_NOT_ENABLED,
1912 .INVALID_SEGDPL,
1913 .AUTODATASEG_EXCEEDS_64k,
1914 .RING2SEG_MUST_BE_MOVABLE,
1915 .RELOC_CHAIN_XEEDS_SEGLIM,
1916 .INFLOOP_IN_RELOC_CHAIN, // MAX_EXEC_ERROR in errno.cpp
1917 // This one is not mapped to ENOEXEC but it is possible, for example
1918 // when calling CreateProcessW on a plain text file with a .exe extension
1919 .EXE_MACHINE_TYPE_MISMATCH,
1920 => return error.InvalidExe,
1921 .COMMITMENT_LIMIT => return error.SystemResources,
1922 else => |err| return unexpectedError(err),
1923 }
1924 }
1925}
1926
1927pub const LoadLibraryError = error{
1928 FileNotFound,
1929 Unexpected,
1930};
1931
1932pub fn LoadLibraryW(lpLibFileName: [*:0]const u16) LoadLibraryError!HMODULE {
1933 return kernel32.LoadLibraryW(lpLibFileName) orelse {
1934 switch (GetLastError()) {
1935 .FILE_NOT_FOUND => return error.FileNotFound,
1936 .PATH_NOT_FOUND => return error.FileNotFound,
1937 .MOD_NOT_FOUND => return error.FileNotFound,
1938 else => |err| return unexpectedError(err),
1939 }
1940 };
1941}
1942
1943pub const LoadLibraryFlags = enum(DWORD) {
1944 none = 0,
1945 dont_resolve_dll_references = 0x00000001,
1946 load_ignore_code_authz_level = 0x00000010,
1947 load_library_as_datafile = 0x00000002,
1948 load_library_as_datafile_exclusive = 0x00000040,
1949 load_library_as_image_resource = 0x00000020,
1950 load_library_search_application_dir = 0x00000200,
1951 load_library_search_default_dirs = 0x00001000,
1952 load_library_search_dll_load_dir = 0x00000100,
1953 load_library_search_system32 = 0x00000800,
1954 load_library_search_user_dirs = 0x00000400,
1955 load_with_altered_search_path = 0x00000008,
1956 load_library_require_signed_target = 0x00000080,
1957 load_library_safe_current_dirs = 0x00002000,
1958};
1959
1960pub fn LoadLibraryExW(lpLibFileName: [*:0]const u16, dwFlags: LoadLibraryFlags) LoadLibraryError!HMODULE {
1961 return kernel32.LoadLibraryExW(lpLibFileName, null, @intFromEnum(dwFlags)) orelse {
1962 switch (GetLastError()) {
1963 .FILE_NOT_FOUND => return error.FileNotFound,
1964 .PATH_NOT_FOUND => return error.FileNotFound,
1965 .MOD_NOT_FOUND => return error.FileNotFound,
1966 else => |err| return unexpectedError(err),
1967 }
1968 };
1969}
1970
1971pub fn FreeLibrary(hModule: HMODULE) void {
1972 assert(kernel32.FreeLibrary(hModule) != 0);
1973}
1974
1975pub fn QueryPerformanceFrequency() u64 {
1976 // "On systems that run Windows XP or later, the function will always succeed"
1977 // https://docs.microsoft.com/en-us/windows/desktop/api/profileapi/nf-profileapi-queryperformancefrequency
1978 var result: LARGE_INTEGER = undefined;
1979 assert(ntdll.RtlQueryPerformanceFrequency(&result) != 0);
1980 // The kernel treats this integer as unsigned.
1981 return @as(u64, @bitCast(result));
1982}
1983
1984pub fn QueryPerformanceCounter() u64 {
1985 // "On systems that run Windows XP or later, the function will always succeed"
1986 // https://docs.microsoft.com/en-us/windows/desktop/api/profileapi/nf-profileapi-queryperformancecounter
1987 var result: LARGE_INTEGER = undefined;
1988 assert(ntdll.RtlQueryPerformanceCounter(&result) != 0);
1989 // The kernel treats this integer as unsigned.
1990 return @as(u64, @bitCast(result));
1991}
1992
1993pub fn InitOnceExecuteOnce(InitOnce: *INIT_ONCE, InitFn: INIT_ONCE_FN, Parameter: ?*anyopaque, Context: ?*anyopaque) void {
1994 assert(kernel32.InitOnceExecuteOnce(InitOnce, InitFn, Parameter, Context) != 0);
1995}
1996
1997pub const SetFileTimeError = error{Unexpected};
1998
1999pub fn SetFileTime(
2000 hFile: HANDLE,
2001 lpCreationTime: ?*const FILETIME,
2002 lpLastAccessTime: ?*const FILETIME,
2003 lpLastWriteTime: ?*const FILETIME,
2004) SetFileTimeError!void {
2005 const rc = kernel32.SetFileTime(hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime);
2006 if (rc == 0) {
2007 switch (GetLastError()) {
2008 else => |err| return unexpectedError(err),
2009 }
2010 }
2011}
2012
2013pub const LockFileError = error{
2014 SystemResources,
2015 WouldBlock,
2016} || UnexpectedError;
2017
2018pub fn LockFile(
2019 FileHandle: HANDLE,
2020 Event: ?HANDLE,
2021 ApcRoutine: ?*IO_APC_ROUTINE,
2022 ApcContext: ?*anyopaque,
2023 IoStatusBlock: *IO_STATUS_BLOCK,
2024 ByteOffset: *const LARGE_INTEGER,
2025 Length: *const LARGE_INTEGER,
2026 Key: ?*ULONG,
2027 FailImmediately: BOOLEAN,
2028 ExclusiveLock: BOOLEAN,
2029) !void {
2030 const rc = ntdll.NtLockFile(
2031 FileHandle,
2032 Event,
2033 ApcRoutine,
2034 ApcContext,
2035 IoStatusBlock,
2036 ByteOffset,
2037 Length,
2038 Key,
2039 FailImmediately,
2040 ExclusiveLock,
2041 );
2042 switch (rc) {
2043 .SUCCESS => return,
2044 .INSUFFICIENT_RESOURCES => return error.SystemResources,
2045 .LOCK_NOT_GRANTED => return error.WouldBlock,
2046 .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer
2047 else => return unexpectedStatus(rc),
2048 }
2049}
2050
2051pub const UnlockFileError = error{
2052 RangeNotLocked,
2053} || UnexpectedError;
2054
2055pub fn UnlockFile(
2056 FileHandle: HANDLE,
2057 IoStatusBlock: *IO_STATUS_BLOCK,
2058 ByteOffset: *const LARGE_INTEGER,
2059 Length: *const LARGE_INTEGER,
2060 Key: ?*ULONG,
2061) !void {
2062 const rc = ntdll.NtUnlockFile(FileHandle, IoStatusBlock, ByteOffset, Length, Key);
2063 switch (rc) {
2064 .SUCCESS => return,
2065 .RANGE_NOT_LOCKED => return error.RangeNotLocked,
2066 .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer
2067 else => return unexpectedStatus(rc),
2068 }
2069}
2070
2071/// This is a workaround for the C backend until zig has the ability to put
2072/// C code in inline assembly.
2073extern fn zig_thumb_windows_teb() callconv(.c) *anyopaque;
2074extern fn zig_aarch64_windows_teb() callconv(.c) *anyopaque;
2075extern fn zig_x86_windows_teb() callconv(.c) *anyopaque;
2076extern fn zig_x86_64_windows_teb() callconv(.c) *anyopaque;
2077
2078pub fn teb() *TEB {
2079 return switch (native_arch) {
2080 .thumb => if (builtin.zig_backend == .stage2_c)
2081 @ptrCast(@alignCast(zig_thumb_windows_teb()))
2082 else
2083 asm (
2084 \\ mrc p15, 0, %[ptr], c13, c0, 2
2085 : [ptr] "=r" (-> *TEB),
2086 ),
2087 .aarch64 => if (builtin.zig_backend == .stage2_c)
2088 @ptrCast(@alignCast(zig_aarch64_windows_teb()))
2089 else
2090 asm (
2091 \\ mov %[ptr], x18
2092 : [ptr] "=r" (-> *TEB),
2093 ),
2094 .x86 => if (builtin.zig_backend == .stage2_c)
2095 @ptrCast(@alignCast(zig_x86_windows_teb()))
2096 else
2097 asm (
2098 \\ movl %%fs:0x18, %[ptr]
2099 : [ptr] "=r" (-> *TEB),
2100 ),
2101 .x86_64 => if (builtin.zig_backend == .stage2_c)
2102 @ptrCast(@alignCast(zig_x86_64_windows_teb()))
2103 else
2104 asm (
2105 \\ movq %%gs:0x30, %[ptr]
2106 : [ptr] "=r" (-> *TEB),
2107 ),
2108 else => @compileError("unsupported arch"),
2109 };
2110}
2111
2112pub fn peb() *PEB {
2113 return teb().ProcessEnvironmentBlock;
2114}
2115
2116/// A file time is a 64-bit value that represents the number of 100-nanosecond
2117/// intervals that have elapsed since 12:00 A.M. January 1, 1601 Coordinated
2118/// Universal Time (UTC).
2119/// This function returns the number of nanoseconds since the canonical epoch,
2120/// which is the POSIX one (Jan 01, 1970 AD).
2121pub fn fromSysTime(hns: i64) Io.Timestamp {
2122 const adjusted_epoch: i128 = hns + std.time.epoch.windows * (std.time.ns_per_s / 100);
2123 return .fromNanoseconds(@intCast(adjusted_epoch * 100));
2124}
2125
2126pub fn toSysTime(ns: Io.Timestamp) i64 {
2127 const hns = @divFloor(ns.nanoseconds, 100);
2128 return @as(i64, @intCast(hns)) - std.time.epoch.windows * (std.time.ns_per_s / 100);
2129}
2130
2131pub fn fileTimeToNanoSeconds(ft: FILETIME) Io.Timestamp {
2132 const hns = (@as(i64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
2133 return fromSysTime(hns);
2134}
2135
2136/// Converts a number of nanoseconds since the POSIX epoch to a Windows FILETIME.
2137pub fn nanoSecondsToFileTime(ns: Io.Timestamp) FILETIME {
2138 const adjusted: u64 = @bitCast(toSysTime(ns));
2139 return .{
2140 .dwHighDateTime = @as(u32, @truncate(adjusted >> 32)),
2141 .dwLowDateTime = @as(u32, @truncate(adjusted)),
2142 };
2143}
2144
2145/// Compares two WTF16 strings using the equivalent functionality of
2146/// `RtlEqualUnicodeString` (with case insensitive comparison enabled).
2147/// This function can be called on any target.
2148pub fn eqlIgnoreCaseWtf16(a: []const u16, b: []const u16) bool {
2149 if (@inComptime() or builtin.os.tag != .windows) {
2150 // This function compares the strings code unit by code unit (aka u16-to-u16),
2151 // so any length difference implies inequality. In other words, there's no possible
2152 // conversion that changes the number of WTF-16 code units needed for the uppercase/lowercase
2153 // version in the conversion table since only codepoints <= max(u16) are eligible
2154 // for conversion at all.
2155 if (a.len != b.len) return false;
2156
2157 for (a, b) |a_c, b_c| {
2158 // The slices are always WTF-16 LE, so need to convert the elements to native
2159 // endianness for the uppercasing
2160 const a_c_native = std.mem.littleToNative(u16, a_c);
2161 const b_c_native = std.mem.littleToNative(u16, b_c);
2162 if (a_c != b_c and nls.upcaseW(a_c_native) != nls.upcaseW(b_c_native)) {
2163 return false;
2164 }
2165 }
2166 return true;
2167 }
2168 // Use RtlEqualUnicodeString on Windows when not in comptime to avoid including a
2169 // redundant copy of the uppercase data.
2170 const a_bytes = @as(u16, @intCast(a.len * 2));
2171 const a_string = UNICODE_STRING{
2172 .Length = a_bytes,
2173 .MaximumLength = a_bytes,
2174 .Buffer = @constCast(a.ptr),
2175 };
2176 const b_bytes = @as(u16, @intCast(b.len * 2));
2177 const b_string = UNICODE_STRING{
2178 .Length = b_bytes,
2179 .MaximumLength = b_bytes,
2180 .Buffer = @constCast(b.ptr),
2181 };
2182 return ntdll.RtlEqualUnicodeString(&a_string, &b_string, TRUE) == TRUE;
2183}
2184
2185/// Compares two WTF-8 strings using the equivalent functionality of
2186/// `RtlEqualUnicodeString` (with case insensitive comparison enabled).
2187/// This function can be called on any target.
2188/// Assumes `a` and `b` are valid WTF-8.
2189pub fn eqlIgnoreCaseWtf8(a: []const u8, b: []const u8) bool {
2190 // A length equality check is not possible here because there are
2191 // some codepoints that have a different length uppercase UTF-8 representations
2192 // than their lowercase counterparts, e.g. U+0250 (2 bytes) <-> U+2C6F (3 bytes).
2193 // There are 7 such codepoints in the uppercase data used by Windows.
2194
2195 var a_wtf8_it = std.unicode.Wtf8View.initUnchecked(a).iterator();
2196 var b_wtf8_it = std.unicode.Wtf8View.initUnchecked(b).iterator();
2197
2198 // Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a
2199 // redundant copy of the uppercase data.
2200 const upcaseImpl = switch (builtin.os.tag) {
2201 .windows => if (@inComptime()) nls.upcaseW else ntdll.RtlUpcaseUnicodeChar,
2202 else => nls.upcaseW,
2203 };
2204
2205 while (true) {
2206 const a_cp = a_wtf8_it.nextCodepoint() orelse break;
2207 const b_cp = b_wtf8_it.nextCodepoint() orelse return false;
2208
2209 if (a_cp <= std.math.maxInt(u16) and b_cp <= std.math.maxInt(u16)) {
2210 if (a_cp != b_cp and upcaseImpl(@intCast(a_cp)) != upcaseImpl(@intCast(b_cp))) {
2211 return false;
2212 }
2213 } else if (a_cp != b_cp) {
2214 return false;
2215 }
2216 }
2217 // Make sure there are no leftover codepoints in b
2218 if (b_wtf8_it.nextCodepoint() != null) return false;
2219
2220 return true;
2221}
2222
2223fn testEqlIgnoreCase(comptime expect_eql: bool, comptime a: []const u8, comptime b: []const u8) !void {
2224 try std.testing.expectEqual(expect_eql, eqlIgnoreCaseWtf8(a, b));
2225 try std.testing.expectEqual(expect_eql, eqlIgnoreCaseWtf16(
2226 std.unicode.utf8ToUtf16LeStringLiteral(a),
2227 std.unicode.utf8ToUtf16LeStringLiteral(b),
2228 ));
2229
2230 try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWtf8(a, b));
2231 try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWtf16(
2232 std.unicode.utf8ToUtf16LeStringLiteral(a),
2233 std.unicode.utf8ToUtf16LeStringLiteral(b),
2234 ));
2235}
2236
2237test "eqlIgnoreCaseWtf16/Wtf8" {
2238 try testEqlIgnoreCase(true, "\x01 a B Λ ɐ", "\x01 A b λ Ɐ");
2239 // does not do case-insensitive comparison for codepoints >= U+10000
2240 try testEqlIgnoreCase(false, "𐓏", "𐓷");
2241}
2242
2243pub const PathSpace = struct {
2244 data: [PATH_MAX_WIDE:0]u16,
2245 len: usize,
2246
2247 pub fn span(self: *const PathSpace) [:0]const u16 {
2248 return self.data[0..self.len :0];
2249 }
2250};
2251
2252/// The error type for `removeDotDirsSanitized`
2253pub const RemoveDotDirsError = error{TooManyParentDirs};
2254
2255/// Removes '.' and '..' path components from a "sanitized relative path".
2256/// A "sanitized path" is one where:
2257/// 1) all forward slashes have been replaced with back slashes
2258/// 2) all repeating back slashes have been collapsed
2259/// 3) the path is a relative one (does not start with a back slash)
2260pub fn removeDotDirsSanitized(comptime T: type, path: []T) RemoveDotDirsError!usize {
2261 std.debug.assert(path.len == 0 or path[0] != '\\');
2262
2263 var write_idx: usize = 0;
2264 var read_idx: usize = 0;
2265 while (read_idx < path.len) {
2266 if (path[read_idx] == '.') {
2267 if (read_idx + 1 == path.len)
2268 return write_idx;
2269
2270 const after_dot = path[read_idx + 1];
2271 if (after_dot == '\\') {
2272 read_idx += 2;
2273 continue;
2274 }
2275 if (after_dot == '.' and (read_idx + 2 == path.len or path[read_idx + 2] == '\\')) {
2276 if (write_idx == 0) return error.TooManyParentDirs;
2277 std.debug.assert(write_idx >= 2);
2278 write_idx -= 1;
2279 while (true) {
2280 write_idx -= 1;
2281 if (write_idx == 0) break;
2282 if (path[write_idx] == '\\') {
2283 write_idx += 1;
2284 break;
2285 }
2286 }
2287 if (read_idx + 2 == path.len)
2288 return write_idx;
2289 read_idx += 3;
2290 continue;
2291 }
2292 }
2293
2294 // skip to the next path separator
2295 while (true) : (read_idx += 1) {
2296 if (read_idx == path.len)
2297 return write_idx;
2298 path[write_idx] = path[read_idx];
2299 write_idx += 1;
2300 if (path[read_idx] == '\\')
2301 break;
2302 }
2303 read_idx += 1;
2304 }
2305 return write_idx;
2306}
2307
2308/// Normalizes a Windows path with the following steps:
2309/// 1) convert all forward slashes to back slashes
2310/// 2) collapse duplicate back slashes
2311/// 3) remove '.' and '..' directory parts
2312/// Returns the length of the new path.
2313pub fn normalizePath(comptime T: type, path: []T) RemoveDotDirsError!usize {
2314 mem.replaceScalar(T, path, '/', '\\');
2315 const new_len = mem.collapseRepeatsLen(T, path, '\\');
2316
2317 const prefix_len: usize = init: {
2318 if (new_len >= 1 and path[0] == '\\') break :init 1;
2319 if (new_len >= 2 and path[1] == ':')
2320 break :init if (new_len >= 3 and path[2] == '\\') @as(usize, 3) else @as(usize, 2);
2321 break :init 0;
2322 };
2323
2324 return prefix_len + try removeDotDirsSanitized(T, path[prefix_len..new_len]);
2325}
2326
2327pub const Wtf8ToPrefixedFileWError = Wtf16ToPrefixedFileWError;
2328
2329/// Same as `sliceToPrefixedFileW` but accepts a pointer
2330/// to a null-terminated WTF-8 encoded path.
2331/// https://wtf-8.codeberg.page/
2332pub fn cStrToPrefixedFileW(dir: ?HANDLE, s: [*:0]const u8) Wtf8ToPrefixedFileWError!PathSpace {
2333 return sliceToPrefixedFileW(dir, mem.sliceTo(s, 0));
2334}
2335
2336/// Same as `wToPrefixedFileW` but accepts a WTF-8 encoded path.
2337/// https://wtf-8.codeberg.page/
2338pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) Wtf8ToPrefixedFileWError!PathSpace {
2339 var temp_path: PathSpace = undefined;
2340 temp_path.len = std.unicode.wtf8ToWtf16Le(&temp_path.data, path) catch |err| switch (err) {
2341 error.InvalidWtf8 => return error.BadPathName,
2342 };
2343 temp_path.data[temp_path.len] = 0;
2344 return wToPrefixedFileW(dir, temp_path.span());
2345}
2346
2347pub const Wtf16ToPrefixedFileWError = error{
2348 AccessDenied,
2349 BadPathName,
2350 FileNotFound,
2351 NameTooLong,
2352 Unexpected,
2353};
2354
2355/// Converts the `path` to WTF16, null-terminated. If the path contains any
2356/// namespace prefix, or is anything but a relative path (rooted, drive relative,
2357/// etc) the result will have the NT-style prefix `\??\`.
2358///
2359/// Similar to RtlDosPathNameToNtPathName_U with a few differences:
2360/// - Does not allocate on the heap.
2361/// - Relative paths are kept as relative unless they contain too many ..
2362/// components, in which case they are resolved against the `dir` if it
2363/// is non-null, or the CWD if it is null.
2364/// - Special case device names like COM1, NUL, etc are not handled specially (TODO)
2365/// - . and space are not stripped from the end of relative paths (potential TODO)
2366pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWError!PathSpace {
2367 const nt_prefix = [_]u16{ '\\', '?', '?', '\\' };
2368 if (hasCommonNtPrefix(u16, path)) {
2369 // TODO: Figure out a way to design an API that can avoid the copy for NT,
2370 // since it is always returned fully unmodified.
2371 var path_space: PathSpace = undefined;
2372 path_space.data[0..nt_prefix.len].* = nt_prefix;
2373 const len_after_prefix = path.len - nt_prefix.len;
2374 @memcpy(path_space.data[nt_prefix.len..][0..len_after_prefix], path[nt_prefix.len..]);
2375 path_space.len = path.len;
2376 path_space.data[path_space.len] = 0;
2377 return path_space;
2378 } else {
2379 const path_type = getWin32PathType(u16, path);
2380 var path_space: PathSpace = undefined;
2381 if (path_type == .local_device) {
2382 switch (getLocalDevicePathType(u16, path)) {
2383 .verbatim => {
2384 path_space.data[0..nt_prefix.len].* = nt_prefix;
2385 const len_after_prefix = path.len - nt_prefix.len;
2386 @memcpy(path_space.data[nt_prefix.len..][0..len_after_prefix], path[nt_prefix.len..]);
2387 path_space.len = path.len;
2388 path_space.data[path_space.len] = 0;
2389 return path_space;
2390 },
2391 .local_device, .fake_verbatim => {
2392 const path_byte_len = ntdll.RtlGetFullPathName_U(
2393 path.ptr,
2394 path_space.data.len * 2,
2395 &path_space.data,
2396 null,
2397 );
2398 if (path_byte_len == 0) {
2399 // TODO: This may not be the right error
2400 return error.BadPathName;
2401 } else if (path_byte_len / 2 > path_space.data.len) {
2402 return error.NameTooLong;
2403 }
2404 path_space.len = path_byte_len / 2;
2405 // Both prefixes will be normalized but retained, so all
2406 // we need to do now is replace them with the NT prefix
2407 path_space.data[0..nt_prefix.len].* = nt_prefix;
2408 return path_space;
2409 },
2410 }
2411 }
2412 relative: {
2413 if (path_type == .relative) {
2414 // TODO: Handle special case device names like COM1, AUX, NUL, CONIN$, CONOUT$, etc.
2415 // See https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html
2416
2417 // TODO: Potentially strip all trailing . and space characters from the
2418 // end of the path. This is something that both RtlDosPathNameToNtPathName_U
2419 // and RtlGetFullPathName_U do. Technically, trailing . and spaces
2420 // are allowed, but such paths may not interact well with Windows (i.e.
2421 // files with these paths can't be deleted from explorer.exe, etc).
2422 // This could be something that normalizePath may want to do.
2423
2424 @memcpy(path_space.data[0..path.len], path);
2425 // Try to normalize, but if we get too many parent directories,
2426 // then we need to start over and use RtlGetFullPathName_U instead.
2427 path_space.len = normalizePath(u16, path_space.data[0..path.len]) catch |err| switch (err) {
2428 error.TooManyParentDirs => break :relative,
2429 };
2430 path_space.data[path_space.len] = 0;
2431 return path_space;
2432 }
2433 }
2434 // We now know we are going to return an absolute NT path, so
2435 // we can unconditionally prefix it with the NT prefix.
2436 path_space.data[0..nt_prefix.len].* = nt_prefix;
2437 if (path_type == .root_local_device) {
2438 // `\\.` and `\\?` always get converted to `\??\` exactly, so
2439 // we can just stop here
2440 path_space.len = nt_prefix.len;
2441 path_space.data[path_space.len] = 0;
2442 return path_space;
2443 }
2444 const path_buf_offset = switch (path_type) {
2445 // UNC paths will always start with `\\`. However, we want to
2446 // end up with something like `\??\UNC\server\share`, so to get
2447 // RtlGetFullPathName to write into the spot we want the `server`
2448 // part to end up, we need to provide an offset such that
2449 // the `\\` part gets written where the `C\` of `UNC\` will be
2450 // in the final NT path.
2451 .unc_absolute => nt_prefix.len + 2,
2452 else => nt_prefix.len,
2453 };
2454 const buf_len: u32 = @intCast(path_space.data.len - path_buf_offset);
2455 const path_to_get: [:0]const u16 = path_to_get: {
2456 // If dir is null, then we don't need to bother with GetFinalPathNameByHandle because
2457 // RtlGetFullPathName_U will resolve relative paths against the CWD for us.
2458 if (path_type != .relative or dir == null) {
2459 break :path_to_get path;
2460 }
2461 // We can also skip GetFinalPathNameByHandle if the handle matches
2462 // the handle returned by fs.cwd()
2463 if (dir.? == std.fs.cwd().fd) {
2464 break :path_to_get path;
2465 }
2466 // At this point, we know we have a relative path that had too many
2467 // `..` components to be resolved by normalizePath, so we need to
2468 // convert it into an absolute path and let RtlGetFullPathName_U
2469 // canonicalize it. We do this by getting the path of the `dir`
2470 // and appending the relative path to it.
2471 var dir_path_buf: [PATH_MAX_WIDE:0]u16 = undefined;
2472 const dir_path = GetFinalPathNameByHandle(dir.?, .{}, &dir_path_buf) catch |err| switch (err) {
2473 // This mapping is not correct; it is actually expected
2474 // that calling GetFinalPathNameByHandle might return
2475 // error.UnrecognizedVolume, and in fact has been observed
2476 // in the wild. The problem is that wToPrefixedFileW was
2477 // never intended to make *any* OS syscall APIs. It's only
2478 // supposed to convert a string to one that is eligible to
2479 // be used in the ntdll syscalls.
2480 //
2481 // To solve this, this function needs to no longer call
2482 // GetFinalPathNameByHandle under any conditions, or the
2483 // calling function needs to get reworked to not need to
2484 // call this function.
2485 //
2486 // This may involve making breaking API changes.
2487 error.UnrecognizedVolume => return error.Unexpected,
2488 else => |e| return e,
2489 };
2490 if (dir_path.len + 1 + path.len > PATH_MAX_WIDE) {
2491 return error.NameTooLong;
2492 }
2493 // We don't have to worry about potentially doubling up path separators
2494 // here since RtlGetFullPathName_U will handle canonicalizing it.
2495 dir_path_buf[dir_path.len] = '\\';
2496 @memcpy(dir_path_buf[dir_path.len + 1 ..][0..path.len], path);
2497 const full_len = dir_path.len + 1 + path.len;
2498 dir_path_buf[full_len] = 0;
2499 break :path_to_get dir_path_buf[0..full_len :0];
2500 };
2501 const path_byte_len = ntdll.RtlGetFullPathName_U(
2502 path_to_get.ptr,
2503 buf_len * 2,
2504 path_space.data[path_buf_offset..].ptr,
2505 null,
2506 );
2507 if (path_byte_len == 0) {
2508 // TODO: This may not be the right error
2509 return error.BadPathName;
2510 } else if (path_byte_len / 2 > buf_len) {
2511 return error.NameTooLong;
2512 }
2513 path_space.len = path_buf_offset + (path_byte_len / 2);
2514 if (path_type == .unc_absolute) {
2515 // Now add in the UNC, the `C` should overwrite the first `\` of the
2516 // FullPathName, ultimately resulting in `\??\UNC\<the rest of the path>`
2517 std.debug.assert(path_space.data[path_buf_offset] == '\\');
2518 std.debug.assert(path_space.data[path_buf_offset + 1] == '\\');
2519 const unc = [_]u16{ 'U', 'N', 'C' };
2520 path_space.data[nt_prefix.len..][0..unc.len].* = unc;
2521 }
2522 return path_space;
2523 }
2524}
2525
2526/// Similar to `RTL_PATH_TYPE`, but without the `UNKNOWN` path type.
2527pub const Win32PathType = enum {
2528 /// `\\server\share\foo`
2529 unc_absolute,
2530 /// `C:\foo`
2531 drive_absolute,
2532 /// `C:foo`
2533 drive_relative,
2534 /// `\foo`
2535 rooted,
2536 /// `foo`
2537 relative,
2538 /// `\\.\foo`, `\\?\foo`
2539 local_device,
2540 /// `\\.`, `\\?`
2541 root_local_device,
2542};
2543
2544/// Get the path type of a Win32 namespace path.
2545/// Similar to `RtlDetermineDosPathNameType_U`.
2546/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
2547pub fn getWin32PathType(comptime T: type, path: []const T) Win32PathType {
2548 if (path.len < 1) return .relative;
2549
2550 const windows_path = std.fs.path.PathType.windows;
2551 if (windows_path.isSep(T, path[0])) {
2552 // \x
2553 if (path.len < 2 or !windows_path.isSep(T, path[1])) return .rooted;
2554 // \\. or \\?
2555 if (path.len > 2 and (path[2] == mem.nativeToLittle(T, '.') or path[2] == mem.nativeToLittle(T, '?'))) {
2556 // exactly \\. or \\? with nothing trailing
2557 if (path.len == 3) return .root_local_device;
2558 // \\.\x or \\?\x
2559 if (windows_path.isSep(T, path[3])) return .local_device;
2560 }
2561 // \\x
2562 return .unc_absolute;
2563 } else {
2564 // Some choice has to be made about how non-ASCII code points as drive-letters are handled, since
2565 // path[0] is a different size for WTF-16 vs WTF-8, leading to a potential mismatch in classification
2566 // for a WTF-8 path and its WTF-16 equivalent. For example, `€:\` encoded in WTF-16 is three code
2567 // units `<0x20AC>:\` whereas `€:\` encoded as WTF-8 is 6 code units `<0xE2><0x82><0xAC>:\` so
2568 // checking path[0], path[1] and path[2] would not behave the same between WTF-8/WTF-16.
2569 //
2570 // `RtlDetermineDosPathNameType_U` exclusively deals with WTF-16 and considers
2571 // `€:\` a drive-absolute path, but code points that take two WTF-16 code units to encode get
2572 // classified as a relative path (e.g. with U+20000 as the drive-letter that'd be encoded
2573 // in WTF-16 as `<0xD840><0xDC00>:\` and be considered a relative path).
2574 //
2575 // The choice made here is to emulate the behavior of `RtlDetermineDosPathNameType_U` for both
2576 // WTF-16 and WTF-8. This is because, while unlikely and not supported by the Disk Manager GUI,
2577 // drive letters are not actually restricted to A-Z. Using `SetVolumeMountPointW` will allow you
2578 // to set any byte value as a drive letter, and going through `IOCTL_MOUNTMGR_CREATE_POINT` will
2579 // allow you to set any WTF-16 code unit as a drive letter.
2580 //
2581 // Non-A-Z drive letters don't interact well with most of Windows, but certain things do work, e.g.
2582 // `cd /D €:\` will work, filesystem functions still work, etc.
2583 //
2584 // The unfortunate part of this is that this makes handling WTF-8 more complicated as we can't
2585 // just check path[0], path[1], path[2].
2586 const colon_i: usize = switch (T) {
2587 u8 => i: {
2588 const code_point_len = std.unicode.utf8ByteSequenceLength(path[0]) catch return .relative;
2589 // Conveniently, 4-byte sequences in WTF-8 have the same starting code point
2590 // as 2-code-unit sequences in WTF-16.
2591 if (code_point_len > 3) return .relative;
2592 break :i code_point_len;
2593 },
2594 u16 => 1,
2595 else => @compileError("unsupported type: " ++ @typeName(T)),
2596 };
2597 // x
2598 if (path.len < colon_i + 1 or path[colon_i] != mem.nativeToLittle(T, ':')) return .relative;
2599 // x:\
2600 if (path.len > colon_i + 1 and windows_path.isSep(T, path[colon_i + 1])) return .drive_absolute;
2601 // x:
2602 return .drive_relative;
2603 }
2604}
2605
2606test getWin32PathType {
2607 try std.testing.expectEqual(.relative, getWin32PathType(u8, ""));
2608 try std.testing.expectEqual(.relative, getWin32PathType(u8, "x"));
2609 try std.testing.expectEqual(.relative, getWin32PathType(u8, "x\\"));
2610
2611 try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "//."));
2612 try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "/\\?"));
2613 try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "\\\\?"));
2614
2615 try std.testing.expectEqual(.local_device, getWin32PathType(u8, "//./x"));
2616 try std.testing.expectEqual(.local_device, getWin32PathType(u8, "/\\?\\x"));
2617 try std.testing.expectEqual(.local_device, getWin32PathType(u8, "\\\\?\\x"));
2618 // local device paths require a path separator after the root, otherwise it is considered a UNC path
2619 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\?x"));
2620 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//.x"));
2621
2622 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//"));
2623 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\x"));
2624 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//x"));
2625
2626 try std.testing.expectEqual(.rooted, getWin32PathType(u8, "\\x"));
2627 try std.testing.expectEqual(.rooted, getWin32PathType(u8, "/"));
2628
2629 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:"));
2630 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:abc"));
2631 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:a/b/c"));
2632
2633 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\"));
2634 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\abc"));
2635 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:/a/b/c"));
2636
2637 // Non-ASCII code point that is encoded as one WTF-16 code unit is considered a valid drive letter
2638 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "€:\\"));
2639 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:\\")));
2640 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "€:"));
2641 try std.testing.expectEqual(.drive_relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:")));
2642 // But code points that are encoded as two WTF-16 code units are not
2643 try std.testing.expectEqual(.relative, getWin32PathType(u8, "\u{10000}:\\"));
2644 try std.testing.expectEqual(.relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("\u{10000}:\\")));
2645}
2646
2647/// Returns true if the path starts with `\??\`, which is indicative of an NT path
2648/// but is not enough to fully distinguish between NT paths and Win32 paths, as
2649/// `\??\` is not actually a distinct prefix but rather the path to a special virtual
2650/// folder in the Object Manager.
2651///
2652/// For example, `\Device\HarddiskVolume2` and `\DosDevices\C:` are also NT paths but
2653/// cannot be distinguished as such by their prefix.
2654///
2655/// So, inferring whether a path is an NT path or a Win32 path is usually a mistake;
2656/// that information should instead be known ahead-of-time.
2657///
2658/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
2659pub fn hasCommonNtPrefix(comptime T: type, path: []const T) bool {
2660 // Must be exactly \??\, forward slashes are not allowed
2661 const expected_wtf8_prefix = "\\??\\";
2662 const expected_prefix = switch (T) {
2663 u8 => expected_wtf8_prefix,
2664 u16 => std.unicode.wtf8ToWtf16LeStringLiteral(expected_wtf8_prefix),
2665 else => @compileError("unsupported type: " ++ @typeName(T)),
2666 };
2667 return mem.startsWith(T, path, expected_prefix);
2668}
2669
2670const LocalDevicePathType = enum {
2671 /// `\\.\` (path separators can be `\` or `/`)
2672 local_device,
2673 /// `\\?\`
2674 /// When converted to an NT path, everything past the prefix is left
2675 /// untouched and `\\?\` is replaced by `\??\`.
2676 verbatim,
2677 /// `\\?\` without all path separators being `\`.
2678 /// This seems to be recognized as a prefix, but the 'verbatim' aspect
2679 /// is not respected (i.e. if `//?/C:/foo` is converted to an NT path,
2680 /// it will become `\??\C:\foo` [it will be canonicalized and the //?/ won't
2681 /// be treated as part of the final path])
2682 fake_verbatim,
2683};
2684
2685/// Only relevant for Win32 -> NT path conversion.
2686/// Asserts `path` is of type `Win32PathType.local_device`.
2687fn getLocalDevicePathType(comptime T: type, path: []const T) LocalDevicePathType {
2688 if (std.debug.runtime_safety) {
2689 std.debug.assert(getWin32PathType(T, path) == .local_device);
2690 }
2691
2692 const backslash = mem.nativeToLittle(T, '\\');
2693 const all_backslash = path[0] == backslash and
2694 path[1] == backslash and
2695 path[3] == backslash;
2696 return switch (path[2]) {
2697 mem.nativeToLittle(T, '?') => if (all_backslash) .verbatim else .fake_verbatim,
2698 mem.nativeToLittle(T, '.') => .local_device,
2699 else => unreachable,
2700 };
2701}
2702
2703/// Similar to `RtlNtPathNameToDosPathName` but does not do any heap allocation.
2704/// The possible transformations are:
2705/// \??\C:\Some\Path -> C:\Some\Path
2706/// \??\UNC\server\share\foo -> \\server\share\foo
2707/// If the path does not have the NT namespace prefix, then `error.NotNtPath` is returned.
2708///
2709/// Functionality is based on the ReactOS test cases found here:
2710/// https://github.com/reactos/reactos/blob/master/modules/rostests/apitests/ntdll/RtlNtPathNameToDosPathName.c
2711///
2712/// `path` should be encoded as WTF-16LE.
2713///
2714/// Supports in-place modification (`path` and `out` may refer to the same slice).
2715pub fn ntToWin32Namespace(path: []const u16, out: []u16) error{ NameTooLong, NotNtPath }![]u16 {
2716 if (path.len > PATH_MAX_WIDE) return error.NameTooLong;
2717 if (!hasCommonNtPrefix(u16, path)) return error.NotNtPath;
2718
2719 var dest_index: usize = 0;
2720 var after_prefix = path[4..]; // after the `\??\`
2721 // The prefix \??\UNC\ means this is a UNC path, in which case the
2722 // `\??\UNC\` should be replaced by `\\` (two backslashes)
2723 const is_unc = after_prefix.len >= 4 and
2724 eqlIgnoreCaseWtf16(after_prefix[0..3], std.unicode.utf8ToUtf16LeStringLiteral("UNC")) and
2725 std.fs.path.PathType.windows.isSep(u16, after_prefix[3]);
2726 const win32_len = path.len - @as(usize, if (is_unc) 6 else 4);
2727 if (out.len < win32_len) return error.NameTooLong;
2728 if (is_unc) {
2729 out[0] = comptime std.mem.nativeToLittle(u16, '\\');
2730 dest_index += 1;
2731 // We want to include the last `\` of `\??\UNC\`
2732 after_prefix = path[7..];
2733 }
2734 @memmove(out[dest_index..][0..after_prefix.len], after_prefix);
2735 return out[0..win32_len];
2736}
2737
2738test ntToWin32Namespace {
2739 const L = std.unicode.utf8ToUtf16LeStringLiteral;
2740
2741 var mutable_unc_path_buf = L("\\??\\UNC\\path1\\path2").*;
2742 try std.testing.expectEqualSlices(u16, L("\\\\path1\\path2"), try ntToWin32Namespace(&mutable_unc_path_buf, &mutable_unc_path_buf));
2743
2744 var mutable_path_buf = L("\\??\\C:\\test\\").*;
2745 try std.testing.expectEqualSlices(u16, L("C:\\test\\"), try ntToWin32Namespace(&mutable_path_buf, &mutable_path_buf));
2746
2747 var too_small_buf: [6]u16 = undefined;
2748 try std.testing.expectError(error.NameTooLong, ntToWin32Namespace(L("\\??\\C:\\test"), &too_small_buf));
2749}
2750
2751inline fn MAKELANGID(p: c_ushort, s: c_ushort) LANGID {
2752 return (s << 10) | p;
2753}
2754
2755/// Call this when you made a windows DLL call or something that does SetLastError
2756/// and you get an unexpected error.
2757pub fn unexpectedError(err: Win32Error) UnexpectedError {
2758 if (std.posix.unexpected_error_tracing) {
2759 // 614 is the length of the longest windows error description
2760 var buf_wstr: [614:0]WCHAR = undefined;
2761 const len = kernel32.FormatMessageW(
2762 FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
2763 null,
2764 err,
2765 MAKELANGID(LANG.NEUTRAL, SUBLANG.DEFAULT),
2766 &buf_wstr,
2767 buf_wstr.len,
2768 null,
2769 );
2770 std.debug.print("error.Unexpected: GetLastError({d}): {f}\n", .{
2771 err, std.unicode.fmtUtf16Le(buf_wstr[0..len]),
2772 });
2773 std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() });
2774 }
2775 return error.Unexpected;
2776}
2777
2778pub fn unexpectedWSAError(err: ws2_32.WinsockError) UnexpectedError {
2779 return unexpectedError(@as(Win32Error, @enumFromInt(@intFromEnum(err))));
2780}
2781
2782/// Call this when you made a windows NtDll call
2783/// and you get an unexpected status.
2784pub fn unexpectedStatus(status: NTSTATUS) UnexpectedError {
2785 if (std.posix.unexpected_error_tracing) {
2786 std.debug.print("error.Unexpected NTSTATUS=0x{x}\n", .{@intFromEnum(status)});
2787 std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() });
2788 }
2789 return error.Unexpected;
2790}
2791
2792pub fn statusBug(status: NTSTATUS) UnexpectedError {
2793 switch (builtin.mode) {
2794 .Debug => std.debug.panic("programmer bug caused syscall status: {t}", .{status}),
2795 else => return error.Unexpected,
2796 }
2797}
2798
2799pub fn errorBug(err: Win32Error) UnexpectedError {
2800 switch (builtin.mode) {
2801 .Debug => std.debug.panic("programmer bug caused syscall status: {t}", .{err}),
2802 else => return error.Unexpected,
2803 }
2804}
2805
2806pub const Win32Error = @import("windows/win32error.zig").Win32Error;
2807pub const NTSTATUS = @import("windows/ntstatus.zig").NTSTATUS;
2808pub const LANG = @import("windows/lang.zig");
2809pub const SUBLANG = @import("windows/sublang.zig");
2810
2811/// The standard input device. Initially, this is the console input buffer, CONIN$.
2812pub const STD_INPUT_HANDLE = maxInt(DWORD) - 10 + 1;
2813
2814/// The standard output device. Initially, this is the active console screen buffer, CONOUT$.
2815pub const STD_OUTPUT_HANDLE = maxInt(DWORD) - 11 + 1;
2816
2817/// The standard error device. Initially, this is the active console screen buffer, CONOUT$.
2818pub const STD_ERROR_HANDLE = maxInt(DWORD) - 12 + 1;
2819
2820pub const BOOL = c_int;
2821pub const BOOLEAN = BYTE;
2822pub const BYTE = u8;
2823pub const CHAR = u8;
2824pub const UCHAR = u8;
2825pub const FLOAT = f32;
2826pub const HANDLE = *anyopaque;
2827pub const HCRYPTPROV = ULONG_PTR;
2828pub const ATOM = u16;
2829pub const HBRUSH = *opaque {};
2830pub const HCURSOR = *opaque {};
2831pub const HICON = *opaque {};
2832pub const HINSTANCE = *opaque {};
2833pub const HMENU = *opaque {};
2834pub const HMODULE = *opaque {};
2835pub const HWND = *opaque {};
2836pub const HDC = *opaque {};
2837pub const HGLRC = *opaque {};
2838pub const FARPROC = *opaque {};
2839pub const PROC = *opaque {};
2840pub const INT = c_int;
2841pub const LPCSTR = [*:0]const CHAR;
2842pub const LPCVOID = *const anyopaque;
2843pub const LPSTR = [*:0]CHAR;
2844pub const LPVOID = *anyopaque;
2845pub const LPWSTR = [*:0]WCHAR;
2846pub const LPCWSTR = [*:0]const WCHAR;
2847pub const PVOID = *anyopaque;
2848pub const PWSTR = [*:0]WCHAR;
2849pub const PCWSTR = [*:0]const WCHAR;
2850/// Allocated by SysAllocString, freed by SysFreeString
2851pub const BSTR = [*:0]WCHAR;
2852pub const SIZE_T = usize;
2853pub const UINT = c_uint;
2854pub const ULONG_PTR = usize;
2855pub const LONG_PTR = isize;
2856pub const DWORD_PTR = ULONG_PTR;
2857pub const WCHAR = u16;
2858pub const WORD = u16;
2859pub const DWORD = u32;
2860pub const DWORD64 = u64;
2861pub const LARGE_INTEGER = i64;
2862pub const ULARGE_INTEGER = u64;
2863pub const USHORT = u16;
2864pub const SHORT = i16;
2865pub const ULONG = u32;
2866pub const LONG = i32;
2867pub const ULONG64 = u64;
2868pub const ULONGLONG = u64;
2869pub const LONGLONG = i64;
2870pub const HLOCAL = HANDLE;
2871pub const LANGID = c_ushort;
2872
2873pub const WPARAM = usize;
2874pub const LPARAM = LONG_PTR;
2875pub const LRESULT = LONG_PTR;
2876
2877pub const va_list = *opaque {};
2878
2879pub const TCHAR = @compileError("Deprecated: choose between `CHAR` or `WCHAR` directly instead.");
2880pub const LPTSTR = @compileError("Deprecated: choose between `LPSTR` or `LPWSTR` directly instead.");
2881pub const LPCTSTR = @compileError("Deprecated: choose between `LPCSTR` or `LPCWSTR` directly instead.");
2882pub const PTSTR = @compileError("Deprecated: choose between `PSTR` or `PWSTR` directly instead.");
2883pub const PCTSTR = @compileError("Deprecated: choose between `PCSTR` or `PCWSTR` directly instead.");
2884
2885pub const TRUE = 1;
2886pub const FALSE = 0;
2887
2888pub const DEVICE_TYPE = ULONG;
2889pub const FILE_DEVICE_BEEP: DEVICE_TYPE = 0x0001;
2890pub const FILE_DEVICE_CD_ROM: DEVICE_TYPE = 0x0002;
2891pub const FILE_DEVICE_CD_ROM_FILE_SYSTEM: DEVICE_TYPE = 0x0003;
2892pub const FILE_DEVICE_CONTROLLER: DEVICE_TYPE = 0x0004;
2893pub const FILE_DEVICE_DATALINK: DEVICE_TYPE = 0x0005;
2894pub const FILE_DEVICE_DFS: DEVICE_TYPE = 0x0006;
2895pub const FILE_DEVICE_DISK: DEVICE_TYPE = 0x0007;
2896pub const FILE_DEVICE_DISK_FILE_SYSTEM: DEVICE_TYPE = 0x0008;
2897pub const FILE_DEVICE_FILE_SYSTEM: DEVICE_TYPE = 0x0009;
2898pub const FILE_DEVICE_INPORT_PORT: DEVICE_TYPE = 0x000a;
2899pub const FILE_DEVICE_KEYBOARD: DEVICE_TYPE = 0x000b;
2900pub const FILE_DEVICE_MAILSLOT: DEVICE_TYPE = 0x000c;
2901pub const FILE_DEVICE_MIDI_IN: DEVICE_TYPE = 0x000d;
2902pub const FILE_DEVICE_MIDI_OUT: DEVICE_TYPE = 0x000e;
2903pub const FILE_DEVICE_MOUSE: DEVICE_TYPE = 0x000f;
2904pub const FILE_DEVICE_MULTI_UNC_PROVIDER: DEVICE_TYPE = 0x0010;
2905pub const FILE_DEVICE_NAMED_PIPE: DEVICE_TYPE = 0x0011;
2906pub const FILE_DEVICE_NETWORK: DEVICE_TYPE = 0x0012;
2907pub const FILE_DEVICE_NETWORK_BROWSER: DEVICE_TYPE = 0x0013;
2908pub const FILE_DEVICE_NETWORK_FILE_SYSTEM: DEVICE_TYPE = 0x0014;
2909pub const FILE_DEVICE_NULL: DEVICE_TYPE = 0x0015;
2910pub const FILE_DEVICE_PARALLEL_PORT: DEVICE_TYPE = 0x0016;
2911pub const FILE_DEVICE_PHYSICAL_NETCARD: DEVICE_TYPE = 0x0017;
2912pub const FILE_DEVICE_PRINTER: DEVICE_TYPE = 0x0018;
2913pub const FILE_DEVICE_SCANNER: DEVICE_TYPE = 0x0019;
2914pub const FILE_DEVICE_SERIAL_MOUSE_PORT: DEVICE_TYPE = 0x001a;
2915pub const FILE_DEVICE_SERIAL_PORT: DEVICE_TYPE = 0x001b;
2916pub const FILE_DEVICE_SCREEN: DEVICE_TYPE = 0x001c;
2917pub const FILE_DEVICE_SOUND: DEVICE_TYPE = 0x001d;
2918pub const FILE_DEVICE_STREAMS: DEVICE_TYPE = 0x001e;
2919pub const FILE_DEVICE_TAPE: DEVICE_TYPE = 0x001f;
2920pub const FILE_DEVICE_TAPE_FILE_SYSTEM: DEVICE_TYPE = 0x0020;
2921pub const FILE_DEVICE_TRANSPORT: DEVICE_TYPE = 0x0021;
2922pub const FILE_DEVICE_UNKNOWN: DEVICE_TYPE = 0x0022;
2923pub const FILE_DEVICE_VIDEO: DEVICE_TYPE = 0x0023;
2924pub const FILE_DEVICE_VIRTUAL_DISK: DEVICE_TYPE = 0x0024;
2925pub const FILE_DEVICE_WAVE_IN: DEVICE_TYPE = 0x0025;
2926pub const FILE_DEVICE_WAVE_OUT: DEVICE_TYPE = 0x0026;
2927pub const FILE_DEVICE_8042_PORT: DEVICE_TYPE = 0x0027;
2928pub const FILE_DEVICE_NETWORK_REDIRECTOR: DEVICE_TYPE = 0x0028;
2929pub const FILE_DEVICE_BATTERY: DEVICE_TYPE = 0x0029;
2930pub const FILE_DEVICE_BUS_EXTENDER: DEVICE_TYPE = 0x002a;
2931pub const FILE_DEVICE_MODEM: DEVICE_TYPE = 0x002b;
2932pub const FILE_DEVICE_VDM: DEVICE_TYPE = 0x002c;
2933pub const FILE_DEVICE_MASS_STORAGE: DEVICE_TYPE = 0x002d;
2934pub const FILE_DEVICE_SMB: DEVICE_TYPE = 0x002e;
2935pub const FILE_DEVICE_KS: DEVICE_TYPE = 0x002f;
2936pub const FILE_DEVICE_CHANGER: DEVICE_TYPE = 0x0030;
2937pub const FILE_DEVICE_SMARTCARD: DEVICE_TYPE = 0x0031;
2938pub const FILE_DEVICE_ACPI: DEVICE_TYPE = 0x0032;
2939pub const FILE_DEVICE_DVD: DEVICE_TYPE = 0x0033;
2940pub const FILE_DEVICE_FULLSCREEN_VIDEO: DEVICE_TYPE = 0x0034;
2941pub const FILE_DEVICE_DFS_FILE_SYSTEM: DEVICE_TYPE = 0x0035;
2942pub const FILE_DEVICE_DFS_VOLUME: DEVICE_TYPE = 0x0036;
2943pub const FILE_DEVICE_SERENUM: DEVICE_TYPE = 0x0037;
2944pub const FILE_DEVICE_TERMSRV: DEVICE_TYPE = 0x0038;
2945pub const FILE_DEVICE_KSEC: DEVICE_TYPE = 0x0039;
2946pub const FILE_DEVICE_FIPS: DEVICE_TYPE = 0x003a;
2947pub const FILE_DEVICE_INFINIBAND: DEVICE_TYPE = 0x003b;
2948// TODO: missing values?
2949pub const FILE_DEVICE_VMBUS: DEVICE_TYPE = 0x003e;
2950pub const FILE_DEVICE_CRYPT_PROVIDER: DEVICE_TYPE = 0x003f;
2951pub const FILE_DEVICE_WPD: DEVICE_TYPE = 0x0040;
2952pub const FILE_DEVICE_BLUETOOTH: DEVICE_TYPE = 0x0041;
2953pub const FILE_DEVICE_MT_COMPOSITE: DEVICE_TYPE = 0x0042;
2954pub const FILE_DEVICE_MT_TRANSPORT: DEVICE_TYPE = 0x0043;
2955pub const FILE_DEVICE_BIOMETRIC: DEVICE_TYPE = 0x0044;
2956pub const FILE_DEVICE_PMI: DEVICE_TYPE = 0x0045;
2957pub const FILE_DEVICE_EHSTOR: DEVICE_TYPE = 0x0046;
2958pub const FILE_DEVICE_DEVAPI: DEVICE_TYPE = 0x0047;
2959pub const FILE_DEVICE_GPIO: DEVICE_TYPE = 0x0048;
2960pub const FILE_DEVICE_USBEX: DEVICE_TYPE = 0x0049;
2961pub const FILE_DEVICE_CONSOLE: DEVICE_TYPE = 0x0050;
2962pub const FILE_DEVICE_NFP: DEVICE_TYPE = 0x0051;
2963pub const FILE_DEVICE_SYSENV: DEVICE_TYPE = 0x0052;
2964pub const FILE_DEVICE_VIRTUAL_BLOCK: DEVICE_TYPE = 0x0053;
2965pub const FILE_DEVICE_POINT_OF_SERVICE: DEVICE_TYPE = 0x0054;
2966pub const FILE_DEVICE_STORAGE_REPLICATION: DEVICE_TYPE = 0x0055;
2967pub const FILE_DEVICE_TRUST_ENV: DEVICE_TYPE = 0x0056;
2968pub const FILE_DEVICE_UCM: DEVICE_TYPE = 0x0057;
2969pub const FILE_DEVICE_UCMTCPCI: DEVICE_TYPE = 0x0058;
2970pub const FILE_DEVICE_PERSISTENT_MEMORY: DEVICE_TYPE = 0x0059;
2971pub const FILE_DEVICE_NVDIMM: DEVICE_TYPE = 0x005a;
2972pub const FILE_DEVICE_HOLOGRAPHIC: DEVICE_TYPE = 0x005b;
2973pub const FILE_DEVICE_SDFXHCI: DEVICE_TYPE = 0x005c;
2974
2975/// https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/buffer-descriptions-for-i-o-control-codes
2976pub const TransferType = enum(u2) {
2977 METHOD_BUFFERED = 0,
2978 METHOD_IN_DIRECT = 1,
2979 METHOD_OUT_DIRECT = 2,
2980 METHOD_NEITHER = 3,
2981};
2982
2983pub const FILE_ANY_ACCESS = 0;
2984pub const FILE_READ_ACCESS = 1;
2985pub const FILE_WRITE_ACCESS = 2;
2986
2987/// https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/defining-i-o-control-codes
2988pub fn CTL_CODE(deviceType: u16, function: u12, method: TransferType, access: u2) DWORD {
2989 return (@as(DWORD, deviceType) << 16) |
2990 (@as(DWORD, access) << 14) |
2991 (@as(DWORD, function) << 2) |
2992 @intFromEnum(method);
2993}
2994
2995pub const INVALID_HANDLE_VALUE = @as(HANDLE, @ptrFromInt(maxInt(usize)));
2996
2997pub const INVALID_FILE_ATTRIBUTES = @as(DWORD, maxInt(DWORD));
2998
2999pub const FILE_ALL_INFORMATION = extern struct {
3000 BasicInformation: FILE_BASIC_INFORMATION,
3001 StandardInformation: FILE_STANDARD_INFORMATION,
3002 InternalInformation: FILE_INTERNAL_INFORMATION,
3003 EaInformation: FILE_EA_INFORMATION,
3004 AccessInformation: FILE_ACCESS_INFORMATION,
3005 PositionInformation: FILE_POSITION_INFORMATION,
3006 ModeInformation: FILE_MODE_INFORMATION,
3007 AlignmentInformation: FILE_ALIGNMENT_INFORMATION,
3008 NameInformation: FILE_NAME_INFORMATION,
3009};
3010
3011pub const FILE_BASIC_INFORMATION = extern struct {
3012 CreationTime: LARGE_INTEGER,
3013 LastAccessTime: LARGE_INTEGER,
3014 LastWriteTime: LARGE_INTEGER,
3015 ChangeTime: LARGE_INTEGER,
3016 FileAttributes: ULONG,
3017};
3018
3019pub const FILE_STANDARD_INFORMATION = extern struct {
3020 AllocationSize: LARGE_INTEGER,
3021 EndOfFile: LARGE_INTEGER,
3022 NumberOfLinks: ULONG,
3023 DeletePending: BOOLEAN,
3024 Directory: BOOLEAN,
3025};
3026
3027pub const FILE_INTERNAL_INFORMATION = extern struct {
3028 IndexNumber: LARGE_INTEGER,
3029};
3030
3031pub const FILE_EA_INFORMATION = extern struct {
3032 EaSize: ULONG,
3033};
3034
3035pub const FILE_ACCESS_INFORMATION = extern struct {
3036 AccessFlags: ACCESS_MASK,
3037};
3038
3039pub const FILE_POSITION_INFORMATION = extern struct {
3040 CurrentByteOffset: LARGE_INTEGER,
3041};
3042
3043pub const FILE_END_OF_FILE_INFORMATION = extern struct {
3044 EndOfFile: LARGE_INTEGER,
3045};
3046
3047pub const FILE_MODE_INFORMATION = extern struct {
3048 Mode: ULONG,
3049};
3050
3051pub const FILE_ALIGNMENT_INFORMATION = extern struct {
3052 AlignmentRequirement: ULONG,
3053};
3054
3055pub const FILE_NAME_INFORMATION = extern struct {
3056 FileNameLength: ULONG,
3057 FileName: [1]WCHAR,
3058};
3059
3060pub const FILE_DISPOSITION_INFORMATION_EX = extern struct {
3061 /// combination of FILE_DISPOSITION_* flags
3062 Flags: ULONG,
3063};
3064
3065pub const FILE_DISPOSITION_DO_NOT_DELETE: ULONG = 0x00000000;
3066pub const FILE_DISPOSITION_DELETE: ULONG = 0x00000001;
3067pub const FILE_DISPOSITION_POSIX_SEMANTICS: ULONG = 0x00000002;
3068pub const FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK: ULONG = 0x00000004;
3069pub const FILE_DISPOSITION_ON_CLOSE: ULONG = 0x00000008;
3070pub const FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE: ULONG = 0x00000010;
3071
3072// FILE_RENAME_INFORMATION.Flags
3073pub const FILE_RENAME_REPLACE_IF_EXISTS = 0x00000001;
3074pub const FILE_RENAME_POSIX_SEMANTICS = 0x00000002;
3075pub const FILE_RENAME_SUPPRESS_PIN_STATE_INHERITANCE = 0x00000004;
3076pub const FILE_RENAME_SUPPRESS_STORAGE_RESERVE_INHERITANCE = 0x00000008;
3077pub const FILE_RENAME_NO_INCREASE_AVAILABLE_SPACE = 0x00000010;
3078pub const FILE_RENAME_NO_DECREASE_AVAILABLE_SPACE = 0x00000020;
3079pub const FILE_RENAME_PRESERVE_AVAILABLE_SPACE = 0x00000030;
3080pub const FILE_RENAME_IGNORE_READONLY_ATTRIBUTE = 0x00000040;
3081pub const FILE_RENAME_FORCE_RESIZE_TARGET_SR = 0x00000080;
3082pub const FILE_RENAME_FORCE_RESIZE_SOURCE_SR = 0x00000100;
3083pub const FILE_RENAME_FORCE_RESIZE_SR = 0x00000180;
3084
3085pub const FILE_RENAME_INFORMATION = extern struct {
3086 Flags: BOOLEAN,
3087 RootDirectory: ?HANDLE,
3088 FileNameLength: ULONG,
3089 FileName: [1]WCHAR,
3090};
3091
3092// FileRenameInformationEx (since .win10_rs1)
3093pub const FILE_RENAME_INFORMATION_EX = extern struct {
3094 Flags: ULONG,
3095 RootDirectory: ?HANDLE,
3096 FileNameLength: ULONG,
3097 FileName: [1]WCHAR,
3098};
3099
3100pub const IO_STATUS_BLOCK = extern struct {
3101 // "DUMMYUNIONNAME" expands to "u"
3102 u: extern union {
3103 Status: NTSTATUS,
3104 Pointer: ?*anyopaque,
3105 },
3106 Information: ULONG_PTR,
3107};
3108
3109pub const FILE_INFORMATION_CLASS = enum(c_int) {
3110 FileDirectoryInformation = 1,
3111 FileFullDirectoryInformation,
3112 FileBothDirectoryInformation,
3113 FileBasicInformation,
3114 FileStandardInformation,
3115 FileInternalInformation,
3116 FileEaInformation,
3117 FileAccessInformation,
3118 FileNameInformation,
3119 FileRenameInformation,
3120 FileLinkInformation,
3121 FileNamesInformation,
3122 FileDispositionInformation,
3123 FilePositionInformation,
3124 FileFullEaInformation,
3125 FileModeInformation,
3126 FileAlignmentInformation,
3127 FileAllInformation,
3128 FileAllocationInformation,
3129 FileEndOfFileInformation,
3130 FileAlternateNameInformation,
3131 FileStreamInformation,
3132 FilePipeInformation,
3133 FilePipeLocalInformation,
3134 FilePipeRemoteInformation,
3135 FileMailslotQueryInformation,
3136 FileMailslotSetInformation,
3137 FileCompressionInformation,
3138 FileObjectIdInformation,
3139 FileCompletionInformation,
3140 FileMoveClusterInformation,
3141 FileQuotaInformation,
3142 FileReparsePointInformation,
3143 FileNetworkOpenInformation,
3144 FileAttributeTagInformation,
3145 FileTrackingInformation,
3146 FileIdBothDirectoryInformation,
3147 FileIdFullDirectoryInformation,
3148 FileValidDataLengthInformation,
3149 FileShortNameInformation,
3150 FileIoCompletionNotificationInformation,
3151 FileIoStatusBlockRangeInformation,
3152 FileIoPriorityHintInformation,
3153 FileSfioReserveInformation,
3154 FileSfioVolumeInformation,
3155 FileHardLinkInformation,
3156 FileProcessIdsUsingFileInformation,
3157 FileNormalizedNameInformation,
3158 FileNetworkPhysicalNameInformation,
3159 FileIdGlobalTxDirectoryInformation,
3160 FileIsRemoteDeviceInformation,
3161 FileUnusedInformation,
3162 FileNumaNodeInformation,
3163 FileStandardLinkInformation,
3164 FileRemoteProtocolInformation,
3165 FileRenameInformationBypassAccessCheck,
3166 FileLinkInformationBypassAccessCheck,
3167 FileVolumeNameInformation,
3168 FileIdInformation,
3169 FileIdExtdDirectoryInformation,
3170 FileReplaceCompletionInformation,
3171 FileHardLinkFullIdInformation,
3172 FileIdExtdBothDirectoryInformation,
3173 FileDispositionInformationEx,
3174 FileRenameInformationEx,
3175 FileRenameInformationExBypassAccessCheck,
3176 FileDesiredStorageClassInformation,
3177 FileStatInformation,
3178 FileMemoryPartitionInformation,
3179 FileStatLxInformation,
3180 FileCaseSensitiveInformation,
3181 FileLinkInformationEx,
3182 FileLinkInformationExBypassAccessCheck,
3183 FileStorageReserveIdInformation,
3184 FileCaseSensitiveInformationForceAccessCheck,
3185 FileMaximumInformation,
3186};
3187
3188pub const FILE_ATTRIBUTE_TAG_INFO = extern struct {
3189 FileAttributes: DWORD,
3190 ReparseTag: DWORD,
3191};
3192
3193/// "If this bit is set, the file or directory represents another named entity in the system."
3194/// https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-point-tags
3195pub const reparse_tag_name_surrogate_bit = 0x20000000;
3196
3197pub const FILE_DISPOSITION_INFORMATION = extern struct {
3198 DeleteFile: BOOLEAN,
3199};
3200
3201pub const FILE_FS_DEVICE_INFORMATION = extern struct {
3202 DeviceType: DEVICE_TYPE,
3203 Characteristics: ULONG,
3204};
3205
3206pub const FILE_FS_VOLUME_INFORMATION = extern struct {
3207 VolumeCreationTime: LARGE_INTEGER,
3208 VolumeSerialNumber: ULONG,
3209 VolumeLabelLength: ULONG,
3210 SupportsObjects: BOOLEAN,
3211 // Flexible array member
3212 VolumeLabel: [1]WCHAR,
3213};
3214
3215pub const FS_INFORMATION_CLASS = enum(c_int) {
3216 FileFsVolumeInformation = 1,
3217 FileFsLabelInformation,
3218 FileFsSizeInformation,
3219 FileFsDeviceInformation,
3220 FileFsAttributeInformation,
3221 FileFsControlInformation,
3222 FileFsFullSizeInformation,
3223 FileFsObjectIdInformation,
3224 FileFsDriverPathInformation,
3225 FileFsVolumeFlagsInformation,
3226 FileFsSectorSizeInformation,
3227 FileFsDataCopyInformation,
3228 FileFsMetadataSizeInformation,
3229 FileFsFullSizeInformationEx,
3230 FileFsMaximumInformation,
3231};
3232
3233pub const OVERLAPPED = extern struct {
3234 Internal: ULONG_PTR,
3235 InternalHigh: ULONG_PTR,
3236 DUMMYUNIONNAME: extern union {
3237 DUMMYSTRUCTNAME: extern struct {
3238 Offset: DWORD,
3239 OffsetHigh: DWORD,
3240 },
3241 Pointer: ?PVOID,
3242 },
3243 hEvent: ?HANDLE,
3244};
3245
3246pub const OVERLAPPED_ENTRY = extern struct {
3247 lpCompletionKey: ULONG_PTR,
3248 lpOverlapped: *OVERLAPPED,
3249 Internal: ULONG_PTR,
3250 dwNumberOfBytesTransferred: DWORD,
3251};
3252
3253pub const MAX_PATH = 260;
3254
3255pub const FILE_INFO_BY_HANDLE_CLASS = enum(u32) {
3256 FileBasicInfo = 0,
3257 FileStandardInfo = 1,
3258 FileNameInfo = 2,
3259 FileRenameInfo = 3,
3260 FileDispositionInfo = 4,
3261 FileAllocationInfo = 5,
3262 FileEndOfFileInfo = 6,
3263 FileStreamInfo = 7,
3264 FileCompressionInfo = 8,
3265 FileAttributeTagInfo = 9,
3266 FileIdBothDirectoryInfo = 10,
3267 FileIdBothDirectoryRestartInfo = 11,
3268 FileIoPriorityHintInfo = 12,
3269 FileRemoteProtocolInfo = 13,
3270 FileFullDirectoryInfo = 14,
3271 FileFullDirectoryRestartInfo = 15,
3272 FileStorageInfo = 16,
3273 FileAlignmentInfo = 17,
3274 FileIdInfo = 18,
3275 FileIdExtdDirectoryInfo = 19,
3276 FileIdExtdDirectoryRestartInfo = 20,
3277};
3278
3279pub const BY_HANDLE_FILE_INFORMATION = extern struct {
3280 dwFileAttributes: DWORD,
3281 ftCreationTime: FILETIME,
3282 ftLastAccessTime: FILETIME,
3283 ftLastWriteTime: FILETIME,
3284 dwVolumeSerialNumber: DWORD,
3285 nFileSizeHigh: DWORD,
3286 nFileSizeLow: DWORD,
3287 nNumberOfLinks: DWORD,
3288 nFileIndexHigh: DWORD,
3289 nFileIndexLow: DWORD,
3290};
3291
3292pub const FILE_NAME_INFO = extern struct {
3293 FileNameLength: DWORD,
3294 FileName: [1]WCHAR,
3295};
3296
3297/// Return the normalized drive name. This is the default.
3298pub const FILE_NAME_NORMALIZED = 0x0;
3299
3300/// Return the opened file name (not normalized).
3301pub const FILE_NAME_OPENED = 0x8;
3302
3303/// Return the path with the drive letter. This is the default.
3304pub const VOLUME_NAME_DOS = 0x0;
3305
3306/// Return the path with a volume GUID path instead of the drive name.
3307pub const VOLUME_NAME_GUID = 0x1;
3308
3309/// Return the path with no drive information.
3310pub const VOLUME_NAME_NONE = 0x4;
3311
3312/// Return the path with the volume device path.
3313pub const VOLUME_NAME_NT = 0x2;
3314
3315pub const SECURITY_ATTRIBUTES = extern struct {
3316 nLength: DWORD,
3317 lpSecurityDescriptor: ?*anyopaque,
3318 bInheritHandle: BOOL,
3319};
3320
3321pub const PIPE_ACCESS_INBOUND = 0x00000001;
3322pub const PIPE_ACCESS_OUTBOUND = 0x00000002;
3323pub const PIPE_ACCESS_DUPLEX = 0x00000003;
3324
3325pub const PIPE_TYPE_BYTE = 0x00000000;
3326pub const PIPE_TYPE_MESSAGE = 0x00000004;
3327
3328pub const PIPE_READMODE_BYTE = 0x00000000;
3329pub const PIPE_READMODE_MESSAGE = 0x00000002;
3330
3331pub const PIPE_WAIT = 0x00000000;
3332pub const PIPE_NOWAIT = 0x00000001;
3333
3334pub const GENERIC_READ = 0x80000000;
3335pub const GENERIC_WRITE = 0x40000000;
3336pub const GENERIC_EXECUTE = 0x20000000;
3337pub const GENERIC_ALL = 0x10000000;
3338
3339pub const FILE_SHARE_DELETE = 0x00000004;
3340pub const FILE_SHARE_READ = 0x00000001;
3341pub const FILE_SHARE_WRITE = 0x00000002;
3342
3343pub const DELETE = 0x00010000;
3344pub const READ_CONTROL = 0x00020000;
3345pub const WRITE_DAC = 0x00040000;
3346pub const WRITE_OWNER = 0x00080000;
3347pub const SYNCHRONIZE = 0x00100000;
3348pub const STANDARD_RIGHTS_READ = READ_CONTROL;
3349pub const STANDARD_RIGHTS_WRITE = READ_CONTROL;
3350pub const STANDARD_RIGHTS_EXECUTE = READ_CONTROL;
3351pub const STANDARD_RIGHTS_REQUIRED = DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER;
3352pub const MAXIMUM_ALLOWED = 0x02000000;
3353
3354// disposition for NtCreateFile
3355pub const FILE_SUPERSEDE = 0;
3356pub const FILE_OPEN = 1;
3357pub const FILE_CREATE = 2;
3358pub const FILE_OPEN_IF = 3;
3359pub const FILE_OVERWRITE = 4;
3360pub const FILE_OVERWRITE_IF = 5;
3361pub const FILE_MAXIMUM_DISPOSITION = 5;
3362
3363// flags for NtCreateFile and NtOpenFile
3364pub const FILE_READ_DATA = 0x00000001;
3365pub const FILE_LIST_DIRECTORY = 0x00000001;
3366pub const FILE_WRITE_DATA = 0x00000002;
3367pub const FILE_ADD_FILE = 0x00000002;
3368pub const FILE_APPEND_DATA = 0x00000004;
3369pub const FILE_ADD_SUBDIRECTORY = 0x00000004;
3370pub const FILE_CREATE_PIPE_INSTANCE = 0x00000004;
3371pub const FILE_READ_EA = 0x00000008;
3372pub const FILE_WRITE_EA = 0x00000010;
3373pub const FILE_EXECUTE = 0x00000020;
3374pub const FILE_TRAVERSE = 0x00000020;
3375pub const FILE_DELETE_CHILD = 0x00000040;
3376pub const FILE_READ_ATTRIBUTES = 0x00000080;
3377pub const FILE_WRITE_ATTRIBUTES = 0x00000100;
3378
3379pub const FILE_DIRECTORY_FILE = 0x00000001;
3380pub const FILE_WRITE_THROUGH = 0x00000002;
3381pub const FILE_SEQUENTIAL_ONLY = 0x00000004;
3382pub const FILE_NO_INTERMEDIATE_BUFFERING = 0x00000008;
3383pub const FILE_SYNCHRONOUS_IO_ALERT = 0x00000010;
3384pub const FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020;
3385pub const FILE_NON_DIRECTORY_FILE = 0x00000040;
3386pub const FILE_CREATE_TREE_CONNECTION = 0x00000080;
3387pub const FILE_COMPLETE_IF_OPLOCKED = 0x00000100;
3388pub const FILE_NO_EA_KNOWLEDGE = 0x00000200;
3389pub const FILE_OPEN_FOR_RECOVERY = 0x00000400;
3390pub const FILE_RANDOM_ACCESS = 0x00000800;
3391pub const FILE_DELETE_ON_CLOSE = 0x00001000;
3392pub const FILE_OPEN_BY_FILE_ID = 0x00002000;
3393pub const FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000;
3394pub const FILE_NO_COMPRESSION = 0x00008000;
3395pub const FILE_RESERVE_OPFILTER = 0x00100000;
3396pub const FILE_OPEN_REPARSE_POINT = 0x00200000;
3397pub const FILE_OPEN_OFFLINE_FILE = 0x00400000;
3398pub const FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x00800000;
3399
3400pub const CREATE_ALWAYS = 2;
3401pub const CREATE_NEW = 1;
3402pub const OPEN_ALWAYS = 4;
3403pub const OPEN_EXISTING = 3;
3404pub const TRUNCATE_EXISTING = 5;
3405
3406pub const FILE_ATTRIBUTE_ARCHIVE = 0x20;
3407pub const FILE_ATTRIBUTE_COMPRESSED = 0x800;
3408pub const FILE_ATTRIBUTE_DEVICE = 0x40;
3409pub const FILE_ATTRIBUTE_DIRECTORY = 0x10;
3410pub const FILE_ATTRIBUTE_ENCRYPTED = 0x4000;
3411pub const FILE_ATTRIBUTE_HIDDEN = 0x2;
3412pub const FILE_ATTRIBUTE_INTEGRITY_STREAM = 0x8000;
3413pub const FILE_ATTRIBUTE_NORMAL = 0x80;
3414pub const FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x2000;
3415pub const FILE_ATTRIBUTE_NO_SCRUB_DATA = 0x20000;
3416pub const FILE_ATTRIBUTE_OFFLINE = 0x1000;
3417pub const FILE_ATTRIBUTE_READONLY = 0x1;
3418pub const FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS = 0x400000;
3419pub const FILE_ATTRIBUTE_RECALL_ON_OPEN = 0x40000;
3420pub const FILE_ATTRIBUTE_REPARSE_POINT = 0x400;
3421pub const FILE_ATTRIBUTE_SPARSE_FILE = 0x200;
3422pub const FILE_ATTRIBUTE_SYSTEM = 0x4;
3423pub const FILE_ATTRIBUTE_TEMPORARY = 0x100;
3424pub const FILE_ATTRIBUTE_VIRTUAL = 0x10000;
3425
3426pub const FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1ff;
3427pub const FILE_GENERIC_READ = STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE;
3428pub const FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE;
3429pub const FILE_GENERIC_EXECUTE = STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE;
3430
3431// Flags for NtCreateNamedPipeFile
3432// NamedPipeType
3433pub const FILE_PIPE_BYTE_STREAM_TYPE = 0x0;
3434pub const FILE_PIPE_MESSAGE_TYPE = 0x1;
3435pub const FILE_PIPE_ACCEPT_REMOTE_CLIENTS = 0x0;
3436pub const FILE_PIPE_REJECT_REMOTE_CLIENTS = 0x2;
3437pub const FILE_PIPE_TYPE_VALID_MASK = 0x3;
3438// CompletionMode
3439pub const FILE_PIPE_QUEUE_OPERATION = 0x0;
3440pub const FILE_PIPE_COMPLETE_OPERATION = 0x1;
3441// ReadMode
3442pub const FILE_PIPE_BYTE_STREAM_MODE = 0x0;
3443pub const FILE_PIPE_MESSAGE_MODE = 0x1;
3444
3445// flags for CreateEvent
3446pub const CREATE_EVENT_INITIAL_SET = 0x00000002;
3447pub const CREATE_EVENT_MANUAL_RESET = 0x00000001;
3448
3449pub const EVENT_ALL_ACCESS = 0x1F0003;
3450pub const EVENT_MODIFY_STATE = 0x0002;
3451
3452// MEMORY_BASIC_INFORMATION.Type flags for VirtualQuery
3453pub const MEM_IMAGE = 0x1000000;
3454pub const MEM_MAPPED = 0x40000;
3455pub const MEM_PRIVATE = 0x20000;
3456
3457pub const PROCESS_INFORMATION = extern struct {
3458 hProcess: HANDLE,
3459 hThread: HANDLE,
3460 dwProcessId: DWORD,
3461 dwThreadId: DWORD,
3462};
3463
3464pub const STARTUPINFOW = extern struct {
3465 cb: DWORD,
3466 lpReserved: ?LPWSTR,
3467 lpDesktop: ?LPWSTR,
3468 lpTitle: ?LPWSTR,
3469 dwX: DWORD,
3470 dwY: DWORD,
3471 dwXSize: DWORD,
3472 dwYSize: DWORD,
3473 dwXCountChars: DWORD,
3474 dwYCountChars: DWORD,
3475 dwFillAttribute: DWORD,
3476 dwFlags: DWORD,
3477 wShowWindow: WORD,
3478 cbReserved2: WORD,
3479 lpReserved2: ?*BYTE,
3480 hStdInput: ?HANDLE,
3481 hStdOutput: ?HANDLE,
3482 hStdError: ?HANDLE,
3483};
3484
3485pub const STARTF_FORCEONFEEDBACK = 0x00000040;
3486pub const STARTF_FORCEOFFFEEDBACK = 0x00000080;
3487pub const STARTF_PREVENTPINNING = 0x00002000;
3488pub const STARTF_RUNFULLSCREEN = 0x00000020;
3489pub const STARTF_TITLEISAPPID = 0x00001000;
3490pub const STARTF_TITLEISLINKNAME = 0x00000800;
3491pub const STARTF_UNTRUSTEDSOURCE = 0x00008000;
3492pub const STARTF_USECOUNTCHARS = 0x00000008;
3493pub const STARTF_USEFILLATTRIBUTE = 0x00000010;
3494pub const STARTF_USEHOTKEY = 0x00000200;
3495pub const STARTF_USEPOSITION = 0x00000004;
3496pub const STARTF_USESHOWWINDOW = 0x00000001;
3497pub const STARTF_USESIZE = 0x00000002;
3498pub const STARTF_USESTDHANDLES = 0x00000100;
3499
3500pub const INFINITE = 4294967295;
3501
3502pub const MAXIMUM_WAIT_OBJECTS = 64;
3503
3504pub const WAIT_ABANDONED = 0x00000080;
3505pub const WAIT_ABANDONED_0 = WAIT_ABANDONED + 0;
3506pub const WAIT_OBJECT_0 = 0x00000000;
3507pub const WAIT_TIMEOUT = 0x00000102;
3508pub const WAIT_FAILED = 0xFFFFFFFF;
3509
3510pub const HANDLE_FLAG_INHERIT = 0x00000001;
3511pub const HANDLE_FLAG_PROTECT_FROM_CLOSE = 0x00000002;
3512
3513pub const MOVEFILE_COPY_ALLOWED = 2;
3514pub const MOVEFILE_CREATE_HARDLINK = 16;
3515pub const MOVEFILE_DELAY_UNTIL_REBOOT = 4;
3516pub const MOVEFILE_FAIL_IF_NOT_TRACKABLE = 32;
3517pub const MOVEFILE_REPLACE_EXISTING = 1;
3518pub const MOVEFILE_WRITE_THROUGH = 8;
3519
3520pub const FILE_BEGIN = 0;
3521pub const FILE_CURRENT = 1;
3522pub const FILE_END = 2;
3523
3524pub const HEAP_CREATE_ENABLE_EXECUTE = 0x00040000;
3525pub const HEAP_REALLOC_IN_PLACE_ONLY = 0x00000010;
3526pub const HEAP_GENERATE_EXCEPTIONS = 0x00000004;
3527pub const HEAP_NO_SERIALIZE = 0x00000001;
3528
3529// AllocationType values
3530pub const MEM_COMMIT = 0x1000;
3531pub const MEM_RESERVE = 0x2000;
3532pub const MEM_FREE = 0x10000;
3533pub const MEM_RESET = 0x80000;
3534pub const MEM_RESET_UNDO = 0x1000000;
3535pub const MEM_LARGE_PAGES = 0x20000000;
3536pub const MEM_PHYSICAL = 0x400000;
3537pub const MEM_TOP_DOWN = 0x100000;
3538pub const MEM_WRITE_WATCH = 0x200000;
3539pub const MEM_RESERVE_PLACEHOLDER = 0x00040000;
3540pub const MEM_PRESERVE_PLACEHOLDER = 0x00000400;
3541
3542// Protect values
3543pub const PAGE_EXECUTE = 0x10;
3544pub const PAGE_EXECUTE_READ = 0x20;
3545pub const PAGE_EXECUTE_READWRITE = 0x40;
3546pub const PAGE_EXECUTE_WRITECOPY = 0x80;
3547pub const PAGE_NOACCESS = 0x01;
3548pub const PAGE_READONLY = 0x02;
3549pub const PAGE_READWRITE = 0x04;
3550pub const PAGE_WRITECOPY = 0x08;
3551pub const PAGE_TARGETS_INVALID = 0x40000000;
3552pub const PAGE_TARGETS_NO_UPDATE = 0x40000000; // Same as PAGE_TARGETS_INVALID
3553pub const PAGE_GUARD = 0x100;
3554pub const PAGE_NOCACHE = 0x200;
3555pub const PAGE_WRITECOMBINE = 0x400;
3556
3557// FreeType values
3558pub const MEM_COALESCE_PLACEHOLDERS = 0x1;
3559pub const MEM_RESERVE_PLACEHOLDERS = 0x2;
3560pub const MEM_DECOMMIT = 0x4000;
3561pub const MEM_RELEASE = 0x8000;
3562
3563pub const PTHREAD_START_ROUTINE = *const fn (LPVOID) callconv(.winapi) DWORD;
3564pub const LPTHREAD_START_ROUTINE = PTHREAD_START_ROUTINE;
3565
3566pub const WIN32_FIND_DATAW = extern struct {
3567 dwFileAttributes: DWORD,
3568 ftCreationTime: FILETIME,
3569 ftLastAccessTime: FILETIME,
3570 ftLastWriteTime: FILETIME,
3571 nFileSizeHigh: DWORD,
3572 nFileSizeLow: DWORD,
3573 dwReserved0: DWORD,
3574 dwReserved1: DWORD,
3575 cFileName: [260]u16,
3576 cAlternateFileName: [14]u16,
3577};
3578
3579pub const FILETIME = extern struct {
3580 dwLowDateTime: DWORD,
3581 dwHighDateTime: DWORD,
3582};
3583
3584pub const SYSTEM_INFO = extern struct {
3585 anon1: extern union {
3586 dwOemId: DWORD,
3587 anon2: extern struct {
3588 wProcessorArchitecture: WORD,
3589 wReserved: WORD,
3590 },
3591 },
3592 dwPageSize: DWORD,
3593 lpMinimumApplicationAddress: LPVOID,
3594 lpMaximumApplicationAddress: LPVOID,
3595 dwActiveProcessorMask: DWORD_PTR,
3596 dwNumberOfProcessors: DWORD,
3597 dwProcessorType: DWORD,
3598 dwAllocationGranularity: DWORD,
3599 wProcessorLevel: WORD,
3600 wProcessorRevision: WORD,
3601};
3602
3603pub const HRESULT = c_long;
3604
3605pub const KNOWNFOLDERID = GUID;
3606pub const GUID = extern struct {
3607 Data1: u32,
3608 Data2: u16,
3609 Data3: u16,
3610 Data4: [8]u8,
3611
3612 const hex_offsets = switch (builtin.target.cpu.arch.endian()) {
3613 .big => [16]u6{
3614 0, 2, 4, 6,
3615 9, 11, 14, 16,
3616 19, 21, 24, 26,
3617 28, 30, 32, 34,
3618 },
3619 .little => [16]u6{
3620 6, 4, 2, 0,
3621 11, 9, 16, 14,
3622 19, 21, 24, 26,
3623 28, 30, 32, 34,
3624 },
3625 };
3626
3627 pub fn parse(s: []const u8) GUID {
3628 assert(s[0] == '{');
3629 assert(s[37] == '}');
3630 return parseNoBraces(s[1 .. s.len - 1]) catch @panic("invalid GUID string");
3631 }
3632
3633 pub fn parseNoBraces(s: []const u8) !GUID {
3634 assert(s.len == 36);
3635 assert(s[8] == '-');
3636 assert(s[13] == '-');
3637 assert(s[18] == '-');
3638 assert(s[23] == '-');
3639 var bytes: [16]u8 = undefined;
3640 for (hex_offsets, 0..) |hex_offset, i| {
3641 bytes[i] = (try std.fmt.charToDigit(s[hex_offset], 16)) << 4 |
3642 try std.fmt.charToDigit(s[hex_offset + 1], 16);
3643 }
3644 return @as(GUID, @bitCast(bytes));
3645 }
3646};
3647
3648test GUID {
3649 try std.testing.expectEqual(
3650 GUID{
3651 .Data1 = 0x01234567,
3652 .Data2 = 0x89ab,
3653 .Data3 = 0xef10,
3654 .Data4 = "\x32\x54\x76\x98\xba\xdc\xfe\x91".*,
3655 },
3656 GUID.parse("{01234567-89AB-EF10-3254-7698badcfe91}"),
3657 );
3658}
3659
3660pub const FOLDERID_LocalAppData = GUID.parse("{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}");
3661
3662pub const KF_FLAG_DEFAULT = 0;
3663pub const KF_FLAG_NO_APPCONTAINER_REDIRECTION = 65536;
3664pub const KF_FLAG_CREATE = 32768;
3665pub const KF_FLAG_DONT_VERIFY = 16384;
3666pub const KF_FLAG_DONT_UNEXPAND = 8192;
3667pub const KF_FLAG_NO_ALIAS = 4096;
3668pub const KF_FLAG_INIT = 2048;
3669pub const KF_FLAG_DEFAULT_PATH = 1024;
3670pub const KF_FLAG_NOT_PARENT_RELATIVE = 512;
3671pub const KF_FLAG_SIMPLE_IDLIST = 256;
3672pub const KF_FLAG_ALIAS_ONLY = -2147483648;
3673
3674pub const S_OK = 0;
3675pub const S_FALSE = 0x00000001;
3676pub const E_NOTIMPL = @as(c_long, @bitCast(@as(c_ulong, 0x80004001)));
3677pub const E_NOINTERFACE = @as(c_long, @bitCast(@as(c_ulong, 0x80004002)));
3678pub const E_POINTER = @as(c_long, @bitCast(@as(c_ulong, 0x80004003)));
3679pub const E_ABORT = @as(c_long, @bitCast(@as(c_ulong, 0x80004004)));
3680pub const E_FAIL = @as(c_long, @bitCast(@as(c_ulong, 0x80004005)));
3681pub const E_UNEXPECTED = @as(c_long, @bitCast(@as(c_ulong, 0x8000FFFF)));
3682pub const E_ACCESSDENIED = @as(c_long, @bitCast(@as(c_ulong, 0x80070005)));
3683pub const E_HANDLE = @as(c_long, @bitCast(@as(c_ulong, 0x80070006)));
3684pub const E_OUTOFMEMORY = @as(c_long, @bitCast(@as(c_ulong, 0x8007000E)));
3685pub const E_INVALIDARG = @as(c_long, @bitCast(@as(c_ulong, 0x80070057)));
3686
3687pub fn HRESULT_CODE(hr: HRESULT) Win32Error {
3688 return @enumFromInt(hr & 0xFFFF);
3689}
3690
3691pub const FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
3692pub const FILE_FLAG_DELETE_ON_CLOSE = 0x04000000;
3693pub const FILE_FLAG_NO_BUFFERING = 0x20000000;
3694pub const FILE_FLAG_OPEN_NO_RECALL = 0x00100000;
3695pub const FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000;
3696pub const FILE_FLAG_OVERLAPPED = 0x40000000;
3697pub const FILE_FLAG_POSIX_SEMANTICS = 0x0100000;
3698pub const FILE_FLAG_RANDOM_ACCESS = 0x10000000;
3699pub const FILE_FLAG_SESSION_AWARE = 0x00800000;
3700pub const FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000;
3701pub const FILE_FLAG_WRITE_THROUGH = 0x80000000;
3702
3703pub const RECT = extern struct {
3704 left: LONG,
3705 top: LONG,
3706 right: LONG,
3707 bottom: LONG,
3708};
3709
3710pub const SMALL_RECT = extern struct {
3711 Left: SHORT,
3712 Top: SHORT,
3713 Right: SHORT,
3714 Bottom: SHORT,
3715};
3716
3717pub const POINT = extern struct {
3718 x: LONG,
3719 y: LONG,
3720};
3721
3722pub const COORD = extern struct {
3723 X: SHORT,
3724 Y: SHORT,
3725};
3726
3727pub const CREATE_UNICODE_ENVIRONMENT = 1024;
3728
3729pub const TLS_OUT_OF_INDEXES = 4294967295;
3730pub const IMAGE_TLS_DIRECTORY = extern struct {
3731 StartAddressOfRawData: usize,
3732 EndAddressOfRawData: usize,
3733 AddressOfIndex: usize,
3734 AddressOfCallBacks: usize,
3735 SizeOfZeroFill: u32,
3736 Characteristics: u32,
3737};
3738pub const IMAGE_TLS_DIRECTORY64 = IMAGE_TLS_DIRECTORY;
3739pub const IMAGE_TLS_DIRECTORY32 = IMAGE_TLS_DIRECTORY;
3740
3741pub const PIMAGE_TLS_CALLBACK = ?*const fn (PVOID, DWORD, PVOID) callconv(.winapi) void;
3742
3743pub const PROV_RSA_FULL = 1;
3744
3745pub const REGSAM = ACCESS_MASK;
3746pub const ACCESS_MASK = DWORD;
3747pub const LSTATUS = LONG;
3748
3749pub const SECTION_INHERIT = enum(c_int) {
3750 ViewShare = 0,
3751 ViewUnmap = 1,
3752};
3753
3754pub const SECTION_QUERY = 0x0001;
3755pub const SECTION_MAP_WRITE = 0x0002;
3756pub const SECTION_MAP_READ = 0x0004;
3757pub const SECTION_MAP_EXECUTE = 0x0008;
3758pub const SECTION_EXTEND_SIZE = 0x0010;
3759pub const SECTION_ALL_ACCESS =
3760 STANDARD_RIGHTS_REQUIRED |
3761 SECTION_QUERY |
3762 SECTION_MAP_WRITE |
3763 SECTION_MAP_READ |
3764 SECTION_MAP_EXECUTE |
3765 SECTION_EXTEND_SIZE;
3766
3767pub const SEC_64K_PAGES = 0x80000;
3768pub const SEC_FILE = 0x800000;
3769pub const SEC_IMAGE = 0x1000000;
3770pub const SEC_PROTECTED_IMAGE = 0x2000000;
3771pub const SEC_RESERVE = 0x4000000;
3772pub const SEC_COMMIT = 0x8000000;
3773pub const SEC_IMAGE_NO_EXECUTE = SEC_IMAGE | SEC_NOCACHE;
3774pub const SEC_NOCACHE = 0x10000000;
3775pub const SEC_WRITECOMBINE = 0x40000000;
3776pub const SEC_LARGE_PAGES = 0x80000000;
3777
3778pub const HKEY = *opaque {};
3779
3780pub const HKEY_CLASSES_ROOT: HKEY = @ptrFromInt(0x80000000);
3781pub const HKEY_CURRENT_USER: HKEY = @ptrFromInt(0x80000001);
3782pub const HKEY_LOCAL_MACHINE: HKEY = @ptrFromInt(0x80000002);
3783pub const HKEY_USERS: HKEY = @ptrFromInt(0x80000003);
3784pub const HKEY_PERFORMANCE_DATA: HKEY = @ptrFromInt(0x80000004);
3785pub const HKEY_PERFORMANCE_TEXT: HKEY = @ptrFromInt(0x80000050);
3786pub const HKEY_PERFORMANCE_NLSTEXT: HKEY = @ptrFromInt(0x80000060);
3787pub const HKEY_CURRENT_CONFIG: HKEY = @ptrFromInt(0x80000005);
3788pub const HKEY_DYN_DATA: HKEY = @ptrFromInt(0x80000006);
3789pub const HKEY_CURRENT_USER_LOCAL_SETTINGS: HKEY = @ptrFromInt(0x80000007);
3790
3791/// Combines the STANDARD_RIGHTS_REQUIRED, KEY_QUERY_VALUE, KEY_SET_VALUE, KEY_CREATE_SUB_KEY,
3792/// KEY_ENUMERATE_SUB_KEYS, KEY_NOTIFY, and KEY_CREATE_LINK access rights.
3793pub const KEY_ALL_ACCESS = 0xF003F;
3794/// Reserved for system use.
3795pub const KEY_CREATE_LINK = 0x0020;
3796/// Required to create a subkey of a registry key.
3797pub const KEY_CREATE_SUB_KEY = 0x0004;
3798/// Required to enumerate the subkeys of a registry key.
3799pub const KEY_ENUMERATE_SUB_KEYS = 0x0008;
3800/// Equivalent to KEY_READ.
3801pub const KEY_EXECUTE = 0x20019;
3802/// Required to request change notifications for a registry key or for subkeys of a registry key.
3803pub const KEY_NOTIFY = 0x0010;
3804/// Required to query the values of a registry key.
3805pub const KEY_QUERY_VALUE = 0x0001;
3806/// Combines the STANDARD_RIGHTS_READ, KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS, and KEY_NOTIFY values.
3807pub const KEY_READ = 0x20019;
3808/// Required to create, delete, or set a registry value.
3809pub const KEY_SET_VALUE = 0x0002;
3810/// Indicates that an application on 64-bit Windows should operate on the 32-bit registry view.
3811/// This flag is ignored by 32-bit Windows.
3812pub const KEY_WOW64_32KEY = 0x0200;
3813/// Indicates that an application on 64-bit Windows should operate on the 64-bit registry view.
3814/// This flag is ignored by 32-bit Windows.
3815pub const KEY_WOW64_64KEY = 0x0100;
3816/// Combines the STANDARD_RIGHTS_WRITE, KEY_SET_VALUE, and KEY_CREATE_SUB_KEY access rights.
3817pub const KEY_WRITE = 0x20006;
3818
3819/// Open symbolic link.
3820pub const REG_OPTION_OPEN_LINK: DWORD = 0x8;
3821
3822pub const RTL_QUERY_REGISTRY_TABLE = extern struct {
3823 QueryRoutine: RTL_QUERY_REGISTRY_ROUTINE,
3824 Flags: ULONG,
3825 Name: ?PWSTR,
3826 EntryContext: ?*anyopaque,
3827 DefaultType: ULONG,
3828 DefaultData: ?*anyopaque,
3829 DefaultLength: ULONG,
3830};
3831
3832pub const RTL_QUERY_REGISTRY_ROUTINE = ?*const fn (
3833 PWSTR,
3834 ULONG,
3835 ?*anyopaque,
3836 ULONG,
3837 ?*anyopaque,
3838 ?*anyopaque,
3839) callconv(.winapi) NTSTATUS;
3840
3841/// Path is a full path
3842pub const RTL_REGISTRY_ABSOLUTE = 0;
3843/// \Registry\Machine\System\CurrentControlSet\Services
3844pub const RTL_REGISTRY_SERVICES = 1;
3845/// \Registry\Machine\System\CurrentControlSet\Control
3846pub const RTL_REGISTRY_CONTROL = 2;
3847/// \Registry\Machine\Software\Microsoft\Windows NT\CurrentVersion
3848pub const RTL_REGISTRY_WINDOWS_NT = 3;
3849/// \Registry\Machine\Hardware\DeviceMap
3850pub const RTL_REGISTRY_DEVICEMAP = 4;
3851/// \Registry\User\CurrentUser
3852pub const RTL_REGISTRY_USER = 5;
3853pub const RTL_REGISTRY_MAXIMUM = 6;
3854
3855/// Low order bits are registry handle
3856pub const RTL_REGISTRY_HANDLE = 0x40000000;
3857/// Indicates the key node is optional
3858pub const RTL_REGISTRY_OPTIONAL = 0x80000000;
3859
3860/// Name is a subkey and remainder of table or until next subkey are value
3861/// names for that subkey to look at.
3862pub const RTL_QUERY_REGISTRY_SUBKEY = 0x00000001;
3863
3864/// Reset current key to original key for this and all following table entries.
3865pub const RTL_QUERY_REGISTRY_TOPKEY = 0x00000002;
3866
3867/// Fail if no match found for this table entry.
3868pub const RTL_QUERY_REGISTRY_REQUIRED = 0x00000004;
3869
3870/// Used to mark a table entry that has no value name, just wants a call out, not
3871/// an enumeration of all values.
3872pub const RTL_QUERY_REGISTRY_NOVALUE = 0x00000008;
3873
3874/// Used to suppress the expansion of REG_MULTI_SZ into multiple callouts or
3875/// to prevent the expansion of environment variable values in REG_EXPAND_SZ.
3876pub const RTL_QUERY_REGISTRY_NOEXPAND = 0x00000010;
3877
3878/// QueryRoutine field ignored. EntryContext field points to location to store value.
3879/// For null terminated strings, EntryContext points to UNICODE_STRING structure that
3880/// that describes maximum size of buffer. If .Buffer field is NULL then a buffer is
3881/// allocated.
3882pub const RTL_QUERY_REGISTRY_DIRECT = 0x00000020;
3883
3884/// Used to delete value keys after they are queried.
3885pub const RTL_QUERY_REGISTRY_DELETE = 0x00000040;
3886
3887/// Use this flag with the RTL_QUERY_REGISTRY_DIRECT flag to verify that the REG_XXX type
3888/// of the stored registry value matches the type expected by the caller.
3889/// If the types do not match, the call fails.
3890pub const RTL_QUERY_REGISTRY_TYPECHECK = 0x00000100;
3891
3892pub const REG = struct {
3893 /// No value type
3894 pub const NONE: ULONG = 0;
3895 /// Unicode nul terminated string
3896 pub const SZ: ULONG = 1;
3897 /// Unicode nul terminated string (with environment variable references)
3898 pub const EXPAND_SZ: ULONG = 2;
3899 /// Free form binary
3900 pub const BINARY: ULONG = 3;
3901 /// 32-bit number
3902 pub const DWORD: ULONG = 4;
3903 /// 32-bit number (same as REG_DWORD)
3904 pub const DWORD_LITTLE_ENDIAN: ULONG = 4;
3905 /// 32-bit number
3906 pub const DWORD_BIG_ENDIAN: ULONG = 5;
3907 /// Symbolic Link (unicode)
3908 pub const LINK: ULONG = 6;
3909 /// Multiple Unicode strings
3910 pub const MULTI_SZ: ULONG = 7;
3911 /// Resource list in the resource map
3912 pub const RESOURCE_LIST: ULONG = 8;
3913 /// Resource list in the hardware description
3914 pub const FULL_RESOURCE_DESCRIPTOR: ULONG = 9;
3915 pub const RESOURCE_REQUIREMENTS_LIST: ULONG = 10;
3916 /// 64-bit number
3917 pub const QWORD: ULONG = 11;
3918 /// 64-bit number (same as REG_QWORD)
3919 pub const QWORD_LITTLE_ENDIAN: ULONG = 11;
3920};
3921
3922pub const FILE_NOTIFY_INFORMATION = extern struct {
3923 NextEntryOffset: DWORD,
3924 Action: DWORD,
3925 FileNameLength: DWORD,
3926 // Flexible array member
3927 // FileName: [1]WCHAR,
3928};
3929
3930pub const FILE_ACTION_ADDED = 0x00000001;
3931pub const FILE_ACTION_REMOVED = 0x00000002;
3932pub const FILE_ACTION_MODIFIED = 0x00000003;
3933pub const FILE_ACTION_RENAMED_OLD_NAME = 0x00000004;
3934pub const FILE_ACTION_RENAMED_NEW_NAME = 0x00000005;
3935
3936pub const LPOVERLAPPED_COMPLETION_ROUTINE = ?*const fn (DWORD, DWORD, *OVERLAPPED) callconv(.winapi) void;
3937
3938pub const FileNotifyChangeFilter = packed struct(DWORD) {
3939 file_name: bool = false,
3940 dir_name: bool = false,
3941 attributes: bool = false,
3942 size: bool = false,
3943 last_write: bool = false,
3944 last_access: bool = false,
3945 creation: bool = false,
3946 ea: bool = false,
3947 security: bool = false,
3948 stream_name: bool = false,
3949 stream_size: bool = false,
3950 stream_write: bool = false,
3951 _pad: u20 = 0,
3952};
3953
3954pub const CONSOLE_SCREEN_BUFFER_INFO = extern struct {
3955 dwSize: COORD,
3956 dwCursorPosition: COORD,
3957 wAttributes: WORD,
3958 srWindow: SMALL_RECT,
3959 dwMaximumWindowSize: COORD,
3960};
3961
3962pub const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4;
3963pub const DISABLE_NEWLINE_AUTO_RETURN = 0x8;
3964
3965pub const FOREGROUND_BLUE = 1;
3966pub const FOREGROUND_GREEN = 2;
3967pub const FOREGROUND_RED = 4;
3968pub const FOREGROUND_INTENSITY = 8;
3969
3970pub const LIST_ENTRY = extern struct {
3971 Flink: *LIST_ENTRY,
3972 Blink: *LIST_ENTRY,
3973};
3974
3975pub const RTL_CRITICAL_SECTION_DEBUG = extern struct {
3976 Type: WORD,
3977 CreatorBackTraceIndex: WORD,
3978 CriticalSection: *RTL_CRITICAL_SECTION,
3979 ProcessLocksList: LIST_ENTRY,
3980 EntryCount: DWORD,
3981 ContentionCount: DWORD,
3982 Flags: DWORD,
3983 CreatorBackTraceIndexHigh: WORD,
3984 SpareWORD: WORD,
3985};
3986
3987pub const RTL_CRITICAL_SECTION = extern struct {
3988 DebugInfo: *RTL_CRITICAL_SECTION_DEBUG,
3989 LockCount: LONG,
3990 RecursionCount: LONG,
3991 OwningThread: HANDLE,
3992 LockSemaphore: HANDLE,
3993 SpinCount: ULONG_PTR,
3994};
3995
3996pub const CRITICAL_SECTION = RTL_CRITICAL_SECTION;
3997pub const INIT_ONCE = RTL_RUN_ONCE;
3998pub const INIT_ONCE_STATIC_INIT = RTL_RUN_ONCE_INIT;
3999pub const INIT_ONCE_FN = *const fn (InitOnce: *INIT_ONCE, Parameter: ?*anyopaque, Context: ?*anyopaque) callconv(.winapi) BOOL;
4000
4001pub const RTL_RUN_ONCE = extern struct {
4002 Ptr: ?*anyopaque,
4003};
4004
4005pub const RTL_RUN_ONCE_INIT = RTL_RUN_ONCE{ .Ptr = null };
4006
4007pub const COINIT = struct {
4008 pub const APARTMENTTHREADED = 2;
4009 pub const MULTITHREADED = 0;
4010 pub const DISABLE_OLE1DDE = 4;
4011 pub const SPEED_OVER_MEMORY = 8;
4012};
4013
4014pub const MEMORY_BASIC_INFORMATION = extern struct {
4015 BaseAddress: PVOID,
4016 AllocationBase: PVOID,
4017 AllocationProtect: DWORD,
4018 PartitionId: WORD,
4019 RegionSize: SIZE_T,
4020 State: DWORD,
4021 Protect: DWORD,
4022 Type: DWORD,
4023};
4024
4025pub const PMEMORY_BASIC_INFORMATION = *MEMORY_BASIC_INFORMATION;
4026
4027/// > The maximum path of 32,767 characters is approximate, because the "\\?\"
4028/// > prefix may be expanded to a longer string by the system at run time, and
4029/// > this expansion applies to the total length.
4030/// from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation
4031pub const PATH_MAX_WIDE = 32767;
4032
4033/// > [Each file name component can be] up to the value returned in the
4034/// > lpMaximumComponentLength parameter of the GetVolumeInformation function
4035/// > (this value is commonly 255 characters)
4036/// from https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
4037///
4038/// > The value that is stored in the variable that *lpMaximumComponentLength points to is
4039/// > used to indicate that a specified file system supports long names. For example, for
4040/// > a FAT file system that supports long names, the function stores the value 255, rather
4041/// > than the previous 8.3 indicator. Long names can also be supported on systems that use
4042/// > the NTFS file system.
4043/// from https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationw
4044///
4045/// The assumption being made here is that while lpMaximumComponentLength may vary, it will never
4046/// be larger than 255.
4047///
4048/// TODO: More verification of this assumption.
4049pub const NAME_MAX = 255;
4050
4051pub const FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
4052pub const FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000;
4053pub const FORMAT_MESSAGE_FROM_HMODULE = 0x00000800;
4054pub const FORMAT_MESSAGE_FROM_STRING = 0x00000400;
4055pub const FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
4056pub const FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
4057pub const FORMAT_MESSAGE_MAX_WIDTH_MASK = 0x000000FF;
4058
4059pub const EXCEPTION_DATATYPE_MISALIGNMENT = 0x80000002;
4060pub const EXCEPTION_ACCESS_VIOLATION = 0xc0000005;
4061pub const EXCEPTION_ILLEGAL_INSTRUCTION = 0xc000001d;
4062pub const EXCEPTION_STACK_OVERFLOW = 0xc00000fd;
4063pub const EXCEPTION_CONTINUE_SEARCH = 0;
4064
4065pub const EXCEPTION_RECORD = extern struct {
4066 ExceptionCode: u32,
4067 ExceptionFlags: u32,
4068 ExceptionRecord: *EXCEPTION_RECORD,
4069 ExceptionAddress: *anyopaque,
4070 NumberParameters: u32,
4071 ExceptionInformation: [15]usize,
4072};
4073
4074pub const FLOATING_SAVE_AREA = switch (native_arch) {
4075 .x86 => extern struct {
4076 ControlWord: DWORD,
4077 StatusWord: DWORD,
4078 TagWord: DWORD,
4079 ErrorOffset: DWORD,
4080 ErrorSelector: DWORD,
4081 DataOffset: DWORD,
4082 DataSelector: DWORD,
4083 RegisterArea: [80]BYTE,
4084 Cr0NpxState: DWORD,
4085 },
4086 else => @compileError("FLOATING_SAVE_AREA only defined on x86"),
4087};
4088
4089pub const M128A = switch (native_arch) {
4090 .x86_64 => extern struct {
4091 Low: ULONGLONG,
4092 High: LONGLONG,
4093 },
4094 else => @compileError("M128A only defined on x86_64"),
4095};
4096
4097pub const XMM_SAVE_AREA32 = switch (native_arch) {
4098 .x86_64 => extern struct {
4099 ControlWord: WORD,
4100 StatusWord: WORD,
4101 TagWord: BYTE,
4102 Reserved1: BYTE,
4103 ErrorOpcode: WORD,
4104 ErrorOffset: DWORD,
4105 ErrorSelector: WORD,
4106 Reserved2: WORD,
4107 DataOffset: DWORD,
4108 DataSelector: WORD,
4109 Reserved3: WORD,
4110 MxCsr: DWORD,
4111 MxCsr_Mask: DWORD,
4112 FloatRegisters: [8]M128A,
4113 XmmRegisters: [16]M128A,
4114 Reserved4: [96]BYTE,
4115 },
4116 else => @compileError("XMM_SAVE_AREA32 only defined on x86_64"),
4117};
4118
4119pub const NEON128 = switch (native_arch) {
4120 .thumb => extern struct {
4121 Low: ULONGLONG,
4122 High: LONGLONG,
4123 },
4124 .aarch64 => extern union {
4125 DUMMYSTRUCTNAME: extern struct {
4126 Low: ULONGLONG,
4127 High: LONGLONG,
4128 },
4129 D: [2]f64,
4130 S: [4]f32,
4131 H: [8]WORD,
4132 B: [16]BYTE,
4133 },
4134 else => @compileError("NEON128 only defined on aarch64"),
4135};
4136
4137pub const CONTEXT = switch (native_arch) {
4138 .x86 => extern struct {
4139 ContextFlags: DWORD,
4140 Dr0: DWORD,
4141 Dr1: DWORD,
4142 Dr2: DWORD,
4143 Dr3: DWORD,
4144 Dr6: DWORD,
4145 Dr7: DWORD,
4146 FloatSave: FLOATING_SAVE_AREA,
4147 SegGs: DWORD,
4148 SegFs: DWORD,
4149 SegEs: DWORD,
4150 SegDs: DWORD,
4151 Edi: DWORD,
4152 Esi: DWORD,
4153 Ebx: DWORD,
4154 Edx: DWORD,
4155 Ecx: DWORD,
4156 Eax: DWORD,
4157 Ebp: DWORD,
4158 Eip: DWORD,
4159 SegCs: DWORD,
4160 EFlags: DWORD,
4161 Esp: DWORD,
4162 SegSs: DWORD,
4163 ExtendedRegisters: [512]BYTE,
4164
4165 pub fn getRegs(ctx: *const CONTEXT) struct { bp: usize, ip: usize, sp: usize } {
4166 return .{ .bp = ctx.Ebp, .ip = ctx.Eip, .sp = ctx.Esp };
4167 }
4168 },
4169 .x86_64 => extern struct {
4170 P1Home: DWORD64 align(16),
4171 P2Home: DWORD64,
4172 P3Home: DWORD64,
4173 P4Home: DWORD64,
4174 P5Home: DWORD64,
4175 P6Home: DWORD64,
4176 ContextFlags: DWORD,
4177 MxCsr: DWORD,
4178 SegCs: WORD,
4179 SegDs: WORD,
4180 SegEs: WORD,
4181 SegFs: WORD,
4182 SegGs: WORD,
4183 SegSs: WORD,
4184 EFlags: DWORD,
4185 Dr0: DWORD64,
4186 Dr1: DWORD64,
4187 Dr2: DWORD64,
4188 Dr3: DWORD64,
4189 Dr6: DWORD64,
4190 Dr7: DWORD64,
4191 Rax: DWORD64,
4192 Rcx: DWORD64,
4193 Rdx: DWORD64,
4194 Rbx: DWORD64,
4195 Rsp: DWORD64,
4196 Rbp: DWORD64,
4197 Rsi: DWORD64,
4198 Rdi: DWORD64,
4199 R8: DWORD64,
4200 R9: DWORD64,
4201 R10: DWORD64,
4202 R11: DWORD64,
4203 R12: DWORD64,
4204 R13: DWORD64,
4205 R14: DWORD64,
4206 R15: DWORD64,
4207 Rip: DWORD64,
4208 DUMMYUNIONNAME: extern union {
4209 FltSave: XMM_SAVE_AREA32,
4210 FloatSave: XMM_SAVE_AREA32,
4211 DUMMYSTRUCTNAME: extern struct {
4212 Header: [2]M128A,
4213 Legacy: [8]M128A,
4214 Xmm0: M128A,
4215 Xmm1: M128A,
4216 Xmm2: M128A,
4217 Xmm3: M128A,
4218 Xmm4: M128A,
4219 Xmm5: M128A,
4220 Xmm6: M128A,
4221 Xmm7: M128A,
4222 Xmm8: M128A,
4223 Xmm9: M128A,
4224 Xmm10: M128A,
4225 Xmm11: M128A,
4226 Xmm12: M128A,
4227 Xmm13: M128A,
4228 Xmm14: M128A,
4229 Xmm15: M128A,
4230 },
4231 },
4232 VectorRegister: [26]M128A,
4233 VectorControl: DWORD64,
4234 DebugControl: DWORD64,
4235 LastBranchToRip: DWORD64,
4236 LastBranchFromRip: DWORD64,
4237 LastExceptionToRip: DWORD64,
4238 LastExceptionFromRip: DWORD64,
4239
4240 pub fn getRegs(ctx: *const CONTEXT) struct { bp: usize, ip: usize, sp: usize } {
4241 return .{ .bp = ctx.Rbp, .ip = ctx.Rip, .sp = ctx.Rsp };
4242 }
4243
4244 pub fn setIp(ctx: *CONTEXT, ip: usize) void {
4245 ctx.Rip = ip;
4246 }
4247
4248 pub fn setSp(ctx: *CONTEXT, sp: usize) void {
4249 ctx.Rsp = sp;
4250 }
4251 },
4252 .thumb => extern struct {
4253 ContextFlags: ULONG,
4254 R0: ULONG,
4255 R1: ULONG,
4256 R2: ULONG,
4257 R3: ULONG,
4258 R4: ULONG,
4259 R5: ULONG,
4260 R6: ULONG,
4261 R7: ULONG,
4262 R8: ULONG,
4263 R9: ULONG,
4264 R10: ULONG,
4265 R11: ULONG,
4266 R12: ULONG,
4267 Sp: ULONG,
4268 Lr: ULONG,
4269 Pc: ULONG,
4270 Cpsr: ULONG,
4271 Fpcsr: ULONG,
4272 Padding: ULONG,
4273 DUMMYUNIONNAME: extern union {
4274 Q: [16]NEON128,
4275 D: [32]ULONGLONG,
4276 S: [32]ULONG,
4277 },
4278 Bvr: [8]ULONG,
4279 Bcr: [8]ULONG,
4280 Wvr: [1]ULONG,
4281 Wcr: [1]ULONG,
4282 Padding2: [2]ULONG,
4283
4284 pub fn getRegs(ctx: *const CONTEXT) struct { bp: usize, ip: usize, sp: usize } {
4285 return .{
4286 .bp = ctx.DUMMYUNIONNAME.S[11],
4287 .ip = ctx.Pc,
4288 .sp = ctx.Sp,
4289 };
4290 }
4291
4292 pub fn setIp(ctx: *CONTEXT, ip: usize) void {
4293 ctx.Pc = ip;
4294 }
4295
4296 pub fn setSp(ctx: *CONTEXT, sp: usize) void {
4297 ctx.Sp = sp;
4298 }
4299 },
4300 .aarch64 => extern struct {
4301 ContextFlags: ULONG align(16),
4302 Cpsr: ULONG,
4303 DUMMYUNIONNAME: extern union {
4304 DUMMYSTRUCTNAME: extern struct {
4305 X0: DWORD64,
4306 X1: DWORD64,
4307 X2: DWORD64,
4308 X3: DWORD64,
4309 X4: DWORD64,
4310 X5: DWORD64,
4311 X6: DWORD64,
4312 X7: DWORD64,
4313 X8: DWORD64,
4314 X9: DWORD64,
4315 X10: DWORD64,
4316 X11: DWORD64,
4317 X12: DWORD64,
4318 X13: DWORD64,
4319 X14: DWORD64,
4320 X15: DWORD64,
4321 X16: DWORD64,
4322 X17: DWORD64,
4323 X18: DWORD64,
4324 X19: DWORD64,
4325 X20: DWORD64,
4326 X21: DWORD64,
4327 X22: DWORD64,
4328 X23: DWORD64,
4329 X24: DWORD64,
4330 X25: DWORD64,
4331 X26: DWORD64,
4332 X27: DWORD64,
4333 X28: DWORD64,
4334 Fp: DWORD64,
4335 Lr: DWORD64,
4336 },
4337 X: [31]DWORD64,
4338 },
4339 Sp: DWORD64,
4340 Pc: DWORD64,
4341 V: [32]NEON128,
4342 Fpcr: DWORD,
4343 Fpsr: DWORD,
4344 Bcr: [8]DWORD,
4345 Bvr: [8]DWORD64,
4346 Wcr: [2]DWORD,
4347 Wvr: [2]DWORD64,
4348
4349 pub fn getRegs(ctx: *const CONTEXT) struct { bp: usize, ip: usize, sp: usize } {
4350 return .{
4351 .bp = ctx.DUMMYUNIONNAME.DUMMYSTRUCTNAME.Fp,
4352 .ip = ctx.Pc,
4353 .sp = ctx.Sp,
4354 };
4355 }
4356
4357 pub fn setIp(ctx: *CONTEXT, ip: usize) void {
4358 ctx.Pc = ip;
4359 }
4360
4361 pub fn setSp(ctx: *CONTEXT, sp: usize) void {
4362 ctx.Sp = sp;
4363 }
4364 },
4365 else => @compileError("CONTEXT is not defined for this architecture"),
4366};
4367
4368pub const RUNTIME_FUNCTION = switch (native_arch) {
4369 .x86_64 => extern struct {
4370 BeginAddress: DWORD,
4371 EndAddress: DWORD,
4372 UnwindData: DWORD,
4373 },
4374 .thumb => extern struct {
4375 BeginAddress: DWORD,
4376 DUMMYUNIONNAME: extern union {
4377 UnwindData: DWORD,
4378 DUMMYSTRUCTNAME: packed struct {
4379 Flag: u2,
4380 FunctionLength: u11,
4381 Ret: u2,
4382 H: u1,
4383 Reg: u3,
4384 R: u1,
4385 L: u1,
4386 C: u1,
4387 StackAdjust: u10,
4388 },
4389 },
4390 },
4391 .aarch64 => extern struct {
4392 BeginAddress: DWORD,
4393 DUMMYUNIONNAME: extern union {
4394 UnwindData: DWORD,
4395 DUMMYSTRUCTNAME: packed struct {
4396 Flag: u2,
4397 FunctionLength: u11,
4398 RegF: u3,
4399 RegI: u4,
4400 H: u1,
4401 CR: u2,
4402 FrameSize: u9,
4403 },
4404 },
4405 },
4406 else => @compileError("RUNTIME_FUNCTION is not defined for this architecture"),
4407};
4408
4409pub const KNONVOLATILE_CONTEXT_POINTERS = switch (native_arch) {
4410 .x86_64 => extern struct {
4411 FloatingContext: [16]?*M128A,
4412 IntegerContext: [16]?*ULONG64,
4413 },
4414 .thumb => extern struct {
4415 R4: ?*DWORD,
4416 R5: ?*DWORD,
4417 R6: ?*DWORD,
4418 R7: ?*DWORD,
4419 R8: ?*DWORD,
4420 R9: ?*DWORD,
4421 R10: ?*DWORD,
4422 R11: ?*DWORD,
4423 Lr: ?*DWORD,
4424 D8: ?*ULONGLONG,
4425 D9: ?*ULONGLONG,
4426 D10: ?*ULONGLONG,
4427 D11: ?*ULONGLONG,
4428 D12: ?*ULONGLONG,
4429 D13: ?*ULONGLONG,
4430 D14: ?*ULONGLONG,
4431 D15: ?*ULONGLONG,
4432 },
4433 .aarch64 => extern struct {
4434 X19: ?*DWORD64,
4435 X20: ?*DWORD64,
4436 X21: ?*DWORD64,
4437 X22: ?*DWORD64,
4438 X23: ?*DWORD64,
4439 X24: ?*DWORD64,
4440 X25: ?*DWORD64,
4441 X26: ?*DWORD64,
4442 X27: ?*DWORD64,
4443 X28: ?*DWORD64,
4444 Fp: ?*DWORD64,
4445 Lr: ?*DWORD64,
4446 D8: ?*DWORD64,
4447 D9: ?*DWORD64,
4448 D10: ?*DWORD64,
4449 D11: ?*DWORD64,
4450 D12: ?*DWORD64,
4451 D13: ?*DWORD64,
4452 D14: ?*DWORD64,
4453 D15: ?*DWORD64,
4454 },
4455 else => @compileError("KNONVOLATILE_CONTEXT_POINTERS is not defined for this architecture"),
4456};
4457
4458pub const EXCEPTION_POINTERS = extern struct {
4459 ExceptionRecord: *EXCEPTION_RECORD,
4460 ContextRecord: *CONTEXT,
4461};
4462
4463pub const VECTORED_EXCEPTION_HANDLER = *const fn (ExceptionInfo: *EXCEPTION_POINTERS) callconv(.winapi) c_long;
4464
4465pub const EXCEPTION_DISPOSITION = i32;
4466pub const EXCEPTION_ROUTINE = *const fn (
4467 ExceptionRecord: ?*EXCEPTION_RECORD,
4468 EstablisherFrame: PVOID,
4469 ContextRecord: *(Self.CONTEXT),
4470 DispatcherContext: PVOID,
4471) callconv(.winapi) EXCEPTION_DISPOSITION;
4472
4473pub const UNWIND_HISTORY_TABLE_SIZE = 12;
4474pub const UNWIND_HISTORY_TABLE_ENTRY = extern struct {
4475 ImageBase: ULONG64,
4476 FunctionEntry: *Self.RUNTIME_FUNCTION,
4477};
4478
4479pub const UNWIND_HISTORY_TABLE = extern struct {
4480 Count: ULONG,
4481 LocalHint: BYTE,
4482 GlobalHint: BYTE,
4483 Search: BYTE,
4484 Once: BYTE,
4485 LowAddress: ULONG64,
4486 HighAddress: ULONG64,
4487 Entry: [UNWIND_HISTORY_TABLE_SIZE]UNWIND_HISTORY_TABLE_ENTRY,
4488};
4489
4490pub const UNW_FLAG_NHANDLER = 0x0;
4491pub const UNW_FLAG_EHANDLER = 0x1;
4492pub const UNW_FLAG_UHANDLER = 0x2;
4493pub const UNW_FLAG_CHAININFO = 0x4;
4494
4495pub const OBJECT_ATTRIBUTES = extern struct {
4496 Length: ULONG,
4497 RootDirectory: ?HANDLE,
4498 ObjectName: *UNICODE_STRING,
4499 Attributes: ULONG,
4500 SecurityDescriptor: ?*anyopaque,
4501 SecurityQualityOfService: ?*anyopaque,
4502};
4503
4504pub const OBJ_INHERIT = 0x00000002;
4505pub const OBJ_PERMANENT = 0x00000010;
4506pub const OBJ_EXCLUSIVE = 0x00000020;
4507pub const OBJ_CASE_INSENSITIVE = 0x00000040;
4508pub const OBJ_OPENIF = 0x00000080;
4509pub const OBJ_OPENLINK = 0x00000100;
4510pub const OBJ_KERNEL_HANDLE = 0x00000200;
4511pub const OBJ_VALID_ATTRIBUTES = 0x000003F2;
4512
4513pub const UNICODE_STRING = extern struct {
4514 Length: c_ushort,
4515 MaximumLength: c_ushort,
4516 Buffer: ?[*]WCHAR,
4517};
4518
4519pub const ACTIVATION_CONTEXT_DATA = opaque {};
4520pub const ASSEMBLY_STORAGE_MAP = opaque {};
4521pub const FLS_CALLBACK_INFO = opaque {};
4522pub const RTL_BITMAP = opaque {};
4523pub const KAFFINITY = usize;
4524pub const KPRIORITY = i32;
4525
4526pub const CLIENT_ID = extern struct {
4527 UniqueProcess: HANDLE,
4528 UniqueThread: HANDLE,
4529};
4530
4531pub const THREAD_BASIC_INFORMATION = extern struct {
4532 ExitStatus: NTSTATUS,
4533 TebBaseAddress: PVOID,
4534 ClientId: CLIENT_ID,
4535 AffinityMask: KAFFINITY,
4536 Priority: KPRIORITY,
4537 BasePriority: KPRIORITY,
4538};
4539
4540pub const TEB = extern struct {
4541 NtTib: NT_TIB,
4542 EnvironmentPointer: PVOID,
4543 ClientId: CLIENT_ID,
4544 ActiveRpcHandle: PVOID,
4545 ThreadLocalStoragePointer: PVOID,
4546 ProcessEnvironmentBlock: *PEB,
4547 LastErrorValue: ULONG,
4548 Reserved2: [399 * @sizeOf(PVOID) - @sizeOf(ULONG)]u8,
4549 Reserved3: [1952]u8,
4550 TlsSlots: [64]PVOID,
4551 Reserved4: [8]u8,
4552 Reserved5: [26]PVOID,
4553 ReservedForOle: PVOID,
4554 Reserved6: [4]PVOID,
4555 TlsExpansionSlots: PVOID,
4556};
4557
4558comptime {
4559 // XXX: Without this check we cannot use `std.Io.Writer` on 16-bit platforms. `std.fmt.bufPrint` will hit the unreachable in `PEB.GdiHandleBuffer` without this guard.
4560 if (builtin.os.tag == .windows) {
4561 // Offsets taken from WinDbg info and Geoff Chappell[1] (RIP)
4562 // [1]: https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/teb/index.htm
4563 assert(@offsetOf(TEB, "NtTib") == 0x00);
4564 if (@sizeOf(usize) == 4) {
4565 assert(@offsetOf(TEB, "EnvironmentPointer") == 0x1C);
4566 assert(@offsetOf(TEB, "ClientId") == 0x20);
4567 assert(@offsetOf(TEB, "ActiveRpcHandle") == 0x28);
4568 assert(@offsetOf(TEB, "ThreadLocalStoragePointer") == 0x2C);
4569 assert(@offsetOf(TEB, "ProcessEnvironmentBlock") == 0x30);
4570 assert(@offsetOf(TEB, "LastErrorValue") == 0x34);
4571 assert(@offsetOf(TEB, "TlsSlots") == 0xe10);
4572 } else if (@sizeOf(usize) == 8) {
4573 assert(@offsetOf(TEB, "EnvironmentPointer") == 0x38);
4574 assert(@offsetOf(TEB, "ClientId") == 0x40);
4575 assert(@offsetOf(TEB, "ActiveRpcHandle") == 0x50);
4576 assert(@offsetOf(TEB, "ThreadLocalStoragePointer") == 0x58);
4577 assert(@offsetOf(TEB, "ProcessEnvironmentBlock") == 0x60);
4578 assert(@offsetOf(TEB, "LastErrorValue") == 0x68);
4579 assert(@offsetOf(TEB, "TlsSlots") == 0x1480);
4580 }
4581 }
4582}
4583
4584pub const EXCEPTION_REGISTRATION_RECORD = extern struct {
4585 Next: ?*EXCEPTION_REGISTRATION_RECORD,
4586 Handler: ?*EXCEPTION_DISPOSITION,
4587};
4588
4589pub const NT_TIB = extern struct {
4590 ExceptionList: ?*EXCEPTION_REGISTRATION_RECORD,
4591 StackBase: PVOID,
4592 StackLimit: PVOID,
4593 SubSystemTib: PVOID,
4594 DUMMYUNIONNAME: extern union { FiberData: PVOID, Version: DWORD },
4595 ArbitraryUserPointer: PVOID,
4596 Self: ?*@This(),
4597};
4598
4599/// Process Environment Block
4600/// Microsoft documentation of this is incomplete, the fields here are taken from various resources including:
4601/// - https://github.com/wine-mirror/wine/blob/1aff1e6a370ee8c0213a0fd4b220d121da8527aa/include/winternl.h#L269
4602/// - https://www.geoffchappell.com/studies/windows/win32/ntdll/structs/peb/index.htm
4603pub const PEB = extern struct {
4604 // Versions: All
4605 InheritedAddressSpace: BOOLEAN,
4606
4607 // Versions: 3.51+
4608 ReadImageFileExecOptions: BOOLEAN,
4609 BeingDebugged: BOOLEAN,
4610
4611 // Versions: 5.2+ (previously was padding)
4612 BitField: UCHAR,
4613
4614 // Versions: all
4615 Mutant: HANDLE,
4616 ImageBaseAddress: HMODULE,
4617 Ldr: *PEB_LDR_DATA,
4618 ProcessParameters: *RTL_USER_PROCESS_PARAMETERS,
4619 SubSystemData: PVOID,
4620 ProcessHeap: HANDLE,
4621
4622 // Versions: 5.1+
4623 FastPebLock: *RTL_CRITICAL_SECTION,
4624
4625 // Versions: 5.2+
4626 AtlThunkSListPtr: PVOID,
4627 IFEOKey: PVOID,
4628
4629 // Versions: 6.0+
4630
4631 /// https://www.geoffchappell.com/studies/windows/win32/ntdll/structs/peb/crossprocessflags.htm
4632 CrossProcessFlags: ULONG,
4633
4634 // Versions: 6.0+
4635 union1: extern union {
4636 KernelCallbackTable: PVOID,
4637 UserSharedInfoPtr: PVOID,
4638 },
4639
4640 // Versions: 5.1+
4641 SystemReserved: ULONG,
4642
4643 // Versions: 5.1, (not 5.2, not 6.0), 6.1+
4644 AtlThunkSListPtr32: ULONG,
4645
4646 // Versions: 6.1+
4647 ApiSetMap: PVOID,
4648
4649 // Versions: all
4650 TlsExpansionCounter: ULONG,
4651 // note: there is padding here on 64 bit
4652 TlsBitmap: *RTL_BITMAP,
4653 TlsBitmapBits: [2]ULONG,
4654 ReadOnlySharedMemoryBase: PVOID,
4655
4656 // Versions: 1703+
4657 SharedData: PVOID,
4658
4659 // Versions: all
4660 ReadOnlyStaticServerData: *PVOID,
4661 AnsiCodePageData: PVOID,
4662 OemCodePageData: PVOID,
4663 UnicodeCaseTableData: PVOID,
4664
4665 // Versions: 3.51+
4666 NumberOfProcessors: ULONG,
4667 NtGlobalFlag: ULONG,
4668
4669 // Versions: all
4670 CriticalSectionTimeout: LARGE_INTEGER,
4671
4672 // End of Original PEB size
4673
4674 // Fields appended in 3.51:
4675 HeapSegmentReserve: ULONG_PTR,
4676 HeapSegmentCommit: ULONG_PTR,
4677 HeapDeCommitTotalFreeThreshold: ULONG_PTR,
4678 HeapDeCommitFreeBlockThreshold: ULONG_PTR,
4679 NumberOfHeaps: ULONG,
4680 MaximumNumberOfHeaps: ULONG,
4681 ProcessHeaps: *PVOID,
4682
4683 // Fields appended in 4.0:
4684 GdiSharedHandleTable: PVOID,
4685 ProcessStarterHelper: PVOID,
4686 GdiDCAttributeList: ULONG,
4687 // note: there is padding here on 64 bit
4688 LoaderLock: *RTL_CRITICAL_SECTION,
4689 OSMajorVersion: ULONG,
4690 OSMinorVersion: ULONG,
4691 OSBuildNumber: USHORT,
4692 OSCSDVersion: USHORT,
4693 OSPlatformId: ULONG,
4694 ImageSubSystem: ULONG,
4695 ImageSubSystemMajorVersion: ULONG,
4696 ImageSubSystemMinorVersion: ULONG,
4697 // note: there is padding here on 64 bit
4698 ActiveProcessAffinityMask: KAFFINITY,
4699 GdiHandleBuffer: [
4700 switch (@sizeOf(usize)) {
4701 4 => 0x22,
4702 8 => 0x3C,
4703 else => unreachable,
4704 }
4705 ]ULONG,
4706
4707 // Fields appended in 5.0 (Windows 2000):
4708 PostProcessInitRoutine: PVOID,
4709 TlsExpansionBitmap: *RTL_BITMAP,
4710 TlsExpansionBitmapBits: [32]ULONG,
4711 SessionId: ULONG,
4712 // note: there is padding here on 64 bit
4713 // Versions: 5.1+
4714 AppCompatFlags: ULARGE_INTEGER,
4715 AppCompatFlagsUser: ULARGE_INTEGER,
4716 ShimData: PVOID,
4717 // Versions: 5.0+
4718 AppCompatInfo: PVOID,
4719 CSDVersion: UNICODE_STRING,
4720
4721 // Fields appended in 5.1 (Windows XP):
4722 ActivationContextData: *const ACTIVATION_CONTEXT_DATA,
4723 ProcessAssemblyStorageMap: *ASSEMBLY_STORAGE_MAP,
4724 SystemDefaultActivationData: *const ACTIVATION_CONTEXT_DATA,
4725 SystemAssemblyStorageMap: *ASSEMBLY_STORAGE_MAP,
4726 MinimumStackCommit: ULONG_PTR,
4727
4728 // Fields appended in 5.2 (Windows Server 2003):
4729 FlsCallback: *FLS_CALLBACK_INFO,
4730 FlsListHead: LIST_ENTRY,
4731 FlsBitmap: *RTL_BITMAP,
4732 FlsBitmapBits: [4]ULONG,
4733 FlsHighIndex: ULONG,
4734
4735 // Fields appended in 6.0 (Windows Vista):
4736 WerRegistrationData: PVOID,
4737 WerShipAssertPtr: PVOID,
4738
4739 // Fields appended in 6.1 (Windows 7):
4740 pUnused: PVOID, // previously pContextData
4741 pImageHeaderHash: PVOID,
4742
4743 /// TODO: https://www.geoffchappell.com/studies/windows/win32/ntdll/structs/peb/tracingflags.htm
4744 TracingFlags: ULONG,
4745
4746 // Fields appended in 6.2 (Windows 8):
4747 CsrServerReadOnlySharedMemoryBase: ULONGLONG,
4748
4749 // Fields appended in 1511:
4750 TppWorkerpListLock: ULONG,
4751 TppWorkerpList: LIST_ENTRY,
4752 WaitOnAddressHashTable: [0x80]PVOID,
4753
4754 // Fields appended in 1709:
4755 TelemetryCoverageHeader: PVOID,
4756 CloudFileFlags: ULONG,
4757};
4758
4759/// The `PEB_LDR_DATA` structure is the main record of what modules are loaded in a process.
4760/// It is essentially the head of three double-linked lists of `LDR_DATA_TABLE_ENTRY` structures which each represent one loaded module.
4761///
4762/// Microsoft documentation of this is incomplete, the fields here are taken from various resources including:
4763/// - https://www.geoffchappell.com/studies/windows/win32/ntdll/structs/peb_ldr_data.htm
4764pub const PEB_LDR_DATA = extern struct {
4765 // Versions: 3.51 and higher
4766 /// The size in bytes of the structure
4767 Length: ULONG,
4768
4769 /// TRUE if the structure is prepared.
4770 Initialized: BOOLEAN,
4771
4772 SsHandle: PVOID,
4773 InLoadOrderModuleList: LIST_ENTRY,
4774 InMemoryOrderModuleList: LIST_ENTRY,
4775 InInitializationOrderModuleList: LIST_ENTRY,
4776
4777 // Versions: 5.1 and higher
4778
4779 /// No known use of this field is known in Windows 8 and higher.
4780 EntryInProgress: PVOID,
4781
4782 // Versions: 6.0 from Windows Vista SP1, and higher
4783 ShutdownInProgress: BOOLEAN,
4784
4785 /// Though ShutdownThreadId is declared as a HANDLE,
4786 /// it is indeed the thread ID as suggested by its name.
4787 /// It is picked up from the UniqueThread member of the CLIENT_ID in the
4788 /// TEB of the thread that asks to terminate the process.
4789 ShutdownThreadId: HANDLE,
4790};
4791
4792/// Microsoft documentation of this is incomplete, the fields here are taken from various resources including:
4793/// - https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb_ldr_data
4794/// - https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntldr/ldr_data_table_entry.htm
4795pub const LDR_DATA_TABLE_ENTRY = extern struct {
4796 InLoadOrderLinks: LIST_ENTRY,
4797 InMemoryOrderLinks: LIST_ENTRY,
4798 InInitializationOrderLinks: LIST_ENTRY,
4799 DllBase: PVOID,
4800 EntryPoint: PVOID,
4801 SizeOfImage: ULONG,
4802 FullDllName: UNICODE_STRING,
4803 BaseDllName: UNICODE_STRING,
4804 Reserved5: [3]PVOID,
4805 DUMMYUNIONNAME: extern union {
4806 CheckSum: ULONG,
4807 Reserved6: PVOID,
4808 },
4809 TimeDateStamp: ULONG,
4810};
4811
4812pub const RTL_USER_PROCESS_PARAMETERS = extern struct {
4813 AllocationSize: ULONG,
4814 Size: ULONG,
4815 Flags: ULONG,
4816 DebugFlags: ULONG,
4817 ConsoleHandle: HANDLE,
4818 ConsoleFlags: ULONG,
4819 hStdInput: HANDLE,
4820 hStdOutput: HANDLE,
4821 hStdError: HANDLE,
4822 CurrentDirectory: CURDIR,
4823 DllPath: UNICODE_STRING,
4824 ImagePathName: UNICODE_STRING,
4825 CommandLine: UNICODE_STRING,
4826 /// Points to a NUL-terminated sequence of NUL-terminated
4827 /// WTF-16 LE encoded `name=value` sequences.
4828 /// Example using string literal syntax:
4829 /// `"NAME=value\x00foo=bar\x00\x00"`
4830 Environment: [*:0]WCHAR,
4831 dwX: ULONG,
4832 dwY: ULONG,
4833 dwXSize: ULONG,
4834 dwYSize: ULONG,
4835 dwXCountChars: ULONG,
4836 dwYCountChars: ULONG,
4837 dwFillAttribute: ULONG,
4838 dwFlags: ULONG,
4839 dwShowWindow: ULONG,
4840 WindowTitle: UNICODE_STRING,
4841 Desktop: UNICODE_STRING,
4842 ShellInfo: UNICODE_STRING,
4843 RuntimeInfo: UNICODE_STRING,
4844 DLCurrentDirectory: [0x20]RTL_DRIVE_LETTER_CURDIR,
4845};
4846
4847pub const RTL_DRIVE_LETTER_CURDIR = extern struct {
4848 Flags: c_ushort,
4849 Length: c_ushort,
4850 TimeStamp: ULONG,
4851 DosPath: UNICODE_STRING,
4852};
4853
4854pub const PPS_POST_PROCESS_INIT_ROUTINE = ?*const fn () callconv(.winapi) void;
4855
4856pub const FILE_DIRECTORY_INFORMATION = extern struct {
4857 NextEntryOffset: ULONG,
4858 FileIndex: ULONG,
4859 CreationTime: LARGE_INTEGER,
4860 LastAccessTime: LARGE_INTEGER,
4861 LastWriteTime: LARGE_INTEGER,
4862 ChangeTime: LARGE_INTEGER,
4863 EndOfFile: LARGE_INTEGER,
4864 AllocationSize: LARGE_INTEGER,
4865 FileAttributes: ULONG,
4866 FileNameLength: ULONG,
4867 FileName: [1]WCHAR,
4868};
4869
4870pub const FILE_BOTH_DIR_INFORMATION = extern struct {
4871 NextEntryOffset: ULONG,
4872 FileIndex: ULONG,
4873 CreationTime: LARGE_INTEGER,
4874 LastAccessTime: LARGE_INTEGER,
4875 LastWriteTime: LARGE_INTEGER,
4876 ChangeTime: LARGE_INTEGER,
4877 EndOfFile: LARGE_INTEGER,
4878 AllocationSize: LARGE_INTEGER,
4879 FileAttributes: ULONG,
4880 FileNameLength: ULONG,
4881 EaSize: ULONG,
4882 ShortNameLength: CHAR,
4883 ShortName: [12]WCHAR,
4884 FileName: [1]WCHAR,
4885};
4886pub const FILE_BOTH_DIRECTORY_INFORMATION = FILE_BOTH_DIR_INFORMATION;
4887
4888/// Helper for iterating a byte buffer of FILE_*_INFORMATION structures (from
4889/// things like NtQueryDirectoryFile calls).
4890pub fn FileInformationIterator(comptime FileInformationType: type) type {
4891 return struct {
4892 byte_offset: usize = 0,
4893 buf: []u8 align(@alignOf(FileInformationType)),
4894
4895 pub fn next(self: *@This()) ?*FileInformationType {
4896 if (self.byte_offset >= self.buf.len) return null;
4897 const cur: *FileInformationType = @ptrCast(@alignCast(&self.buf[self.byte_offset]));
4898 if (cur.NextEntryOffset == 0) {
4899 self.byte_offset = self.buf.len;
4900 } else {
4901 self.byte_offset += cur.NextEntryOffset;
4902 }
4903 return cur;
4904 }
4905 };
4906}
4907
4908pub const IO_APC_ROUTINE = *const fn (PVOID, *IO_STATUS_BLOCK, ULONG) callconv(.winapi) void;
4909
4910pub const CURDIR = extern struct {
4911 DosPath: UNICODE_STRING,
4912 Handle: HANDLE,
4913};
4914
4915pub const DUPLICATE_SAME_ACCESS = 2;
4916
4917pub const MODULEINFO = extern struct {
4918 lpBaseOfDll: LPVOID,
4919 SizeOfImage: DWORD,
4920 EntryPoint: LPVOID,
4921};
4922
4923pub const PSAPI_WS_WATCH_INFORMATION = extern struct {
4924 FaultingPc: LPVOID,
4925 FaultingVa: LPVOID,
4926};
4927
4928pub const VM_COUNTERS = extern struct {
4929 PeakVirtualSize: SIZE_T,
4930 VirtualSize: SIZE_T,
4931 PageFaultCount: ULONG,
4932 PeakWorkingSetSize: SIZE_T,
4933 WorkingSetSize: SIZE_T,
4934 QuotaPeakPagedPoolUsage: SIZE_T,
4935 QuotaPagedPoolUsage: SIZE_T,
4936 QuotaPeakNonPagedPoolUsage: SIZE_T,
4937 QuotaNonPagedPoolUsage: SIZE_T,
4938 PagefileUsage: SIZE_T,
4939 PeakPagefileUsage: SIZE_T,
4940};
4941
4942pub const PROCESS_MEMORY_COUNTERS = extern struct {
4943 cb: DWORD,
4944 PageFaultCount: DWORD,
4945 PeakWorkingSetSize: SIZE_T,
4946 WorkingSetSize: SIZE_T,
4947 QuotaPeakPagedPoolUsage: SIZE_T,
4948 QuotaPagedPoolUsage: SIZE_T,
4949 QuotaPeakNonPagedPoolUsage: SIZE_T,
4950 QuotaNonPagedPoolUsage: SIZE_T,
4951 PagefileUsage: SIZE_T,
4952 PeakPagefileUsage: SIZE_T,
4953};
4954
4955pub const PROCESS_MEMORY_COUNTERS_EX = extern struct {
4956 cb: DWORD,
4957 PageFaultCount: DWORD,
4958 PeakWorkingSetSize: SIZE_T,
4959 WorkingSetSize: SIZE_T,
4960 QuotaPeakPagedPoolUsage: SIZE_T,
4961 QuotaPagedPoolUsage: SIZE_T,
4962 QuotaPeakNonPagedPoolUsage: SIZE_T,
4963 QuotaNonPagedPoolUsage: SIZE_T,
4964 PagefileUsage: SIZE_T,
4965 PeakPagefileUsage: SIZE_T,
4966 PrivateUsage: SIZE_T,
4967};
4968
4969pub const GetProcessMemoryInfoError = error{
4970 AccessDenied,
4971 InvalidHandle,
4972 Unexpected,
4973};
4974
4975pub fn GetProcessMemoryInfo(hProcess: HANDLE) GetProcessMemoryInfoError!VM_COUNTERS {
4976 var vmc: VM_COUNTERS = undefined;
4977 const rc = ntdll.NtQueryInformationProcess(hProcess, .ProcessVmCounters, &vmc, @sizeOf(VM_COUNTERS), null);
4978 switch (rc) {
4979 .SUCCESS => return vmc,
4980 .ACCESS_DENIED => return error.AccessDenied,
4981 .INVALID_HANDLE => return error.InvalidHandle,
4982 .INVALID_PARAMETER => unreachable,
4983 else => return unexpectedStatus(rc),
4984 }
4985}
4986
4987pub const PERFORMANCE_INFORMATION = extern struct {
4988 cb: DWORD,
4989 CommitTotal: SIZE_T,
4990 CommitLimit: SIZE_T,
4991 CommitPeak: SIZE_T,
4992 PhysicalTotal: SIZE_T,
4993 PhysicalAvailable: SIZE_T,
4994 SystemCache: SIZE_T,
4995 KernelTotal: SIZE_T,
4996 KernelPaged: SIZE_T,
4997 KernelNonpaged: SIZE_T,
4998 PageSize: SIZE_T,
4999 HandleCount: DWORD,
5000 ProcessCount: DWORD,
5001 ThreadCount: DWORD,
5002};
5003
5004pub const ENUM_PAGE_FILE_INFORMATION = extern struct {
5005 cb: DWORD,
5006 Reserved: DWORD,
5007 TotalSize: SIZE_T,
5008 TotalInUse: SIZE_T,
5009 PeakUsage: SIZE_T,
5010};
5011
5012pub const PENUM_PAGE_FILE_CALLBACKW = ?*const fn (?LPVOID, *ENUM_PAGE_FILE_INFORMATION, LPCWSTR) callconv(.winapi) BOOL;
5013pub const PENUM_PAGE_FILE_CALLBACKA = ?*const fn (?LPVOID, *ENUM_PAGE_FILE_INFORMATION, LPCSTR) callconv(.winapi) BOOL;
5014
5015pub const PSAPI_WS_WATCH_INFORMATION_EX = extern struct {
5016 BasicInfo: PSAPI_WS_WATCH_INFORMATION,
5017 FaultingThreadId: ULONG_PTR,
5018 Flags: ULONG_PTR,
5019};
5020
5021pub const OSVERSIONINFOW = extern struct {
5022 dwOSVersionInfoSize: ULONG,
5023 dwMajorVersion: ULONG,
5024 dwMinorVersion: ULONG,
5025 dwBuildNumber: ULONG,
5026 dwPlatformId: ULONG,
5027 szCSDVersion: [128]WCHAR,
5028};
5029pub const RTL_OSVERSIONINFOW = OSVERSIONINFOW;
5030
5031pub const REPARSE_DATA_BUFFER = extern struct {
5032 ReparseTag: ULONG,
5033 ReparseDataLength: USHORT,
5034 Reserved: USHORT,
5035 DataBuffer: [1]UCHAR,
5036};
5037pub const SYMBOLIC_LINK_REPARSE_BUFFER = extern struct {
5038 SubstituteNameOffset: USHORT,
5039 SubstituteNameLength: USHORT,
5040 PrintNameOffset: USHORT,
5041 PrintNameLength: USHORT,
5042 Flags: ULONG,
5043 PathBuffer: [1]WCHAR,
5044};
5045pub const MOUNT_POINT_REPARSE_BUFFER = extern struct {
5046 SubstituteNameOffset: USHORT,
5047 SubstituteNameLength: USHORT,
5048 PrintNameOffset: USHORT,
5049 PrintNameLength: USHORT,
5050 PathBuffer: [1]WCHAR,
5051};
5052pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: ULONG = 16 * 1024;
5053pub const FSCTL_SET_REPARSE_POINT: DWORD = 0x900a4;
5054pub const FSCTL_GET_REPARSE_POINT: DWORD = 0x900a8;
5055pub const IO_REPARSE_TAG_SYMLINK: ULONG = 0xa000000c;
5056pub const IO_REPARSE_TAG_MOUNT_POINT: ULONG = 0xa0000003;
5057pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x1;
5058
5059pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1;
5060pub const SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE: DWORD = 0x2;
5061
5062pub const MOUNTMGRCONTROLTYPE = 0x0000006D;
5063
5064pub const MOUNTMGR_MOUNT_POINT = extern struct {
5065 SymbolicLinkNameOffset: ULONG,
5066 SymbolicLinkNameLength: USHORT,
5067 Reserved1: USHORT,
5068 UniqueIdOffset: ULONG,
5069 UniqueIdLength: USHORT,
5070 Reserved2: USHORT,
5071 DeviceNameOffset: ULONG,
5072 DeviceNameLength: USHORT,
5073 Reserved3: USHORT,
5074};
5075pub const MOUNTMGR_MOUNT_POINTS = extern struct {
5076 Size: ULONG,
5077 NumberOfMountPoints: ULONG,
5078 MountPoints: [1]MOUNTMGR_MOUNT_POINT,
5079};
5080pub const IOCTL_MOUNTMGR_QUERY_POINTS = CTL_CODE(MOUNTMGRCONTROLTYPE, 2, .METHOD_BUFFERED, FILE_ANY_ACCESS);
5081
5082pub const MOUNTMGR_TARGET_NAME = extern struct {
5083 DeviceNameLength: USHORT,
5084 DeviceName: [1]WCHAR,
5085};
5086pub const MOUNTMGR_VOLUME_PATHS = extern struct {
5087 MultiSzLength: ULONG,
5088 MultiSz: [1]WCHAR,
5089};
5090pub const IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH = CTL_CODE(MOUNTMGRCONTROLTYPE, 12, .METHOD_BUFFERED, FILE_ANY_ACCESS);
5091
5092pub const OBJECT_INFORMATION_CLASS = enum(c_int) {
5093 ObjectBasicInformation = 0,
5094 ObjectNameInformation = 1,
5095 ObjectTypeInformation = 2,
5096 ObjectTypesInformation = 3,
5097 ObjectHandleFlagInformation = 4,
5098 ObjectSessionInformation = 5,
5099 MaxObjectInfoClass,
5100};
5101
5102pub const OBJECT_NAME_INFORMATION = extern struct {
5103 Name: UNICODE_STRING,
5104};
5105
5106pub const SRWLOCK_INIT = SRWLOCK{};
5107pub const SRWLOCK = extern struct {
5108 Ptr: ?PVOID = null,
5109};
5110
5111pub const CONDITION_VARIABLE_INIT = CONDITION_VARIABLE{};
5112pub const CONDITION_VARIABLE = extern struct {
5113 Ptr: ?PVOID = null,
5114};
5115
5116pub const FILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 0x1;
5117pub const FILE_SKIP_SET_EVENT_ON_HANDLE = 0x2;
5118
5119pub const CTRL_C_EVENT: DWORD = 0;
5120pub const CTRL_BREAK_EVENT: DWORD = 1;
5121pub const CTRL_CLOSE_EVENT: DWORD = 2;
5122pub const CTRL_LOGOFF_EVENT: DWORD = 5;
5123pub const CTRL_SHUTDOWN_EVENT: DWORD = 6;
5124
5125pub const HANDLER_ROUTINE = *const fn (dwCtrlType: DWORD) callconv(.winapi) BOOL;
5126
5127/// Processor feature enumeration.
5128pub const PF = enum(DWORD) {
5129 /// On a Pentium, a floating-point precision error can occur in rare circumstances.
5130 FLOATING_POINT_PRECISION_ERRATA = 0,
5131
5132 /// Floating-point operations are emulated using software emulator.
5133 /// This function returns a nonzero value if floating-point operations are emulated; otherwise, it returns zero.
5134 FLOATING_POINT_EMULATED = 1,
5135
5136 /// The atomic compare and exchange operation (cmpxchg) is available.
5137 COMPARE_EXCHANGE_DOUBLE = 2,
5138
5139 /// The MMX instruction set is available.
5140 MMX_INSTRUCTIONS_AVAILABLE = 3,
5141
5142 PPC_MOVEMEM_64BIT_OK = 4,
5143 ALPHA_BYTE_INSTRUCTIONS = 5,
5144
5145 /// The SSE instruction set is available.
5146 XMMI_INSTRUCTIONS_AVAILABLE = 6,
5147
5148 /// The 3D-Now instruction is available.
5149 @"3DNOW_INSTRUCTIONS_AVAILABLE" = 7,
5150
5151 /// The RDTSC instruction is available.
5152 RDTSC_INSTRUCTION_AVAILABLE = 8,
5153
5154 /// The processor is PAE-enabled.
5155 PAE_ENABLED = 9,
5156
5157 /// The SSE2 instruction set is available.
5158 XMMI64_INSTRUCTIONS_AVAILABLE = 10,
5159
5160 SSE_DAZ_MODE_AVAILABLE = 11,
5161
5162 /// Data execution prevention is enabled.
5163 NX_ENABLED = 12,
5164
5165 /// The SSE3 instruction set is available.
5166 SSE3_INSTRUCTIONS_AVAILABLE = 13,
5167
5168 /// The atomic compare and exchange 128-bit operation (cmpxchg16b) is available.
5169 COMPARE_EXCHANGE128 = 14,
5170
5171 /// The atomic compare 64 and exchange 128-bit operation (cmp8xchg16) is available.
5172 COMPARE64_EXCHANGE128 = 15,
5173
5174 /// The processor channels are enabled.
5175 CHANNELS_ENABLED = 16,
5176
5177 /// The processor implements the XSAVI and XRSTOR instructions.
5178 XSAVE_ENABLED = 17,
5179
5180 /// The VFP/Neon: 32 x 64bit register bank is present.
5181 /// This flag has the same meaning as PF_ARM_VFP_EXTENDED_REGISTERS.
5182 ARM_VFP_32_REGISTERS_AVAILABLE = 18,
5183
5184 /// This ARM processor implements the ARM v8 NEON instruction set.
5185 ARM_NEON_INSTRUCTIONS_AVAILABLE = 19,
5186
5187 /// Second Level Address Translation is supported by the hardware.
5188 SECOND_LEVEL_ADDRESS_TRANSLATION = 20,
5189
5190 /// Virtualization is enabled in the firmware and made available by the operating system.
5191 VIRT_FIRMWARE_ENABLED = 21,
5192
5193 /// RDFSBASE, RDGSBASE, WRFSBASE, and WRGSBASE instructions are available.
5194 RDWRFSGBASE_AVAILABLE = 22,
5195
5196 /// _fastfail() is available.
5197 FASTFAIL_AVAILABLE = 23,
5198
5199 /// The divide instruction_available.
5200 ARM_DIVIDE_INSTRUCTION_AVAILABLE = 24,
5201
5202 /// The 64-bit load/store atomic instructions are available.
5203 ARM_64BIT_LOADSTORE_ATOMIC = 25,
5204
5205 /// The external cache is available.
5206 ARM_EXTERNAL_CACHE_AVAILABLE = 26,
5207
5208 /// The floating-point multiply-accumulate instruction is available.
5209 ARM_FMAC_INSTRUCTIONS_AVAILABLE = 27,
5210
5211 RDRAND_INSTRUCTION_AVAILABLE = 28,
5212
5213 /// This ARM processor implements the ARM v8 instructions set.
5214 ARM_V8_INSTRUCTIONS_AVAILABLE = 29,
5215
5216 /// This ARM processor implements the ARM v8 extra cryptographic instructions (i.e., AES, SHA1 and SHA2).
5217 ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE = 30,
5218
5219 /// This ARM processor implements the ARM v8 extra CRC32 instructions.
5220 ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE = 31,
5221
5222 RDTSCP_INSTRUCTION_AVAILABLE = 32,
5223 RDPID_INSTRUCTION_AVAILABLE = 33,
5224
5225 /// This ARM processor implements the ARM v8.1 atomic instructions (e.g., CAS, SWP).
5226 ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE = 34,
5227
5228 MONITORX_INSTRUCTION_AVAILABLE = 35,
5229
5230 /// The SSSE3 instruction set is available.
5231 SSSE3_INSTRUCTIONS_AVAILABLE = 36,
5232
5233 /// The SSE4_1 instruction set is available.
5234 SSE4_1_INSTRUCTIONS_AVAILABLE = 37,
5235
5236 /// The SSE4_2 instruction set is available.
5237 SSE4_2_INSTRUCTIONS_AVAILABLE = 38,
5238
5239 /// The AVX instruction set is available.
5240 AVX_INSTRUCTIONS_AVAILABLE = 39,
5241
5242 /// The AVX2 instruction set is available.
5243 AVX2_INSTRUCTIONS_AVAILABLE = 40,
5244
5245 /// The AVX512F instruction set is available.
5246 AVX512F_INSTRUCTIONS_AVAILABLE = 41,
5247
5248 ERMS_AVAILABLE = 42,
5249
5250 /// This ARM processor implements the ARM v8.2 Dot Product (DP) instructions.
5251 ARM_V82_DP_INSTRUCTIONS_AVAILABLE = 43,
5252
5253 /// This ARM processor implements the ARM v8.3 JavaScript conversion (JSCVT) instructions.
5254 ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE = 44,
5255
5256 /// This Arm processor implements the Arm v8.3 LRCPC instructions (for example, LDAPR). Note that certain Arm v8.2 CPUs may optionally support the LRCPC instructions.
5257 ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE,
5258};
5259
5260pub const MAX_WOW64_SHARED_ENTRIES = 16;
5261pub const PROCESSOR_FEATURE_MAX = 64;
5262pub const MAXIMUM_XSTATE_FEATURES = 64;
5263
5264pub const KSYSTEM_TIME = extern struct {
5265 LowPart: ULONG,
5266 High1Time: LONG,
5267 High2Time: LONG,
5268};
5269
5270pub const NT_PRODUCT_TYPE = enum(INT) {
5271 NtProductWinNt = 1,
5272 NtProductLanManNt,
5273 NtProductServer,
5274};
5275
5276pub const ALTERNATIVE_ARCHITECTURE_TYPE = enum(INT) {
5277 StandardDesign,
5278 NEC98x86,
5279 EndAlternatives,
5280};
5281
5282pub const XSTATE_FEATURE = extern struct {
5283 Offset: ULONG,
5284 Size: ULONG,
5285};
5286
5287pub const XSTATE_CONFIGURATION = extern struct {
5288 EnabledFeatures: ULONG64,
5289 Size: ULONG,
5290 OptimizedSave: ULONG,
5291 Features: [MAXIMUM_XSTATE_FEATURES]XSTATE_FEATURE,
5292};
5293
5294/// Shared Kernel User Data
5295pub const KUSER_SHARED_DATA = extern struct {
5296 TickCountLowDeprecated: ULONG,
5297 TickCountMultiplier: ULONG,
5298 InterruptTime: KSYSTEM_TIME,
5299 SystemTime: KSYSTEM_TIME,
5300 TimeZoneBias: KSYSTEM_TIME,
5301 ImageNumberLow: USHORT,
5302 ImageNumberHigh: USHORT,
5303 NtSystemRoot: [260]WCHAR,
5304 MaxStackTraceDepth: ULONG,
5305 CryptoExponent: ULONG,
5306 TimeZoneId: ULONG,
5307 LargePageMinimum: ULONG,
5308 AitSamplingValue: ULONG,
5309 AppCompatFlag: ULONG,
5310 RNGSeedVersion: ULONGLONG,
5311 GlobalValidationRunlevel: ULONG,
5312 TimeZoneBiasStamp: LONG,
5313 NtBuildNumber: ULONG,
5314 NtProductType: NT_PRODUCT_TYPE,
5315 ProductTypeIsValid: BOOLEAN,
5316 Reserved0: [1]BOOLEAN,
5317 NativeProcessorArchitecture: USHORT,
5318 NtMajorVersion: ULONG,
5319 NtMinorVersion: ULONG,
5320 ProcessorFeatures: [PROCESSOR_FEATURE_MAX]BOOLEAN,
5321 Reserved1: ULONG,
5322 Reserved3: ULONG,
5323 TimeSlip: ULONG,
5324 AlternativeArchitecture: ALTERNATIVE_ARCHITECTURE_TYPE,
5325 BootId: ULONG,
5326 SystemExpirationDate: LARGE_INTEGER,
5327 SuiteMaskY: ULONG,
5328 KdDebuggerEnabled: BOOLEAN,
5329 DummyUnion1: extern union {
5330 MitigationPolicies: UCHAR,
5331 Alt: packed struct {
5332 NXSupportPolicy: u2,
5333 SEHValidationPolicy: u2,
5334 CurDirDevicesSkippedForDlls: u2,
5335 Reserved: u2,
5336 },
5337 },
5338 CyclesPerYield: USHORT,
5339 ActiveConsoleId: ULONG,
5340 DismountCount: ULONG,
5341 ComPlusPackage: ULONG,
5342 LastSystemRITEventTickCount: ULONG,
5343 NumberOfPhysicalPages: ULONG,
5344 SafeBootMode: BOOLEAN,
5345 DummyUnion2: extern union {
5346 VirtualizationFlags: UCHAR,
5347 Alt: packed struct {
5348 ArchStartedInEl2: u1,
5349 QcSlIsSupported: u1,
5350 SpareBits: u6,
5351 },
5352 },
5353 Reserved12: [2]UCHAR,
5354 DummyUnion3: extern union {
5355 SharedDataFlags: ULONG,
5356 Alt: packed struct {
5357 DbgErrorPortPresent: u1,
5358 DbgElevationEnabled: u1,
5359 DbgVirtEnabled: u1,
5360 DbgInstallerDetectEnabled: u1,
5361 DbgLkgEnabled: u1,
5362 DbgDynProcessorEnabled: u1,
5363 DbgConsoleBrokerEnabled: u1,
5364 DbgSecureBootEnabled: u1,
5365 DbgMultiSessionSku: u1,
5366 DbgMultiUsersInSessionSku: u1,
5367 DbgStateSeparationEnabled: u1,
5368 SpareBits: u21,
5369 },
5370 },
5371 DataFlagsPad: [1]ULONG,
5372 TestRetInstruction: ULONGLONG,
5373 QpcFrequency: LONGLONG,
5374 SystemCall: ULONG,
5375 Reserved2: ULONG,
5376 SystemCallPad: [2]ULONGLONG,
5377 DummyUnion4: extern union {
5378 TickCount: KSYSTEM_TIME,
5379 TickCountQuad: ULONG64,
5380 Alt: extern struct {
5381 ReservedTickCountOverlay: [3]ULONG,
5382 TickCountPad: [1]ULONG,
5383 },
5384 },
5385 Cookie: ULONG,
5386 CookiePad: [1]ULONG,
5387 ConsoleSessionForegroundProcessId: LONGLONG,
5388 TimeUpdateLock: ULONGLONG,
5389 BaselineSystemTimeQpc: ULONGLONG,
5390 BaselineInterruptTimeQpc: ULONGLONG,
5391 QpcSystemTimeIncrement: ULONGLONG,
5392 QpcInterruptTimeIncrement: ULONGLONG,
5393 QpcSystemTimeIncrementShift: UCHAR,
5394 QpcInterruptTimeIncrementShift: UCHAR,
5395 UnparkedProcessorCount: USHORT,
5396 EnclaveFeatureMask: [4]ULONG,
5397 TelemetryCoverageRound: ULONG,
5398 UserModeGlobalLogger: [16]USHORT,
5399 ImageFileExecutionOptions: ULONG,
5400 LangGenerationCount: ULONG,
5401 Reserved4: ULONGLONG,
5402 InterruptTimeBias: ULONGLONG,
5403 QpcBias: ULONGLONG,
5404 ActiveProcessorCount: ULONG,
5405 ActiveGroupCount: UCHAR,
5406 Reserved9: UCHAR,
5407 DummyUnion5: extern union {
5408 QpcData: USHORT,
5409 Alt: extern struct {
5410 QpcBypassEnabled: UCHAR,
5411 QpcShift: UCHAR,
5412 },
5413 },
5414 TimeZoneBiasEffectiveStart: LARGE_INTEGER,
5415 TimeZoneBiasEffectiveEnd: LARGE_INTEGER,
5416 XState: XSTATE_CONFIGURATION,
5417 FeatureConfigurationChangeStamp: KSYSTEM_TIME,
5418 Spare: ULONG,
5419 UserPointerAuthMask: ULONG64,
5420};
5421
5422/// Read-only user-mode address for the shared data.
5423/// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm
5424/// https://msrc-blog.microsoft.com/2022/04/05/randomizing-the-kuser_shared_data-structure-on-windows/
5425pub const SharedUserData: *const KUSER_SHARED_DATA = @as(*const KUSER_SHARED_DATA, @ptrFromInt(0x7FFE0000));
5426
5427pub fn IsProcessorFeaturePresent(feature: PF) bool {
5428 if (@intFromEnum(feature) >= PROCESSOR_FEATURE_MAX) return false;
5429 return SharedUserData.ProcessorFeatures[@intFromEnum(feature)] == 1;
5430}
5431
5432pub const TH32CS_SNAPHEAPLIST = 0x00000001;
5433pub const TH32CS_SNAPPROCESS = 0x00000002;
5434pub const TH32CS_SNAPTHREAD = 0x00000004;
5435pub const TH32CS_SNAPMODULE = 0x00000008;
5436pub const TH32CS_SNAPMODULE32 = 0x00000010;
5437pub const TH32CS_SNAPALL = TH32CS_SNAPHEAPLIST | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE;
5438pub const TH32CS_INHERIT = 0x80000000;
5439
5440pub const MAX_MODULE_NAME32 = 255;
5441pub const MODULEENTRY32 = extern struct {
5442 dwSize: DWORD,
5443 th32ModuleID: DWORD,
5444 th32ProcessID: DWORD,
5445 GlblcntUsage: DWORD,
5446 ProccntUsage: DWORD,
5447 modBaseAddr: *BYTE,
5448 modBaseSize: DWORD,
5449 hModule: HMODULE,
5450 szModule: [MAX_MODULE_NAME32 + 1]CHAR,
5451 szExePath: [MAX_PATH]CHAR,
5452};
5453
5454pub const SYSTEM_INFORMATION_CLASS = enum(c_int) {
5455 SystemBasicInformation = 0,
5456 SystemPerformanceInformation = 2,
5457 SystemTimeOfDayInformation = 3,
5458 SystemProcessInformation = 5,
5459 SystemProcessorPerformanceInformation = 8,
5460 SystemInterruptInformation = 23,
5461 SystemExceptionInformation = 33,
5462 SystemRegistryQuotaInformation = 37,
5463 SystemLookasideInformation = 45,
5464 SystemCodeIntegrityInformation = 103,
5465 SystemPolicyInformation = 134,
5466};
5467
5468pub const SYSTEM_BASIC_INFORMATION = extern struct {
5469 Reserved: ULONG,
5470 TimerResolution: ULONG,
5471 PageSize: ULONG,
5472 NumberOfPhysicalPages: ULONG,
5473 LowestPhysicalPageNumber: ULONG,
5474 HighestPhysicalPageNumber: ULONG,
5475 AllocationGranularity: ULONG,
5476 MinimumUserModeAddress: ULONG_PTR,
5477 MaximumUserModeAddress: ULONG_PTR,
5478 ActiveProcessorsAffinityMask: KAFFINITY,
5479 NumberOfProcessors: UCHAR,
5480};
5481
5482pub const THREADINFOCLASS = enum(c_int) {
5483 ThreadBasicInformation,
5484 ThreadTimes,
5485 ThreadPriority,
5486 ThreadBasePriority,
5487 ThreadAffinityMask,
5488 ThreadImpersonationToken,
5489 ThreadDescriptorTableEntry,
5490 ThreadEnableAlignmentFaultFixup,
5491 ThreadEventPair_Reusable,
5492 ThreadQuerySetWin32StartAddress,
5493 ThreadZeroTlsCell,
5494 ThreadPerformanceCount,
5495 ThreadAmILastThread,
5496 ThreadIdealProcessor,
5497 ThreadPriorityBoost,
5498 ThreadSetTlsArrayAddress,
5499 ThreadIsIoPending,
5500 // Windows 2000+ from here
5501 ThreadHideFromDebugger,
5502 // Windows XP+ from here
5503 ThreadBreakOnTermination,
5504 ThreadSwitchLegacyState,
5505 ThreadIsTerminated,
5506 // Windows Vista+ from here
5507 ThreadLastSystemCall,
5508 ThreadIoPriority,
5509 ThreadCycleTime,
5510 ThreadPagePriority,
5511 ThreadActualBasePriority,
5512 ThreadTebInformation,
5513 ThreadCSwitchMon,
5514 // Windows 7+ from here
5515 ThreadCSwitchPmu,
5516 ThreadWow64Context,
5517 ThreadGroupInformation,
5518 ThreadUmsInformation,
5519 ThreadCounterProfiling,
5520 ThreadIdealProcessorEx,
5521 // Windows 8+ from here
5522 ThreadCpuAccountingInformation,
5523 // Windows 8.1+ from here
5524 ThreadSuspendCount,
5525 // Windows 10+ from here
5526 ThreadHeterogeneousCpuPolicy,
5527 ThreadContainerId,
5528 ThreadNameInformation,
5529 ThreadSelectedCpuSets,
5530 ThreadSystemThreadInformation,
5531 ThreadActualGroupAffinity,
5532};
5533
5534pub const PROCESSINFOCLASS = enum(c_int) {
5535 ProcessBasicInformation,
5536 ProcessQuotaLimits,
5537 ProcessIoCounters,
5538 ProcessVmCounters,
5539 ProcessTimes,
5540 ProcessBasePriority,
5541 ProcessRaisePriority,
5542 ProcessDebugPort,
5543 ProcessExceptionPort,
5544 ProcessAccessToken,
5545 ProcessLdtInformation,
5546 ProcessLdtSize,
5547 ProcessDefaultHardErrorMode,
5548 ProcessIoPortHandlers,
5549 ProcessPooledUsageAndLimits,
5550 ProcessWorkingSetWatch,
5551 ProcessUserModeIOPL,
5552 ProcessEnableAlignmentFaultFixup,
5553 ProcessPriorityClass,
5554 ProcessWx86Information,
5555 ProcessHandleCount,
5556 ProcessAffinityMask,
5557 ProcessPriorityBoost,
5558 ProcessDeviceMap,
5559 ProcessSessionInformation,
5560 ProcessForegroundInformation,
5561 ProcessWow64Information,
5562 ProcessImageFileName,
5563 ProcessLUIDDeviceMapsEnabled,
5564 ProcessBreakOnTermination,
5565 ProcessDebugObjectHandle,
5566 ProcessDebugFlags,
5567 ProcessHandleTracing,
5568 ProcessIoPriority,
5569 ProcessExecuteFlags,
5570 ProcessTlsInformation,
5571 ProcessCookie,
5572 ProcessImageInformation,
5573 ProcessCycleTime,
5574 ProcessPagePriority,
5575 ProcessInstrumentationCallback,
5576 ProcessThreadStackAllocation,
5577 ProcessWorkingSetWatchEx,
5578 ProcessImageFileNameWin32,
5579 ProcessImageFileMapping,
5580 ProcessAffinityUpdateMode,
5581 ProcessMemoryAllocationMode,
5582 ProcessGroupInformation,
5583 ProcessTokenVirtualizationEnabled,
5584 ProcessConsoleHostProcess,
5585 ProcessWindowInformation,
5586 MaxProcessInfoClass,
5587};
5588
5589pub const PROCESS_BASIC_INFORMATION = extern struct {
5590 ExitStatus: NTSTATUS,
5591 PebBaseAddress: *PEB,
5592 AffinityMask: ULONG_PTR,
5593 BasePriority: KPRIORITY,
5594 UniqueProcessId: ULONG_PTR,
5595 InheritedFromUniqueProcessId: ULONG_PTR,
5596};
5597
5598pub const ReadMemoryError = error{
5599 Unexpected,
5600};
5601
5602pub fn ReadProcessMemory(handle: HANDLE, addr: ?LPVOID, buffer: []u8) ReadMemoryError![]u8 {
5603 var nread: usize = 0;
5604 switch (ntdll.NtReadVirtualMemory(
5605 handle,
5606 addr,
5607 buffer.ptr,
5608 buffer.len,
5609 &nread,
5610 )) {
5611 .SUCCESS => return buffer[0..nread],
5612 // TODO: map errors
5613 else => |rc| return unexpectedStatus(rc),
5614 }
5615}
5616
5617pub const WriteMemoryError = error{
5618 Unexpected,
5619};
5620
5621pub fn WriteProcessMemory(handle: HANDLE, addr: ?LPVOID, buffer: []const u8) WriteMemoryError!usize {
5622 var nwritten: usize = 0;
5623 switch (ntdll.NtWriteVirtualMemory(
5624 handle,
5625 addr,
5626 buffer.ptr,
5627 buffer.len,
5628 &nwritten,
5629 )) {
5630 .SUCCESS => return nwritten,
5631 // TODO: map errors
5632 else => |rc| return unexpectedStatus(rc),
5633 }
5634}
5635
5636pub const ProcessBaseAddressError = GetProcessMemoryInfoError || ReadMemoryError;
5637
5638/// Returns the base address of the process loaded into memory.
5639pub fn ProcessBaseAddress(handle: HANDLE) ProcessBaseAddressError!HMODULE {
5640 var info: PROCESS_BASIC_INFORMATION = undefined;
5641 var nread: DWORD = 0;
5642 const rc = ntdll.NtQueryInformationProcess(
5643 handle,
5644 .ProcessBasicInformation,
5645 &info,
5646 @sizeOf(PROCESS_BASIC_INFORMATION),
5647 &nread,
5648 );
5649 switch (rc) {
5650 .SUCCESS => {},
5651 .ACCESS_DENIED => return error.AccessDenied,
5652 .INVALID_HANDLE => return error.InvalidHandle,
5653 .INVALID_PARAMETER => unreachable,
5654 else => return unexpectedStatus(rc),
5655 }
5656
5657 var peb_buf: [@sizeOf(PEB)]u8 align(@alignOf(PEB)) = undefined;
5658 const peb_out = try ReadProcessMemory(handle, info.PebBaseAddress, &peb_buf);
5659 const ppeb: *const PEB = @ptrCast(@alignCast(peb_out.ptr));
5660 return ppeb.ImageBaseAddress;
5661}
5662
5663pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) error{ BadPathName, NameTooLong }!usize {
5664 // Each u8 in UTF-8/WTF-8 correlates to at most one u16 in UTF-16LE/WTF-16LE.
5665 if (wtf16le.len < wtf8.len) {
5666 const utf16_len = std.unicode.calcUtf16LeLenImpl(wtf8, .can_encode_surrogate_half) catch
5667 return error.BadPathName;
5668 if (utf16_len > wtf16le.len)
5669 return error.NameTooLong;
5670 }
5671 return std.unicode.wtf8ToWtf16Le(wtf16le, wtf8) catch |err| switch (err) {
5672 error.InvalidWtf8 => return error.BadPathName,
5673 };
5674}