authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-01-30 13:07:41-05:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-02-04 14:15:41-05:00
logffc6da29e3fa53a9c81bcb8af7467cdd6345538f
treed884e55fa802c6e12682135f917b13cc6c11428e
parentfce7878a9149caa80433e6d650e0bd7f60d345fd

std.Io.Threaded: implement and cleanup windows codepaths


15 files changed, 923 insertions(+), 1048 deletions(-)

build.zig+1
......@@ -1498,6 +1498,7 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {
14981498 defer dir.close(io);
14991499
15001500 var wf = b.addWriteFiles();
1501 b.step("test-docs", "Test code snippets from the docs").dependOn(&wf.step);
15011502
15021503 var it = dir.iterateAssumeFirstIteration();
15031504 while (it.next(io) catch @panic("failed to read dir")) |entry| {
lib/std/Build/Watch.zig+7-11
......@@ -366,15 +366,7 @@ const Os = switch (builtin.os.tag) {
366366 .MaximumLength = @intCast(path_len_bytes),
367367 .Buffer = @constCast(sub_path_w.span().ptr),
368368 };
369 var attr = windows.OBJECT_ATTRIBUTES{
370 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
371 .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd,
372 .Attributes = .{},
373 .ObjectName = &nt_name,
374 .SecurityDescriptor = null,
375 .SecurityQualityOfService = null,
376 };
377 var io: windows.IO_STATUS_BLOCK = undefined;
369 var iosb: windows.IO_STATUS_BLOCK = undefined;
378370
379371 switch (windows.ntdll.NtCreateFile(
380372 &dir_handle,
......@@ -385,14 +377,18 @@ const Os = switch (builtin.os.tag) {
385377 .STANDARD = .{ .SYNCHRONIZE = true },
386378 .GENERIC = .{ .READ = true },
387379 },
388 &attr,
389 &io,
380 &.{
381 .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd,
382 .ObjectName = &nt_name,
383 },
384 &iosb,
390385 null,
391386 .{},
392387 .VALID_FLAGS,
393388 .OPEN,
394389 .{
395390 .DIRECTORY_FILE = true,
391 .IO = .ASYNCHRONOUS,
396392 .OPEN_FOR_BACKUP_INTENT = true,
397393 },
398394 null,
lib/std/Io/Threaded.zig+381-341
......@@ -76,6 +76,7 @@ environ: Environ,
7676
7777null_file: NullFile = .{},
7878random_file: RandomFile = .{},
79pipe_file: PipeFile = .{},
7980
8081csprng: Csprng = .{},
8182
......@@ -121,7 +122,7 @@ pub const Argv0 = switch (native_os) {
121122
122123const Environ = struct {
123124 /// Unmodified data directly from the OS.
124 process_environ: process.Environ = .empty,
125 process_environ: process.Environ,
125126 /// Protected by `mutex`. Determines whether the other fields have been
126127 /// memoized based on `process_environ`.
127128 initialized: bool = false,
......@@ -131,13 +132,15 @@ const Environ = struct {
131132 /// Protected by `mutex`. Memoized based on `process_environ`.
132133 string: String = .{},
133134 /// ZIG_PROGRESS
134 zig_progress_handle: std.Progress.ParentFileError!u31 = error.EnvironmentVariableMissing,
135 zig_progress_file: std.Progress.ParentFileError!File = error.EnvironmentVariableMissing,
135136 /// Protected by `mutex`. Tracks the problem, if any, that occurred when
136137 /// trying to scan environment variables.
137138 ///
138139 /// Errors are only possible on WASI.
139140 err: ?Error = null,
140141
142 pub const empty: Environ = .{ .process_environ = .empty };
143
141144 pub const Error = Allocator.Error || Io.UnexpectedError;
142145
143146 pub const Exist = struct {
......@@ -193,6 +196,24 @@ pub const RandomFile = switch (native_os) {
193196 },
194197};
195198
199pub const PipeFile = switch (native_os) {
200 .windows => struct {
201 handle: ?windows.HANDLE = null,
202
203 fn deinit(this: *@This()) void {
204 if (this.handle) |handle| {
205 windows.CloseHandle(handle);
206 this.handle = null;
207 }
208 }
209 },
210 else => struct {
211 fn deinit(this: @This()) void {
212 _ = this;
213 }
214 },
215};
216
196217pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
197218 unknown = 0,
198219 _,
......@@ -1496,7 +1517,9 @@ pub const init_single_threaded: Threaded = .{
14961517 .old_sig_pipe = undefined,
14971518 .have_signal_handler = false,
14981519 .argv0 = .empty,
1499 .environ = .{},
1520 .environ = .{ .process_environ = .{
1521 .block = if (process.Environ.Block == process.Environ.GlobalBlock) .global else .empty,
1522 } },
15001523 .worker_threads = .init(null),
15011524 .disable_memory_mapping = false,
15021525};
......@@ -1531,6 +1554,7 @@ pub fn deinit(t: *Threaded) void {
15311554 }
15321555 t.null_file.deinit();
15331556 t.random_file.deinit();
1557 t.pipe_file.deinit();
15341558 t.* = undefined;
15351559}
15361560
......@@ -1573,14 +1597,7 @@ fn worker(t: *Threaded) void {
15731597 },
15741598 },
15751599 },
1576 &.{
1577 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
1578 .RootDirectory = null,
1579 .ObjectName = null,
1580 .Attributes = .{},
1581 .SecurityDescriptor = null,
1582 .SecurityQualityOfService = null,
1583 },
1600 &.{ .ObjectName = null },
15841601 &windows.teb().ClientId,
15851602 ) == .SUCCESS);
15861603 }
......@@ -3376,12 +3393,8 @@ fn dirCreateDirPathOpenWindows(
33763393 },
33773394 },
33783395 &.{
3379 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
33803396 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
3381 .Attributes = .{},
33823397 .ObjectName = &nt_name,
3383 .SecurityDescriptor = null,
3384 .SecurityQualityOfService = null,
33853398 },
33863399 &io_status_block,
33873400 null,
......@@ -4063,13 +4076,9 @@ fn dirAccessWindows(
40634076 .MaximumLength = path_len_bytes,
40644077 .Buffer = @constCast(sub_path_w.ptr),
40654078 };
4066 var attr: windows.OBJECT_ATTRIBUTES = .{
4067 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
4079 const attr: windows.OBJECT_ATTRIBUTES = .{
40684080 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
4069 .Attributes = .{},
40704081 .ObjectName = &nt_name,
4071 .SecurityDescriptor = null,
4072 .SecurityQualityOfService = null,
40734082 };
40744083 var basic_info: windows.FILE.BASIC_INFORMATION = undefined;
40754084 const syscall: Syscall = try .start();
......@@ -4285,14 +4294,8 @@ fn dirCreateFileWindows(
42854294 .Buffer = @constCast(sub_path_w.ptr),
42864295 };
42874296 const attr: windows.OBJECT_ATTRIBUTES = .{
4288 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
42894297 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
4290 .Attributes = .{
4291 .INHERIT = false,
4292 },
42934298 .ObjectName = &nt_name,
4294 .SecurityDescriptor = null,
4295 .SecurityQualityOfService = null,
42964299 };
42974300 const create_disposition: windows.FILE.CREATE_DISPOSITION = if (flags.exclusive)
42984301 .CREATE
......@@ -4905,17 +4908,6 @@ pub fn dirOpenFileWtf16(
49054908 .MaximumLength = path_len_bytes,
49064909 .Buffer = @constCast(sub_path_w.ptr),
49074910 };
4908 var attr: w.OBJECT_ATTRIBUTES = .{
4909 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
4910 .RootDirectory = dir_handle,
4911 .Attributes = .{
4912 // TODO should we set INHERIT=false?
4913 //.INHERIT = false,
4914 },
4915 .ObjectName = &nt_name,
4916 .SecurityDescriptor = null,
4917 .SecurityQualityOfService = null,
4918 };
49194911 var io_status_block: w.IO_STATUS_BLOCK = undefined;
49204912
49214913 // There are multiple kernel bugs being worked around with retries.
......@@ -4934,7 +4926,10 @@ pub fn dirOpenFileWtf16(
49344926 .WRITE = flags.isWrite(),
49354927 },
49364928 },
4937 &attr,
4929 &.{
4930 .RootDirectory = dir_handle,
4931 .ObjectName = &nt_name,
4932 },
49384933 &io_status_block,
49394934 null,
49404935 .{ .NORMAL = true },
......@@ -5302,12 +5297,8 @@ pub fn dirOpenDirWindows(
53025297 },
53035298 },
53045299 &.{
5305 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
53065300 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
5307 .Attributes = .{},
53085301 .ObjectName = &nt_name,
5309 .SecurityDescriptor = null,
5310 .SecurityQualityOfService = null,
53115302 },
53125303 &io_status_block,
53135304 null,
......@@ -6517,12 +6508,8 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
65176508 .SYNCHRONIZE = true,
65186509 } },
65196510 &.{
6520 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
65216511 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
6522 .Attributes = .{},
65236512 .ObjectName = &nt_name,
6524 .SecurityDescriptor = null,
6525 .SecurityQualityOfService = null,
65266513 },
65276514 &io_status_block,
65286515 null,
......@@ -6531,6 +6518,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
65316518 .OPEN,
65326519 .{
65336520 .DIRECTORY_FILE = remove_dir,
6521 .IO = .SYNCHRONOUS_NONALERT,
65346522 .NON_DIRECTORY_FILE = !remove_dir,
65356523 .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead?
65366524 },
......@@ -7342,14 +7330,8 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink
73427330 .Buffer = @constCast(sub_path_w.ptr),
73437331 };
73447332 const attr: windows.OBJECT_ATTRIBUTES = .{
7345 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
73467333 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
7347 .Attributes = .{
7348 .INHERIT = false,
7349 },
73507334 .ObjectName = &nt_name,
7351 .SecurityDescriptor = null,
7352 .SecurityQualityOfService = null,
73537335 };
73547336 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
73557337 var result_handle: windows.HANDLE = undefined;
......@@ -7906,24 +7888,19 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void {
79067888 const t: *Threaded = @ptrCast(@alignCast(userdata));
79077889 _ = t;
79087890
7891 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
79097892 const syscall: Syscall = try .start();
79107893 while (true) {
7911 if (windows.kernel32.FlushFileBuffers(file.handle) != 0) {
7912 return syscall.finish();
7913 }
7914 switch (windows.GetLastError()) {
7915 .SUCCESS => unreachable, // `FlushFileBuffers` returned nonzero
7916 .INVALID_HANDLE => unreachable,
7917 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time
7918 .UNEXP_NET_ERR => return syscall.fail(error.InputOutput),
7919 .OPERATION_ABORTED => {
7894 switch (windows.ntdll.NtFlushBuffersFile(file.handle, &io_status_block)) {
7895 .SUCCESS => break syscall.finish(),
7896 .CANCELLED => {
79207897 try syscall.checkCancel();
79217898 continue;
79227899 },
7923 else => |err| {
7924 syscall.finish();
7925 return windows.unexpectedError(err);
7926 },
7900 .INVALID_HANDLE => unreachable,
7901 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time
7902 .UNEXPECTED_NETWORK_ERROR => return syscall.fail(error.InputOutput),
7903 else => |status| return syscall.unexpectedNtstatus(status),
79277904 }
79287905 }
79297906}
......@@ -14556,22 +14533,39 @@ fn scanEnviron(t: *Threaded) void {
1455614533 comptime assert(@sizeOf(Environ.String) == 0);
1455714534 }
1455814535 } else {
14559 for (t.environ.process_environ.block) |opt_line| {
14560 const line = opt_line.?;
14561 var line_i: usize = 0;
14562 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
14563 const key = line[0..line_i];
14536 for (t.environ.process_environ.block.slice) |opt_entry| {
14537 const entry = opt_entry.?;
14538 var entry_i: usize = 0;
14539 while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {}
14540 const key = entry[0..entry_i];
1456414541
14565 var end_i: usize = line_i;
14566 while (line[end_i] != 0) : (end_i += 1) {}
14567 const value = line[line_i + 1 .. end_i :0];
14542 var end_i: usize = entry_i;
14543 while (entry[end_i] != 0) : (end_i += 1) {}
14544 const value = entry[entry_i + 1 .. end_i :0];
1456814545
1456914546 if (std.mem.eql(u8, key, "NO_COLOR")) {
1457014547 t.environ.exist.NO_COLOR = true;
1457114548 } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) {
1457214549 t.environ.exist.CLICOLOR_FORCE = true;
1457314550 } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) {
14574 t.environ.zig_progress_handle = std.fmt.parseInt(u31, value, 10) catch error.UnrecognizedFormat;
14551 t.environ.zig_progress_file = file: {
14552 const int = std.fmt.parseInt(switch (@typeInfo(File.Handle)) {
14553 .int => |int_info| @Int(
14554 .unsigned,
14555 int_info.bits - @intFromBool(int_info.signedness == .signed),
14556 ),
14557 .pointer => usize,
14558 else => break :file error.UnsupportedOperation,
14559 }, value, 10) catch break :file error.UnrecognizedFormat;
14560 break :file .{
14561 .handle = switch (@typeInfo(File.Handle)) {
14562 .int => int,
14563 .pointer => @ptrFromInt(int),
14564 else => comptime unreachable,
14565 },
14566 .flags = .{ .nonblocking = true },
14567 };
14568 };
1457514569 } else inline for (@typeInfo(Environ.String).@"struct".fields) |field| {
1457614570 if (std.mem.eql(u8, key, field.name)) @field(t.environ.string, field.name) = value;
1457714571 }
......@@ -14594,19 +14588,17 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
1459414588 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);
1459514589 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
1459614590
14597 const envp: [*:null]const ?[*:0]const u8 = m: {
14591 const env_block = env_block: {
1459814592 const prog_fd: i32 = -1;
14599 if (options.environ_map) |environ_map| {
14600 break :m (try environ_map.createBlockPosix(arena, .{
14601 .zig_progress_fd = prog_fd,
14602 })).ptr;
14603 }
14604 break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{
14593 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
1460514594 .zig_progress_fd = prog_fd,
14606 })).ptr;
14595 });
14596 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{
14597 .zig_progress_fd = prog_fd,
14598 });
1460714599 };
1460814600
14609 return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH);
14601 return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
1461014602}
1461114603
1461214604fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError {
......@@ -14705,16 +14697,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1470514697 const prog_fileno = 3;
1470614698 comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno);
1470714699
14708 const envp: [*:null]const ?[*:0]const u8 = m: {
14700 const env_block = env_block: {
1470914701 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
14710 if (options.environ_map) |environ_map| {
14711 break :m (try environ_map.createBlockPosix(arena, .{
14712 .zig_progress_fd = prog_fd,
14713 })).ptr;
14714 }
14715 break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{
14702 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
1471614703 .zig_progress_fd = prog_fd,
14717 })).ptr;
14704 });
14705 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{
14706 .zig_progress_fd = prog_fd,
14707 });
1471814708 };
1471914709
1472014710 // This pipe communicates to the parent errors in the child between `fork` and `execvpe`.
......@@ -14797,7 +14787,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1479714787 }
1479814788 }
1479914789
14800 const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH);
14790 const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
1480114791 forkBail(ep1, err);
1480214792 }
1480314793
......@@ -14811,7 +14801,6 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1481114801 if (options.stderr == .pipe) posix.close(stderr_pipe[1]);
1481214802
1481314803 if (prog_pipe[1] != -1) posix.close(prog_pipe[1]);
14814
1481514804 options.progress_node.setIpcFd(prog_pipe[0]);
1481614805
1481714806 return .{
......@@ -14935,42 +14924,44 @@ fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT
1493514924 // some rare edge cases where our process handle no longer has the
1493614925 // PROCESS_TERMINATE access right, so let's do another check to make
1493714926 // sure the process is really no longer running:
14938 windows.WaitForSingleObjectEx(handle, 0, false) catch return error.AccessDenied;
14939 return error.AlreadyTerminated;
14927 const minimal_timeout: windows.LARGE_INTEGER = -1;
14928 switch (windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &minimal_timeout)) {
14929 .SUCCESS => return error.AlreadyTerminated,
14930 else => return error.AccessDenied,
14931 }
1494014932 },
1494114933 else => |err| return windows.unexpectedError(err),
1494214934 }
1494314935 }
14944 _ = windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE);
14936 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
14937 _ = windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &infinite_timeout);
1494514938 childCleanupWindows(child);
1494614939}
1494714940
1494814941fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term {
1494914942 const handle = child.id.?;
1495014943
14951 const syscall: Syscall = try .start();
14952 while (true) switch (windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE)) {
14953 windows.WAIT_OBJECT_0 => break syscall.finish(),
14954 windows.WAIT_ABANDONED, windows.WAIT_TIMEOUT => {
14955 try syscall.checkCancel();
14944 const alertable_syscall: AlertableSyscall = try .start();
14945 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
14946 while (true) switch (windows.ntdll.NtWaitForSingleObject(handle, windows.TRUE, &infinite_timeout)) {
14947 windows.NTSTATUS.WAIT_0 => break alertable_syscall.finish(),
14948 .USER_APC, .ALERTED, .TIMEOUT => {
14949 try alertable_syscall.checkCancel();
1495614950 continue;
1495714951 },
14958 windows.WAIT_FAILED => {
14959 syscall.finish();
14960 switch (windows.GetLastError()) {
14961 else => |err| return windows.unexpectedError(err),
14962 }
14963 },
14964 else => return syscall.fail(error.Unexpected),
14952 else => |status| return alertable_syscall.unexpectedNtstatus(status),
1496514953 };
1496614954
14967 const term: process.Child.Term = x: {
14968 var exit_code: windows.DWORD = undefined;
14969 if (windows.kernel32.GetExitCodeProcess(handle, &exit_code) == 0) {
14970 break :x .{ .unknown = 0 };
14971 } else {
14972 break :x .{ .exited = @as(u8, @truncate(exit_code)) };
14973 }
14955 var info: windows.PROCESS_BASIC_INFORMATION = undefined;
14956 const term: process.Child.Term = switch (windows.ntdll.NtQueryInformationProcess(
14957 handle,
14958 .BasicInformation,
14959 &info,
14960 @sizeOf(windows.PROCESS_BASIC_INFORMATION),
14961 null,
14962 )) {
14963 .SUCCESS => .{ .exited = @as(u8, @truncate(@intFromEnum(info.ExitStatus))) },
14964 else => .{ .unknown = 0 },
1497414965 };
1497514966
1497614967 childCleanupWindows(child);
......@@ -15233,88 +15224,70 @@ fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32
1523315224fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
1523415225 const t: *Threaded = @ptrCast(@alignCast(userdata));
1523515226
15236 var saAttr: windows.SECURITY_ATTRIBUTES = .{
15237 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
15238 .bInheritHandle = windows.TRUE,
15239 .lpSecurityDescriptor = null,
15240 };
15241
1524215227 const any_ignore =
1524315228 options.stdin == .ignore or
1524415229 options.stdout == .ignore or
1524515230 options.stderr == .ignore;
15246
15247 const nul_handle = if (any_ignore) try getNulHandle(t) else undefined;
15248
15249 var g_hChildStd_IN_Rd: ?windows.HANDLE = null;
15250 var g_hChildStd_IN_Wr: ?windows.HANDLE = null;
15251 switch (options.stdin) {
15252 .pipe => {
15253 try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr);
15254 },
15255 .ignore => {
15256 g_hChildStd_IN_Rd = nul_handle;
15257 },
15258 .inherit => {
15259 g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE) catch null;
15260 },
15261 .close => {
15262 g_hChildStd_IN_Rd = null;
15263 },
15264 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
15265 }
15266 errdefer if (options.stdin == .pipe) {
15267 windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr);
15268 };
15269
15270 var g_hChildStd_OUT_Rd: ?windows.HANDLE = null;
15271 var g_hChildStd_OUT_Wr: ?windows.HANDLE = null;
15272 switch (options.stdout) {
15273 .pipe => {
15274 try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr);
15275 },
15276 .ignore => {
15277 g_hChildStd_OUT_Wr = nul_handle;
15278 },
15279 .inherit => {
15280 g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) catch null;
15281 },
15282 .close => {
15283 g_hChildStd_OUT_Wr = null;
15284 },
15285 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
15286 }
15287 errdefer if (options.stdout == .pipe) {
15288 windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr);
15289 };
15290
15291 var g_hChildStd_ERR_Rd: ?windows.HANDLE = null;
15292 var g_hChildStd_ERR_Wr: ?windows.HANDLE = null;
15293 switch (options.stderr) {
15294 .pipe => {
15295 try windowsMakeAsyncPipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr);
15296 },
15297 .ignore => {
15298 g_hChildStd_ERR_Wr = nul_handle;
15299 },
15300 .inherit => {
15301 g_hChildStd_ERR_Wr = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null;
15302 },
15303 .close => {
15304 g_hChildStd_ERR_Wr = null;
15305 },
15306 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
15307 }
15308 errdefer if (options.stderr == .pipe) {
15309 windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr);
15310 };
15231 const nul_handle = if (any_ignore) try getNulDevice(t) else undefined;
15232
15233 const any_inherit =
15234 options.stdin == .inherit or
15235 options.stdout == .inherit or
15236 options.stderr == .inherit;
15237 const peb = if (any_inherit) windows.peb() else undefined;
15238
15239 const stdin_pipe = if (options.stdin == .pipe) try t.windowsCreatePipe(.{
15240 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15241 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15242 .outbound = true,
15243 }) else undefined;
15244 errdefer if (options.stdin == .pipe) for (stdin_pipe) |handle| windows.CloseHandle(handle);
15245
15246 const stdout_pipe = if (options.stdout == .pipe) try t.windowsCreatePipe(.{
15247 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15248 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15249 .inbound = true,
15250 }) else undefined;
15251 errdefer if (options.stdout == .pipe) for (stdout_pipe) |handle| windows.CloseHandle(handle);
15252
15253 const stderr_pipe = if (options.stderr == .pipe) try t.windowsCreatePipe(.{
15254 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15255 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15256 .inbound = true,
15257 }) else undefined;
15258 errdefer if (options.stderr == .pipe) for (stderr_pipe) |handle| windows.CloseHandle(handle);
15259
15260 const prog_pipe = if (options.progress_node.index != .none) try t.windowsCreatePipe(.{
15261 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15262 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15263 .inbound = true,
15264 }) else undefined;
15265 errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle);
1531115266
1531215267 var siStartInfo: windows.STARTUPINFOW = .{
1531315268 .cb = @sizeOf(windows.STARTUPINFOW),
15314 .hStdError = g_hChildStd_ERR_Wr,
15315 .hStdOutput = g_hChildStd_OUT_Wr,
15316 .hStdInput = g_hChildStd_IN_Rd,
1531715269 .dwFlags = windows.STARTF_USESTDHANDLES,
15270 .hStdInput = switch (options.stdin) {
15271 .inherit => peb.ProcessParameters.hStdInput,
15272 .file => |file| file.handle,
15273 .ignore => nul_handle,
15274 .pipe => stdin_pipe[1],
15275 .close => null,
15276 },
15277 .hStdOutput = switch (options.stdout) {
15278 .inherit => peb.ProcessParameters.hStdOutput,
15279 .file => |file| file.handle,
15280 .ignore => nul_handle,
15281 .pipe => stdout_pipe[1],
15282 .close => null,
15283 },
15284 .hStdError = switch (options.stderr) {
15285 .inherit => peb.ProcessParameters.hStdError,
15286 .file => |file| file.handle,
15287 .ignore => nul_handle,
15288 .pipe => stderr_pipe[1],
15289 .close => null,
15290 },
1531815291
1531915292 .lpReserved = null,
1532015293 .lpDesktop = null,
......@@ -15360,8 +15333,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1536015333 };
1536115334 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
1536215335
15363 const maybe_envp_buf = if (options.environ_map) |environ_map| try environ_map.createBlockWindows(arena) else null;
15364 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
15336 const env_block = env_block: {
15337 const prog_handle = if (options.progress_node.index != .none)
15338 prog_pipe[1]
15339 else
15340 windows.INVALID_HANDLE_VALUE;
15341 if (options.environ_map) |environ_map| break :env_block try environ_map.createWindowsBlock(arena, .{
15342 .zig_progress_handle = prog_handle,
15343 });
15344 break :env_block try t.environ.process_environ.createWindowsBlock(arena, .{
15345 .zig_progress_handle = if (options.progress_node.index != .none) prog_pipe[1] else windows.INVALID_HANDLE_VALUE,
15346 });
15347 };
1536515348
1536615349 const app_name_wtf8 = options.argv[0];
1536715350 const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8);
......@@ -15436,7 +15419,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1543615419 &app_buf,
1543715420 PATHEXT,
1543815421 &cmd_line_cache,
15439 envp_ptr,
15422 env_block,
1544015423 cwd_w_ptr,
1544115424 flags,
1544215425 &siStartInfo,
......@@ -15471,7 +15454,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1547115454 &app_buf,
1547215455 PATHEXT,
1547315456 &cmd_line_cache,
15474 envp_ptr,
15457 env_block,
1547515458 cwd_w_ptr,
1547615459 flags,
1547715460 &siStartInfo,
......@@ -15491,21 +15474,40 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1549115474 };
1549215475 }
1549315476
15494 if (options.stdin == .pipe) windows.CloseHandle(g_hChildStd_IN_Rd.?);
15495 if (options.stderr == .pipe) windows.CloseHandle(g_hChildStd_ERR_Wr.?);
15496 if (options.stdout == .pipe) windows.CloseHandle(g_hChildStd_OUT_Wr.?);
15477 if (options.progress_node.index != .none) {
15478 windows.CloseHandle(prog_pipe[1]);
15479 options.progress_node.setIpcFd(prog_pipe[0]);
15480 }
1549715481
1549815482 return .{
1549915483 .id = piProcInfo.hProcess,
1550015484 .thread_handle = piProcInfo.hThread,
15501 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = false } } else null,
15502 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
15503 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
15485 .stdin = stdin: switch (options.stdin) {
15486 .pipe => {
15487 windows.CloseHandle(stdin_pipe[1]);
15488 break :stdin .{ .handle = stdin_pipe[0], .flags = .{ .nonblocking = false } };
15489 },
15490 else => null,
15491 },
15492 .stdout = stdout: switch (options.stdout) {
15493 .pipe => {
15494 windows.CloseHandle(stdout_pipe[1]);
15495 break :stdout .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = true } };
15496 },
15497 else => null,
15498 },
15499 .stderr = stderr: switch (options.stderr) {
15500 .pipe => {
15501 windows.CloseHandle(stderr_pipe[1]);
15502 break :stderr .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = true } };
15503 },
15504 else => null,
15505 },
1550415506 .request_resource_usage_statistics = options.request_resource_usage_statistics,
1550515507 };
1550615508}
1550715509
15508fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
15510fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1550915511 {
1551015512 mutexLock(&t.mutex);
1551115513 defer mutexUnlock(&t.mutex);
......@@ -15513,12 +15515,6 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1551315515 }
1551415516
1551515517 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' };
15516
15517 var nt_name: windows.UNICODE_STRING = .{
15518 .Length = device_path.len * 2,
15519 .MaximumLength = 0,
15520 .Buffer = @constCast(&device_path),
15521 };
1552215518 var fresh_handle: windows.HANDLE = undefined;
1552315519 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1552415520 var syscall: Syscall = try .start();
......@@ -15529,12 +15525,11 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1552915525 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } },
1553015526 },
1553115527 &.{
15532 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
15533 .RootDirectory = null,
15534 .ObjectName = &nt_name,
15535 .Attributes = .{},
15536 .SecurityDescriptor = null,
15537 .SecurityQualityOfService = null,
15528 .ObjectName = @constCast(&windows.UNICODE_STRING{
15529 .Length = @sizeOf(@TypeOf(device_path)),
15530 .MaximumLength = 0,
15531 .Buffer = @constCast(&device_path),
15532 }),
1553815533 },
1553915534 &io_status_block,
1554015535 .VALID_FLAGS,
......@@ -15561,7 +15556,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1556115556 };
1556215557}
1556315558
15564fn getNulHandle(t: *Threaded) !windows.HANDLE {
15559fn getNulDevice(t: *Threaded) !windows.HANDLE {
1556515560 {
1556615561 mutexLock(&t.mutex);
1556715562 defer mutexUnlock(&t.mutex);
......@@ -15569,44 +15564,26 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE {
1556915564 }
1557015565
1557115566 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' };
15572 var nt_name: windows.UNICODE_STRING = .{
15573 .Length = device_path.len * 2,
15574 .MaximumLength = 0,
15575 .Buffer = @constCast(&device_path),
15576 };
15577 const attr: windows.OBJECT_ATTRIBUTES = .{
15578 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
15579 .RootDirectory = null,
15580 .Attributes = .{
15581 .INHERIT = true,
15582 },
15583 .ObjectName = &nt_name,
15584 .SecurityDescriptor = null,
15585 .SecurityQualityOfService = null,
15586 };
15587 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1558815567 var fresh_handle: windows.HANDLE = undefined;
15568 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1558915569 var syscall: Syscall = try .start();
15590 while (true) switch (windows.ntdll.NtCreateFile(
15570 while (true) switch (windows.ntdll.NtOpenFile(
1559115571 &fresh_handle,
1559215572 .{
1559315573 .STANDARD = .{ .SYNCHRONIZE = true },
15594 .GENERIC = .{ .WRITE = true, .READ = true },
15574 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true, .WRITE_DATA = true } },
15575 },
15576 &.{
15577 .Attributes = .{ .INHERIT = true },
15578 .ObjectName = @constCast(&windows.UNICODE_STRING{
15579 .Length = @sizeOf(@TypeOf(device_path)),
15580 .MaximumLength = 0,
15581 .Buffer = @constCast(&device_path),
15582 }),
1559515583 },
15596 &attr,
1559715584 &io_status_block,
15598 null,
15599 .{ .NORMAL = true },
1560015585 .VALID_FLAGS,
15601 .OPEN,
15602 .{
15603 .DIRECTORY_FILE = false,
15604 .NON_DIRECTORY_FILE = true,
15605 .IO = .SYNCHRONOUS_NONALERT,
15606 .OPEN_REPARSE_POINT = false,
15607 },
15608 null,
15609 0,
15586 .{ .IO = .SYNCHRONOUS_NONALERT },
1561015587 )) {
1561115588 .SUCCESS => {
1561215589 syscall.finish();
......@@ -15620,6 +15597,64 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE {
1562015597 return fresh_handle;
1562115598 }
1562215599 },
15600 .CANCELLED => {
15601 try syscall.checkCancel();
15602 continue;
15603 },
15604 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
15605 .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status),
15606 .INVALID_HANDLE => |status| return syscall.ntstatusBug(status),
15607 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
15608 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
15609 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
15610 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
15611 .SHARING_VIOLATION => return syscall.fail(error.AccessDenied),
15612 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
15613 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice),
15614 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
15615 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
15616 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
15617 else => |status| return syscall.unexpectedNtstatus(status),
15618 };
15619}
15620
15621fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE {
15622 {
15623 mutexLock(&t.mutex);
15624 defer mutexUnlock(&t.mutex);
15625 if (t.pipe_file.handle) |handle| return handle;
15626 }
15627
15628 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'a', 'm', 'e', 'd', 'P', 'i', 'p', 'e', '\\' };
15629 var fresh_handle: windows.HANDLE = undefined;
15630 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
15631 var syscall: Syscall = try .start();
15632 while (true) switch (windows.ntdll.NtOpenFile(
15633 &fresh_handle,
15634 .{ .STANDARD = .{ .SYNCHRONIZE = true } },
15635 &.{
15636 .ObjectName = @constCast(&windows.UNICODE_STRING{
15637 .Length = @sizeOf(@TypeOf(device_path)),
15638 .MaximumLength = 0,
15639 .Buffer = @constCast(&device_path),
15640 }),
15641 },
15642 &io_status_block,
15643 .VALID_FLAGS,
15644 .{ .IO = .SYNCHRONOUS_NONALERT },
15645 )) {
15646 .SUCCESS => {
15647 syscall.finish();
15648 mutexLock(&t.mutex); // Another thread might have won the race.
15649 defer mutexUnlock(&t.mutex);
15650 if (t.pipe_file.handle) |prev_handle| {
15651 windows.CloseHandle(fresh_handle);
15652 return prev_handle;
15653 } else {
15654 t.pipe_file.handle = fresh_handle;
15655 return fresh_handle;
15656 }
15657 },
1562315658 .DELETE_PENDING => {
1562415659 // This error means that there *was* a file in this location on
1562515660 // the file system, but it was deleted. However, the OS is not
......@@ -15666,7 +15701,7 @@ fn windowsCreateProcessPathExt(
1566615701 app_buf: *std.ArrayList(u16),
1566715702 pathext: [:0]const u16,
1566815703 cmd_line_cache: *WindowsCommandLineCache,
15669 envp_ptr: ?[*:0]const u16,
15704 env_block: ?process.Environ.WindowsBlock,
1567015705 cwd_ptr: ?[*:0]u16,
1567115706 flags: windows.CreateProcessFlags,
1567215707 lpStartupInfo: *windows.STARTUPINFOW,
......@@ -15843,7 +15878,7 @@ fn windowsCreateProcessPathExt(
1584315878 if (windowsCreateProcess(
1584415879 app_name_w.ptr,
1584515880 cmd_line_w.ptr,
15846 envp_ptr,
15881 env_block,
1584715882 cwd_ptr,
1584815883 flags,
1584915884 lpStartupInfo,
......@@ -15903,7 +15938,7 @@ fn windowsCreateProcessPathExt(
1590315938 else
1590415939 full_app_name;
1590515940
15906 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {
15941 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, env_block, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {
1590715942 return;
1590815943 } else |err| switch (err) {
1590915944 error.FileNotFound => continue,
......@@ -15927,7 +15962,7 @@ fn windowsCreateProcessPathExt(
1592715962fn windowsCreateProcess(
1592815963 app_name: [*:0]u16,
1592915964 cmd_line: [*:0]u16,
15930 env_ptr: ?[*:0]const u16,
15965 env_block: ?process.Environ.WindowsBlock,
1593115966 cwd_ptr: ?[*:0]u16,
1593215967 flags: windows.CreateProcessFlags,
1593315968 lpStartupInfo: *windows.STARTUPINFOW,
......@@ -15942,7 +15977,7 @@ fn windowsCreateProcess(
1594215977 null,
1594315978 windows.TRUE,
1594415979 flags,
15945 env_ptr,
15980 if (env_block) |block| block.slice.ptr else null,
1594615981 cwd_ptr,
1594715982 lpStartupInfo,
1594815983 lpProcessInformation,
......@@ -16463,11 +16498,11 @@ fn posixExecv(
1646316498 arg0_expand: process.ArgExpansion,
1646416499 file: [*:0]const u8,
1646516500 child_argv: [*:null]?[*:0]const u8,
16466 envp: [*:null]const ?[*:0]const u8,
16501 env_block: process.Environ.PosixBlock,
1646716502 PATH: []const u8,
1646816503) process.ReplaceError {
1646916504 const file_slice = std.mem.sliceTo(file, 0);
16470 if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, envp);
16505 if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, env_block);
1647116506
1647216507 // Use of PATH_MAX here is valid as the path_buf will be passed
1647316508 // directly to the operating system in posixExecvPath.
......@@ -16495,7 +16530,7 @@ fn posixExecv(
1649516530 .expand => child_argv[0] = full_path,
1649616531 .no_expand => {},
1649716532 }
16498 err = posixExecvPath(full_path, child_argv, envp);
16533 err = posixExecvPath(full_path, child_argv, env_block);
1649916534 switch (err) {
1650016535 error.AccessDenied => seen_eacces = true,
1650116536 error.FileNotFound, error.NotDir => {},
......@@ -16510,10 +16545,10 @@ fn posixExecv(
1651016545pub fn posixExecvPath(
1651116546 path: [*:0]const u8,
1651216547 child_argv: [*:null]const ?[*:0]const u8,
16513 envp: [*:null]const ?[*:0]const u8,
16548 env_block: process.Environ.PosixBlock,
1651416549) process.ReplaceError {
1651516550 try Thread.checkCancel();
16516 switch (posix.errno(posix.system.execve(path, child_argv, envp))) {
16551 switch (posix.errno(posix.system.execve(path, child_argv, env_block.slice.ptr))) {
1651716552 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
1651816553 .@"2BIG" => return error.SystemResources,
1651916554 .MFILE => return error.ProcessFdQuotaExceeded,
......@@ -16545,100 +16580,105 @@ pub fn posixExecvPath(
1654516580 }
1654616581}
1654716582
16548fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
16549 var rd_h: windows.HANDLE = undefined;
16550 var wr_h: windows.HANDLE = undefined;
16551 try windows.CreatePipe(&rd_h, &wr_h, sattr);
16552 errdefer windowsDestroyPipe(rd_h, wr_h);
16553 try windows.SetHandleInformation(wr_h, windows.HANDLE_FLAG_INHERIT, 0);
16554 rd.* = rd_h;
16555 wr.* = wr_h;
16556}
16557
16558fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void {
16559 if (rd) |h| posix.close(h);
16560 if (wr) |h| posix.close(h);
16561}
16562
16563fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
16564 var tmp_bufw: [128]u16 = undefined;
16583pub const CreatePipeOptions = struct {
16584 server: End,
16585 client: End,
16586 inbound: bool = false,
16587 outbound: bool = false,
16588 maximum_instances: u32 = 1,
16589 quota: u32 = 4096,
16590 default_timeout: windows.LARGE_INTEGER = -120 * std.time.ns_per_s / 100,
1656516591
16566 // Anonymous pipes are built upon Named pipes.
16567 // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe
16568 // Asynchronous (overlapped) read and write operations are not supported by anonymous pipes.
16569 // https://docs.microsoft.com/en-us/windows/win32/ipc/anonymous-pipe-operations
16570 const pipe_path = blk: {
16571 var tmp_buf: [128]u8 = undefined;
16572 // Forge a random path for the pipe.
16573 const pipe_path = std.fmt.bufPrintSentinel(
16574 &tmp_buf,
16575 "\\\\.\\pipe\\zig-childprocess-{d}-{d}",
16576 .{ windows.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) },
16577 0,
16578 ) catch unreachable;
16579 const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable;
16580 tmp_bufw[len] = 0;
16581 break :blk tmp_bufw[0..len :0];
16592 pub const End = struct {
16593 attributes: windows.OBJECT_ATTRIBUTES.ATTRIBUTES = .{},
16594 mode: windows.FILE.MODE,
1658216595 };
16583
16584 // Create the read handle that can be used with overlapped IO ops.
16585 const read_handle = windows.kernel32.CreateNamedPipeW(
16586 pipe_path.ptr,
16587 windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED,
16588 windows.PIPE_TYPE_BYTE,
16589 1,
16590 4096,
16591 4096,
16592 0,
16593 sattr,
16594 );
16595 if (read_handle == windows.INVALID_HANDLE_VALUE) {
16596 switch (windows.GetLastError()) {
16597 else => |err| return windows.unexpectedError(err),
16598 }
16599 }
16600 errdefer posix.close(read_handle);
16601
16602 var sattr_copy = sattr.*;
16603 const write_handle = windows.kernel32.CreateFileW(
16604 pipe_path.ptr,
16605 .{ .GENERIC = .{ .WRITE = true } },
16606 0,
16607 &sattr_copy,
16608 windows.OPEN_EXISTING,
16609 @bitCast(windows.FILE.ATTRIBUTE{ .NORMAL = true }),
16610 null,
16611 );
16612 if (write_handle == windows.INVALID_HANDLE_VALUE) {
16613 switch (windows.GetLastError()) {
16614 else => |err| return windows.unexpectedError(err),
16615 }
16616 }
16617 errdefer posix.close(write_handle);
16618
16619 try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0);
16620
16621 rd.* = read_handle;
16622 wr.* = write_handle;
16596};
16597pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.HANDLE {
16598 const named_pipe_device = try t.getNamedPipeDevice();
16599 const server_handle = server_handle: {
16600 var handle: windows.HANDLE = undefined;
16601 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
16602 const syscall: Syscall = try .start();
16603 while (true) switch (windows.ntdll.NtCreateNamedPipeFile(
16604 &handle,
16605 .{
16606 .SPECIFIC = .{ .FILE_PIPE = .{
16607 .READ_DATA = options.inbound,
16608 .WRITE_DATA = options.outbound,
16609 .WRITE_ATTRIBUTES = true,
16610 } },
16611 .STANDARD = .{ .SYNCHRONIZE = true },
16612 },
16613 &.{
16614 .RootDirectory = named_pipe_device,
16615 .Attributes = options.server.attributes,
16616 },
16617 &io_status_block,
16618 .{ .READ = true, .WRITE = true },
16619 .CREATE,
16620 options.server.mode,
16621 .{ .TYPE = .BYTE_STREAM },
16622 .{ .MODE = .BYTE_STREAM },
16623 .{ .OPERATION = .QUEUE },
16624 options.maximum_instances,
16625 if (options.inbound) options.quota else 0,
16626 if (options.outbound) options.quota else 0,
16627 &options.default_timeout,
16628 )) {
16629 .SUCCESS => break syscall.finish(),
16630 .CANCELLED => {
16631 try syscall.checkCancel();
16632 continue;
16633 },
16634 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
16635 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
16636 else => |status| return syscall.unexpectedNtstatus(status),
16637 };
16638 break :server_handle handle;
16639 };
16640 errdefer windows.CloseHandle(server_handle);
16641 const client_handle = client_handle: {
16642 var handle: windows.HANDLE = undefined;
16643 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
16644 const syscall: Syscall = try .start();
16645 while (true) switch (windows.ntdll.NtOpenFile(
16646 &handle,
16647 .{
16648 .SPECIFIC = .{ .FILE_PIPE = .{
16649 .READ_DATA = options.outbound,
16650 .WRITE_DATA = options.inbound,
16651 .WRITE_ATTRIBUTES = true,
16652 } },
16653 .STANDARD = .{ .SYNCHRONIZE = true },
16654 },
16655 &.{
16656 .RootDirectory = server_handle,
16657 .Attributes = options.client.attributes,
16658 },
16659 &io_status_block,
16660 .{ .READ = true, .WRITE = true },
16661 options.client.mode,
16662 )) {
16663 .SUCCESS => break syscall.finish(),
16664 .CANCELLED => {
16665 try syscall.checkCancel();
16666 continue;
16667 },
16668 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
16669 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
16670 else => |status| return syscall.unexpectedNtstatus(status),
16671 };
16672 break :client_handle handle;
16673 };
16674 errdefer windows.CloseHandle(client_handle);
16675 return .{ server_handle, client_handle };
1662316676}
1662416677
16625var pipe_name_counter = std.atomic.Value(u32).init(1);
16626
1662716678fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
1662816679 const t: *Threaded = @ptrCast(@alignCast(userdata));
16629
1663016680 t.scanEnviron();
16631
16632 const int = try t.environ.zig_progress_handle;
16633
16634 return .{
16635 .handle = switch (@typeInfo(Io.File.Handle)) {
16636 .int => int,
16637 .pointer => @ptrFromInt(int),
16638 else => return error.UnsupportedOperation,
16639 },
16640 .flags = .{ .nonblocking = false },
16641 };
16681 return t.environ.zig_progress_file;
1664216682}
1664316683
1664416684pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {
......@@ -16734,7 +16774,7 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void {
1673416774 // despite the function being documented to always return TRUE
1673516775 // * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG
1673616776 // Therefore, that function is avoided in favor of using the device directly.
16737 const cng_device = try getCngHandle(t);
16777 const cng_device = try getCngDevice(t);
1673816778 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1673916779 var i: usize = 0;
1674016780 const syscall: Syscall = try .start();
lib/std/Io/Threaded/test.zig+10-6
......@@ -181,13 +181,17 @@ test "cancel blocked read from pipe" {
181181 var write_end: Io.File = undefined;
182182 switch (builtin.target.os.tag) {
183183 .wasi => return error.SkipZigTest,
184 .windows => try std.os.windows.CreatePipe(&read_end.handle, &write_end.handle, &.{
185 .nLength = @sizeOf(std.os.windows.SECURITY_ATTRIBUTES),
186 .lpSecurityDescriptor = null,
187 .bInheritHandle = std.os.windows.FALSE,
188 }),
184 .windows => {
185 const pipe = try threaded.windowsCreatePipe(.{
186 .server = .{ .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
187 .client = .{ .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
188 .inbound = true,
189 });
190 read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } };
191 write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } };
192 },
189193 else => {
190 const pipe = try std.Io.Threaded.pipe2(.{});
194 const pipe = try std.Io.Threaded.pipe2(.{ .CLOEXEC = true });
191195 read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } };
192196 write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } };
193197 },
lib/std/Progress.zig+20-17
......@@ -139,7 +139,7 @@ pub const Node = struct {
139139 fn setIpcFd(s: *Storage, fd: Io.File.Handle) void {
140140 const integer: u32 = switch (@typeInfo(Io.File.Handle)) {
141141 .int => @bitCast(fd),
142 .pointer => @intFromPtr(fd),
142 .pointer => @intCast(@intFromPtr(fd)),
143143 else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
144144 };
145145 // `estimated_total_count` max int indicates the special state that
......@@ -342,10 +342,18 @@ pub const Node = struct {
342342 /// Posix-only. Used by `std.process.Child`. Thread-safe.
343343 pub fn setIpcFd(node: Node, fd: Io.File.Handle) void {
344344 const index = node.index.unwrap() orelse return;
345 assert(fd >= 0);
346 assert(fd != posix.STDOUT_FILENO);
347 assert(fd != posix.STDIN_FILENO);
348 assert(fd != posix.STDERR_FILENO);
345 switch (@typeInfo(Io.File.Handle)) {
346 .int => {
347 assert(fd >= 0);
348 assert(fd != posix.STDOUT_FILENO);
349 assert(fd != posix.STDIN_FILENO);
350 assert(fd != posix.STDERR_FILENO);
351 },
352 .pointer => {
353 assert(fd != windows.INVALID_HANDLE_VALUE);
354 },
355 else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
356 }
349357 storageByIndex(index).setIpcFd(fd);
350358 }
351359
......@@ -477,21 +485,18 @@ pub fn start(io: Io, options: Options) Node {
477485 global_progress.refresh_rate_ns = @intCast(options.refresh_rate_ns.toNanoseconds());
478486 global_progress.initial_delay_ns = @intCast(options.initial_delay_ns.toNanoseconds());
479487
480 if (noop_impl)
481 return Node.none;
488 if (noop_impl) return .none;
482489
483490 global_progress.io = io;
484491
485492 if (io.vtable.progressParentFile(io.userdata)) |ipc_file| {
486493 global_progress.update_worker = io.concurrent(ipcThreadRun, .{ io, ipc_file }) catch |err| {
487494 global_progress.start_failure = .{ .spawn_ipc_worker = err };
488 return Node.none;
495 return .none;
489496 };
490497 } else |env_err| switch (env_err) {
491498 error.EnvironmentVariableMissing => {
492 if (options.disable_printing) {
493 return Node.none;
494 }
499 if (options.disable_printing) return .none;
495500 const stderr: Io.File = .stderr();
496501 global_progress.terminal = stderr;
497502 if (stderr.enableAnsiEscapeCodes(io)) |_| {
......@@ -504,14 +509,12 @@ pub fn start(io: Io, options: Options) Node {
504509 } else |err| switch (err) {
505510 error.Canceled => {
506511 io.recancel();
507 return Node.none;
512 return .none;
508513 },
509514 }
510515 }
511516
512 if (global_progress.terminal_mode == .off) {
513 return Node.none;
514 }
517 if (global_progress.terminal_mode == .off) return .none;
515518
516519 if (have_sigwinch) {
517520 const act: posix.Sigaction = .{
......@@ -530,12 +533,12 @@ pub fn start(io: Io, options: Options) Node {
530533 global_progress.update_worker = future;
531534 } else |err| {
532535 global_progress.start_failure = .{ .spawn_update_worker = err };
533 return Node.none;
536 return .none;
534537 }
535538 },
536539 else => |e| {
537540 global_progress.start_failure = .{ .parent_ipc = e };
538 return Node.none;
541 return .none;
539542 },
540543 }
541544
lib/std/Thread.zig+5-1
......@@ -598,7 +598,11 @@ const WindowsThreadImpl = struct {
598598 }
599599
600600 fn join(self: Impl) void {
601 windows.WaitForSingleObjectEx(self.thread.thread_handle, windows.INFINITE, false) catch unreachable;
601 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
602 switch (windows.ntdll.NtWaitForSingleObject(self.thread.thread_handle, windows.FALSE, &infinite_timeout)) {
603 windows.NTSTATUS.WAIT_0 => {},
604 else => |status| windows.unexpectedStatus(status) catch unreachable,
605 }
602606 windows.CloseHandle(self.thread.thread_handle);
603607 assert(self.thread.completion.load(.seq_cst) == .completed);
604608 self.thread.free();
lib/std/mem/Allocator.zig+13-2
......@@ -452,12 +452,23 @@ pub fn dupe(allocator: Allocator, comptime T: type, m: []const T) Error![]T {
452452 return new_buf;
453453}
454454
455/// Deprecated in favor of `dupeSentinel`
455456/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
456457pub fn dupeZ(allocator: Allocator, comptime T: type, m: []const T) Error![:0]T {
458 return allocator.dupeSentinel(T, m, 0);
459}
460
461/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
462pub fn dupeSentinel(
463 allocator: Allocator,
464 comptime T: type,
465 m: []const T,
466 comptime sentinel: T,
467) Error![:sentinel]T {
457468 const new_buf = try allocator.alloc(T, m.len + 1);
458469 @memcpy(new_buf[0..m.len], m);
459 new_buf[m.len] = 0;
460 return new_buf[0..m.len :0];
470 new_buf[m.len] = sentinel;
471 return new_buf[0..m.len :sentinel];
461472}
462473
463474/// An allocator that always fails to allocate.
lib/std/os/windows.zig+19-250
......@@ -521,7 +521,7 @@ pub const FILE = struct {
521521 _,
522522
523523 pub const VALID_FLAGS: @This() = @enumFromInt(0b11);
524 } = .ASYNCHRONOUS,
524 },
525525 /// The file being opened must not be a directory file or this call
526526 /// fails. The file object being opened can represent a data file, a
527527 /// logical, virtual, or physical device, or a volume.
......@@ -2324,12 +2324,12 @@ pub fn GetProcessHeap() ?*HEAP {
23242324// ref: um/winternl.h
23252325
23262326pub const OBJECT_ATTRIBUTES = extern struct {
2327 Length: ULONG,
2328 RootDirectory: ?HANDLE,
2329 ObjectName: ?*UNICODE_STRING,
2330 Attributes: ATTRIBUTES,
2331 SecurityDescriptor: ?*anyopaque,
2332 SecurityQualityOfService: ?*anyopaque,
2327 Length: ULONG = @sizeOf(OBJECT_ATTRIBUTES),
2328 RootDirectory: ?HANDLE = null,
2329 ObjectName: ?*UNICODE_STRING = @constCast(&UNICODE_STRING.empty),
2330 Attributes: ATTRIBUTES = .{},
2331 SecurityDescriptor: ?*anyopaque = null,
2332 SecurityQualityOfService: ?*anyopaque = null,
23332333
23342334 // Valid values for the Attributes field
23352335 pub const ATTRIBUTES = packed struct(ULONG) {
......@@ -2420,14 +2420,10 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
24202420 .Buffer = @constCast(sub_path_w.ptr),
24212421 };
24222422 const attr: OBJECT_ATTRIBUTES = .{
2423 .Length = @sizeOf(OBJECT_ATTRIBUTES),
24242423 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir,
2425 .Attributes = .{
2426 .INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false,
2427 },
2424 .Attributes = .{ .INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false },
24282425 .ObjectName = &nt_name,
24292426 .SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null,
2430 .SecurityQualityOfService = null,
24312427 };
24322428 var io: IO_STATUS_BLOCK = undefined;
24332429 while (true) {
......@@ -2475,7 +2471,8 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
24752471 // call has failed. There is not really a sane way to handle
24762472 // this other than retrying the creation after the OS finishes
24772473 // the deletion.
2478 _ = kernel32.SleepEx(1, TRUE);
2474 const delay_one_ms: LARGE_INTEGER = -(std.time.ns_per_ms / 100);
2475 _ = ntdll.NtDelayExecution(TRUE, &delay_one_ms);
24792476 continue;
24802477 },
24812478 .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
......@@ -2506,151 +2503,6 @@ pub fn GetCurrentThreadId() DWORD {
25062503pub fn GetLastError() Win32Error {
25072504 return @enumFromInt(teb().LastErrorValue);
25082505}
2509
2510pub const CreatePipeError = error{ Unexpected, SystemResources };
2511
2512var npfs: ?HANDLE = null;
2513
2514/// A Zig wrapper around `NtCreateNamedPipeFile` and `NtCreateFile` syscalls.
2515/// It implements similar behavior to `CreatePipe` and is meant to serve
2516/// as a direct substitute for that call.
2517pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void {
2518 // Up to NT 5.2 (Windows XP/Server 2003), `CreatePipe` would generate a pipe similar to:
2519 //
2520 // \??\pipe\Win32Pipes.{pid}.{count}
2521 //
2522 // where `pid` is the process id and count is a incrementing counter.
2523 // The implementation was changed after NT 6.0 (Vista) to open a handle to the Named Pipe File System
2524 // and use that as the root directory for `NtCreateNamedPipeFile`.
2525 // This object is visible under the NPFS but has no filename attached to it.
2526 //
2527 // This implementation replicates how `CreatePipe` works in modern Windows versions.
2528 const opt_dev_handle = @atomicLoad(?HANDLE, &npfs, .seq_cst);
2529 const dev_handle = opt_dev_handle orelse blk: {
2530 const str = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\NamedPipe\\");
2531 const len: u16 = @truncate(str.len * @sizeOf(u16));
2532 const name: UNICODE_STRING = .{
2533 .Length = len,
2534 .MaximumLength = len,
2535 .Buffer = @ptrCast(@constCast(str)),
2536 };
2537 const attrs: OBJECT_ATTRIBUTES = .{
2538 .ObjectName = @constCast(&name),
2539 .Length = @sizeOf(OBJECT_ATTRIBUTES),
2540 .RootDirectory = null,
2541 .Attributes = .{},
2542 .SecurityDescriptor = null,
2543 .SecurityQualityOfService = null,
2544 };
2545
2546 var iosb: IO_STATUS_BLOCK = undefined;
2547 var handle: HANDLE = undefined;
2548 switch (ntdll.NtCreateFile(
2549 &handle,
2550 .{
2551 .STANDARD = .{ .SYNCHRONIZE = true },
2552 .GENERIC = .{ .READ = true },
2553 },
2554 @constCast(&attrs),
2555 &iosb,
2556 null,
2557 .{},
2558 .VALID_FLAGS,
2559 .OPEN,
2560 .{ .IO = .SYNCHRONOUS_NONALERT },
2561 null,
2562 0,
2563 )) {
2564 .SUCCESS => {},
2565 // Judging from the ReactOS sources this is technically possible.
2566 .INSUFFICIENT_RESOURCES => return error.SystemResources,
2567 .INVALID_PARAMETER => unreachable,
2568 else => |e| return unexpectedStatus(e),
2569 }
2570 if (@cmpxchgStrong(?HANDLE, &npfs, null, handle, .seq_cst, .seq_cst)) |xchg| {
2571 CloseHandle(handle);
2572 break :blk xchg.?;
2573 } else break :blk handle;
2574 };
2575
2576 const name: UNICODE_STRING = .{ .Buffer = null, .Length = 0, .MaximumLength = 0 };
2577 var attrs: OBJECT_ATTRIBUTES = .{
2578 .ObjectName = @constCast(&name),
2579 .Length = @sizeOf(OBJECT_ATTRIBUTES),
2580 .RootDirectory = dev_handle,
2581 .Attributes = .{ .INHERIT = sattr.bInheritHandle != FALSE },
2582 .SecurityDescriptor = sattr.lpSecurityDescriptor,
2583 .SecurityQualityOfService = null,
2584 };
2585
2586 // 120 second relative timeout in 100ns units.
2587 const default_timeout: LARGE_INTEGER = (-120 * std.time.ns_per_s) / 100;
2588 var iosb: IO_STATUS_BLOCK = undefined;
2589 var read: HANDLE = undefined;
2590 switch (ntdll.NtCreateNamedPipeFile(
2591 &read,
2592 .{
2593 .SPECIFIC = .{ .FILE_PIPE = .{
2594 .WRITE_ATTRIBUTES = true,
2595 } },
2596 .STANDARD = .{ .SYNCHRONIZE = true },
2597 .GENERIC = .{ .READ = true },
2598 },
2599 &attrs,
2600 &iosb,
2601 .{ .READ = true, .WRITE = true },
2602 .CREATE,
2603 .{ .IO = .SYNCHRONOUS_NONALERT },
2604 .{ .TYPE = .BYTE_STREAM },
2605 .{ .MODE = .BYTE_STREAM },
2606 .{ .OPERATION = .QUEUE },
2607 1,
2608 4096,
2609 4096,
2610 @constCast(&default_timeout),
2611 )) {
2612 .SUCCESS => {},
2613 .INVALID_PARAMETER => unreachable,
2614 .INSUFFICIENT_RESOURCES => return error.SystemResources,
2615 else => |e| return unexpectedStatus(e),
2616 }
2617 errdefer CloseHandle(read);
2618
2619 attrs.RootDirectory = read;
2620
2621 var write: HANDLE = undefined;
2622 switch (ntdll.NtCreateFile(
2623 &write,
2624 .{
2625 .SPECIFIC = .{ .FILE_PIPE = .{
2626 .READ_ATTRIBUTES = true,
2627 } },
2628 .STANDARD = .{ .SYNCHRONIZE = true },
2629 .GENERIC = .{ .WRITE = true },
2630 },
2631 &attrs,
2632 &iosb,
2633 null,
2634 .{},
2635 .VALID_FLAGS,
2636 .OPEN,
2637 .{
2638 .IO = .SYNCHRONOUS_NONALERT,
2639 .NON_DIRECTORY_FILE = true,
2640 },
2641 null,
2642 0,
2643 )) {
2644 .SUCCESS => {},
2645 .INVALID_PARAMETER => unreachable,
2646 .INSUFFICIENT_RESOURCES => return error.SystemResources,
2647 else => |e| return unexpectedStatus(e),
2648 }
2649
2650 rd.* = read;
2651 wr.* = write;
2652}
2653
26542506/// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls.
26552507/// It implements similar behavior to `DeviceIoControl` and is meant to serve
26562508/// as a direct substitute for that call.
......@@ -2707,66 +2559,6 @@ pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWOR
27072559 return bytes;
27082560}
27092561
2710pub const SetHandleInformationError = error{Unexpected};
2711
2712pub fn SetHandleInformation(h: HANDLE, mask: DWORD, flags: DWORD) SetHandleInformationError!void {
2713 if (kernel32.SetHandleInformation(h, mask, flags) == 0) {
2714 switch (GetLastError()) {
2715 else => |err| return unexpectedError(err),
2716 }
2717 }
2718}
2719
2720pub const WaitForSingleObjectError = error{
2721 WaitAbandoned,
2722 WaitTimeOut,
2723 Unexpected,
2724};
2725
2726pub fn WaitForSingleObject(handle: HANDLE, milliseconds: DWORD) WaitForSingleObjectError!void {
2727 return WaitForSingleObjectEx(handle, milliseconds, false);
2728}
2729
2730pub fn WaitForSingleObjectEx(handle: HANDLE, milliseconds: DWORD, alertable: bool) WaitForSingleObjectError!void {
2731 switch (kernel32.WaitForSingleObjectEx(handle, milliseconds, @intFromBool(alertable))) {
2732 WAIT_ABANDONED => return error.WaitAbandoned,
2733 WAIT_OBJECT_0 => return,
2734 WAIT_TIMEOUT => return error.WaitTimeOut,
2735 WAIT_FAILED => switch (GetLastError()) {
2736 else => |err| return unexpectedError(err),
2737 },
2738 else => return error.Unexpected,
2739 }
2740}
2741
2742pub fn WaitForMultipleObjectsEx(handles: []const HANDLE, waitAll: bool, milliseconds: DWORD, alertable: bool) !u32 {
2743 assert(handles.len > 0 and handles.len <= MAXIMUM_WAIT_OBJECTS);
2744 const nCount: DWORD = @as(DWORD, @intCast(handles.len));
2745 switch (kernel32.WaitForMultipleObjectsEx(
2746 nCount,
2747 handles.ptr,
2748 @intFromBool(waitAll),
2749 milliseconds,
2750 @intFromBool(alertable),
2751 )) {
2752 WAIT_OBJECT_0...WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS => |n| {
2753 const handle_index = n - WAIT_OBJECT_0;
2754 assert(handle_index < nCount);
2755 return handle_index;
2756 },
2757 WAIT_ABANDONED_0...WAIT_ABANDONED_0 + MAXIMUM_WAIT_OBJECTS => |n| {
2758 const handle_index = n - WAIT_ABANDONED_0;
2759 assert(handle_index < nCount);
2760 return error.WaitAbandoned;
2761 },
2762 WAIT_TIMEOUT => return error.WaitTimeOut,
2763 WAIT_FAILED => switch (GetLastError()) {
2764 else => |err| return unexpectedError(err),
2765 },
2766 else => return error.Unexpected,
2767 }
2768}
2769
27702562pub const CreateIoCompletionPortError = error{Unexpected};
27712563
27722564pub fn CreateIoCompletionPort(
......@@ -2878,21 +2670,6 @@ pub fn CloseHandle(hObject: HANDLE) void {
28782670 assert(ntdll.NtClose(hObject) == .SUCCESS);
28792671}
28802672
2881pub const GetStdHandleError = error{
2882 NoStandardHandleAttached,
2883 Unexpected,
2884};
2885
2886pub fn GetStdHandle(handle_id: DWORD) GetStdHandleError!HANDLE {
2887 const handle = kernel32.GetStdHandle(handle_id) orelse return error.NoStandardHandleAttached;
2888 if (handle == INVALID_HANDLE_VALUE) {
2889 switch (GetLastError()) {
2890 else => |err| return unexpectedError(err),
2891 }
2892 }
2893 return handle;
2894}
2895
28962673pub const QueryObjectNameError = error{
28972674 AccessDenied,
28982675 InvalidHandle,
......@@ -3545,6 +3322,12 @@ pub fn nanoSecondsToFileTime(ns: Io.Timestamp) FILETIME {
35453322 };
35463323}
35473324
3325/// Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a
3326/// redundant copy of the uppercase data.
3327pub inline fn toUpperWtf16(c: u16) u16 {
3328 return (if (builtin.os.tag != .windows or @inComptime()) nls.upcaseW else ntdll.RtlUpcaseUnicodeChar)(c);
3329}
3330
35483331/// Compares two WTF16 strings using the equivalent functionality of
35493332/// `RtlEqualUnicodeString` (with case insensitive comparison enabled).
35503333/// This function can be called on any target.
......@@ -3598,19 +3381,12 @@ pub fn eqlIgnoreCaseWtf8(a: []const u8, b: []const u8) bool {
35983381 var a_wtf8_it = std.unicode.Wtf8View.initUnchecked(a).iterator();
35993382 var b_wtf8_it = std.unicode.Wtf8View.initUnchecked(b).iterator();
36003383
3601 // Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a
3602 // redundant copy of the uppercase data.
3603 const upcaseImpl = switch (builtin.os.tag) {
3604 .windows => if (@inComptime()) nls.upcaseW else ntdll.RtlUpcaseUnicodeChar,
3605 else => nls.upcaseW,
3606 };
3607
36083384 while (true) {
36093385 const a_cp = a_wtf8_it.nextCodepoint() orelse break;
36103386 const b_cp = b_wtf8_it.nextCodepoint() orelse return false;
36113387
36123388 if (a_cp <= maxInt(u16) and b_cp <= maxInt(u16)) {
3613 if (a_cp != b_cp and upcaseImpl(@intCast(a_cp)) != upcaseImpl(@intCast(b_cp))) {
3389 if (a_cp != b_cp and toUpperWtf16(@intCast(a_cp)) != toUpperWtf16(@intCast(b_cp))) {
36143390 return false;
36153391 }
36163392 } else if (a_cp != b_cp) {
......@@ -4098,15 +3874,6 @@ pub const Win32Error = @import("windows/win32error.zig").Win32Error;
40983874pub const LANG = @import("windows/lang.zig");
40993875pub const SUBLANG = @import("windows/sublang.zig");
41003876
4101/// The standard input device. Initially, this is the console input buffer, CONIN$.
4102pub const STD_INPUT_HANDLE = maxInt(DWORD) - 10 + 1;
4103
4104/// The standard output device. Initially, this is the active console screen buffer, CONOUT$.
4105pub const STD_OUTPUT_HANDLE = maxInt(DWORD) - 11 + 1;
4106
4107/// The standard error device. Initially, this is the active console screen buffer, CONOUT$.
4108pub const STD_ERROR_HANDLE = maxInt(DWORD) - 12 + 1;
4109
41103877pub const BOOL = c_int;
41113878pub const BOOLEAN = BYTE;
41123879pub const BYTE = u8;
......@@ -5244,6 +5011,8 @@ pub const UNICODE_STRING = extern struct {
52445011 Length: c_ushort,
52455012 MaximumLength: c_ushort,
52465013 Buffer: ?[*]WCHAR,
5014
5015 pub const empty: UNICODE_STRING = .{ .Length = 0, .MaximumLength = 0, .Buffer = null };
52475016};
52485017
52495018pub const ACTIVATION_CONTEXT_DATA = opaque {};
lib/std/os/windows/kernel32.zig-108
......@@ -12,8 +12,6 @@ const FILETIME = windows.FILETIME;
1212const HANDLE = windows.HANDLE;
1313const HANDLER_ROUTINE = windows.HANDLER_ROUTINE;
1414const HMODULE = windows.HMODULE;
15const INIT_ONCE = windows.INIT_ONCE;
16const INIT_ONCE_FN = windows.INIT_ONCE_FN;
1715const LARGE_INTEGER = windows.LARGE_INTEGER;
1816const LPCSTR = windows.LPCSTR;
1917const LPCVOID = windows.LPCVOID;
......@@ -24,7 +22,6 @@ const LPWSTR = windows.LPWSTR;
2422const MODULEENTRY32 = windows.MODULEENTRY32;
2523const OVERLAPPED = windows.OVERLAPPED;
2624const OVERLAPPED_ENTRY = windows.OVERLAPPED_ENTRY;
27const PMEMORY_BASIC_INFORMATION = windows.PMEMORY_BASIC_INFORMATION;
2825const PROCESS_INFORMATION = windows.PROCESS_INFORMATION;
2926const SECURITY_ATTRIBUTES = windows.SECURITY_ATTRIBUTES;
3027const SIZE_T = windows.SIZE_T;
......@@ -37,7 +34,6 @@ const ULONG = windows.ULONG;
3734const ULONG_PTR = windows.ULONG_PTR;
3835const va_list = windows.va_list;
3936const WCHAR = windows.WCHAR;
40const WIN32_FIND_DATAW = windows.WIN32_FIND_DATAW;
4137const Win32Error = windows.Win32Error;
4238const WORD = windows.WORD;
4339
......@@ -59,39 +55,6 @@ pub extern "kernel32" fn CancelIo(
5955 hFile: HANDLE,
6056) callconv(.winapi) BOOL;
6157
62// TODO: Wrapper around NtCancelIoFileEx.
63pub extern "kernel32" fn CancelIoEx(
64 hFile: HANDLE,
65 lpOverlapped: ?*OVERLAPPED,
66) callconv(.winapi) BOOL;
67
68pub extern "kernel32" fn CreateFileW(
69 lpFileName: LPCWSTR,
70 dwDesiredAccess: ACCESS_MASK,
71 dwShareMode: DWORD,
72 lpSecurityAttributes: ?*SECURITY_ATTRIBUTES,
73 dwCreationDisposition: DWORD,
74 dwFlagsAndAttributes: DWORD,
75 hTemplateFile: ?HANDLE,
76) callconv(.winapi) HANDLE;
77
78// TODO A bunch of logic around NtCreateNamedPipe
79pub extern "kernel32" fn CreateNamedPipeW(
80 lpName: LPCWSTR,
81 dwOpenMode: DWORD,
82 dwPipeMode: DWORD,
83 nMaxInstances: DWORD,
84 nOutBufferSize: DWORD,
85 nInBufferSize: DWORD,
86 nDefaultTimeOut: DWORD,
87 lpSecurityAttributes: ?*const SECURITY_ATTRIBUTES,
88) callconv(.winapi) HANDLE;
89
90// TODO: Matches `STD_*_HANDLE` to peb().ProcessParameters.Standard*
91pub extern "kernel32" fn GetStdHandle(
92 nStdHandle: DWORD,
93) callconv(.winapi) ?HANDLE;
94
9558// TODO: Wrapper around NtSetInformationFile + `FILE_POSITION_INFORMATION`.
9659// `FILE_STANDARD_INFORMATION` is also used if dwMoveMethod is `FILE_END`
9760pub extern "kernel32" fn SetFilePointerEx(
......@@ -117,11 +80,6 @@ pub extern "kernel32" fn WriteFile(
11780 in_out_lpOverlapped: ?*OVERLAPPED,
11881) callconv(.winapi) BOOL;
11982
120// TODO: Wrapper around GetStdHandle + NtFlushBuffersFile.
121pub extern "kernel32" fn FlushFileBuffers(
122 hFile: HANDLE,
123) callconv(.winapi) BOOL;
124
12583// TODO: Wrapper around NtSetInformationFile + `FILE_IO_COMPLETION_NOTIFICATION_INFORMATION`.
12684pub extern "kernel32" fn SetFileCompletionNotificationModes(
12785 FileHandle: HANDLE,
......@@ -143,24 +101,6 @@ pub extern "kernel32" fn GetSystemDirectoryW(
143101
144102// I/O - Kernel Objects
145103
146// TODO: Wrapper around GetStdHandle + NtDuplicateObject.
147pub extern "kernel32" fn DuplicateHandle(
148 hSourceProcessHandle: HANDLE,
149 hSourceHandle: HANDLE,
150 hTargetProcessHandle: HANDLE,
151 lpTargetHandle: *HANDLE,
152 dwDesiredAccess: ACCESS_MASK,
153 bInheritHandle: BOOL,
154 dwOptions: DWORD,
155) callconv(.winapi) BOOL;
156
157// TODO: Wrapper around GetStdHandle + NtQueryObject + NtSetInformationObject with .ObjectHandleFlagInformation.
158pub extern "kernel32" fn SetHandleInformation(
159 hObject: HANDLE,
160 dwMask: DWORD,
161 dwFlags: DWORD,
162) callconv(.winapi) BOOL;
163
164104// TODO: Wrapper around NtRemoveIoCompletion.
165105pub extern "kernel32" fn GetQueuedCompletionStatus(
166106 CompletionPort: HANDLE,
......@@ -210,37 +150,6 @@ pub extern "kernel32" fn TerminateProcess(
210150 uExitCode: UINT,
211151) callconv(.winapi) BOOL;
212152
213// TODO: WaitForSingleObjectEx with bAlertable=false.
214pub extern "kernel32" fn WaitForSingleObject(
215 hHandle: HANDLE,
216 dwMilliseconds: DWORD,
217) callconv(.winapi) DWORD;
218
219// TODO: Wrapper for GetStdHandle + NtWaitForSingleObject.
220// Sets up an activation context before calling NtWaitForSingleObject.
221pub extern "kernel32" fn WaitForSingleObjectEx(
222 hHandle: HANDLE,
223 dwMilliseconds: DWORD,
224 bAlertable: BOOL,
225) callconv(.winapi) DWORD;
226
227// TODO: WaitForMultipleObjectsEx with alertable=false
228pub extern "kernel32" fn WaitForMultipleObjects(
229 nCount: DWORD,
230 lpHandle: [*]const HANDLE,
231 bWaitAll: BOOL,
232 dwMilliseconds: DWORD,
233) callconv(.winapi) DWORD;
234
235// TODO: Wrapper around NtWaitForMultipleObjects.
236pub extern "kernel32" fn WaitForMultipleObjectsEx(
237 nCount: DWORD,
238 lpHandle: [*]const HANDLE,
239 bWaitAll: BOOL,
240 dwMilliseconds: DWORD,
241 bAlertable: BOOL,
242) callconv(.winapi) DWORD;
243
244153// Process Management
245154
246155pub extern "kernel32" fn CreateProcessW(
......@@ -256,12 +165,6 @@ pub extern "kernel32" fn CreateProcessW(
256165 lpProcessInformation: *PROCESS_INFORMATION,
257166) callconv(.winapi) BOOL;
258167
259// TODO: implement via ntdll instead
260pub extern "kernel32" fn SleepEx(
261 dwMilliseconds: DWORD,
262 bAlertable: BOOL,
263) callconv(.winapi) DWORD;
264
265168// TODO: Wrapper around NtQueryInformationProcess with `PROCESS_BASIC_INFORMATION`.
266169pub extern "kernel32" fn GetExitCodeProcess(
267170 hProcess: HANDLE,
......@@ -436,14 +339,3 @@ pub extern "kernel32" fn FormatMessageW(
436339
437340// TODO: Getter for teb().LastErrorValue.
438341pub extern "kernel32" fn GetLastError() callconv(.winapi) Win32Error;
439
440// TODO: Wrapper around RtlSetLastWin32Error.
441pub extern "kernel32" fn SetLastError(
442 dwErrCode: Win32Error,
443) callconv(.winapi) void;
444
445// Everything Else
446
447pub extern "kernel32" fn GetSystemInfo(
448 lpSystemInfo: *SYSTEM_INFO,
449) callconv(.winapi) void;
lib/std/os/windows/ntdll.zig+9-4
......@@ -407,6 +407,11 @@ pub extern "ntdll" fn NtCreateNamedPipeFile(
407407 DefaultTimeout: ?*const LARGE_INTEGER,
408408) callconv(.winapi) NTSTATUS;
409409
410pub extern "ntdll" fn NtFlushBuffersFile(
411 FileHandle: HANDLE,
412 IoStatusBlock: *IO_STATUS_BLOCK,
413) callconv(.winapi) NTSTATUS;
414
410415pub extern "ntdll" fn NtMapViewOfSection(
411416 SectionHandle: HANDLE,
412417 ProcessHandle: HANDLE,
......@@ -590,7 +595,7 @@ pub extern "ntdll" fn NtOpenThread(
590595
591596pub extern "ntdll" fn NtCancelSynchronousIoFile(
592597 ThreadHandle: HANDLE,
593 RequestToCancel: ?*IO_STATUS_BLOCK,
598 IoRequestToCancel: ?*IO_STATUS_BLOCK,
594599 IoStatusBlock: *IO_STATUS_BLOCK,
595600) callconv(.winapi) NTSTATUS;
596601
......@@ -606,13 +611,13 @@ pub extern "ntdll" fn NtDelayExecution(
606611 DelayInterval: *const LARGE_INTEGER,
607612) callconv(.winapi) NTSTATUS;
608613
609pub extern "ntdll" fn NtCancelIoFileEx(
614pub extern "ntdll" fn NtCancelIoFile(
610615 FileHandle: HANDLE,
611 IoRequestToCancel: *const IO_STATUS_BLOCK,
612616 IoStatusBlock: *IO_STATUS_BLOCK,
613617) callconv(.winapi) NTSTATUS;
614618
615pub extern "ntdll" fn NtCancelIoFile(
619pub extern "ntdll" fn NtCancelIoFileEx(
616620 FileHandle: HANDLE,
621 IoRequestToCancel: *const IO_STATUS_BLOCK,
617622 IoStatusBlock: *IO_STATUS_BLOCK,
618623) callconv(.winapi) NTSTATUS;
lib/std/process/Environ.zig+438-272
......@@ -4,7 +4,7 @@ const builtin = @import("builtin");
44const native_os = builtin.os.tag;
55
66const std = @import("../std.zig");
7const Allocator = std.mem.Allocator;
7const Allocator = mem.Allocator;
88const assert = std.debug.assert;
99const testing = std.testing;
1010const unicode = std.unicode;
......@@ -14,12 +14,7 @@ const mem = std.mem;
1414/// Unmodified, unprocessed data provided by the operating system.
1515block: Block,
1616
17pub const empty: Environ = .{
18 .block = switch (Block) {
19 void => {},
20 else => &.{},
21 },
22};
17pub const empty: Environ = .{ .block = .empty };
2318
2419/// On WASI without libc, this is `void` because the environment has to be
2520/// queried and heap-allocated at runtime.
......@@ -28,13 +23,65 @@ pub const empty: Environ = .{
2823/// is modified, so a long-lived pointer cannot be used. Therefore, on this
2924/// operating system `void` is also used.
3025pub const Block = switch (native_os) {
31 .windows => void,
26 .windows => GlobalBlock,
3227 .wasi => switch (builtin.link_libc) {
33 false => void,
34 true => [:null]const ?[*:0]const u8,
28 false => GlobalBlock,
29 true => PosixBlock,
3530 },
36 .freestanding, .other => void,
37 else => [:null]const ?[*:0]const u8,
31 .freestanding, .other => GlobalBlock,
32 else => PosixBlock,
33};
34
35pub const GlobalBlock = struct {
36 use_global: bool,
37
38 pub const empty: GlobalBlock = .{ .use_global = false };
39 pub const global: GlobalBlock = .{ .use_global = true };
40
41 pub fn deinit(_: GlobalBlock, _: Allocator) void {}
42};
43
44pub const PosixBlock = struct {
45 slice: [:null]const ?[*:0]const u8,
46
47 pub const empty: PosixBlock = .{ .slice = &.{} };
48
49 pub fn deinit(block: PosixBlock, gpa: Allocator) void {
50 for (block.slice) |entry| gpa.free(mem.span(entry.?));
51 gpa.free(block.slice);
52 }
53
54 pub const View = struct {
55 slice: []const [*:0]const u8,
56
57 pub fn isEmpty(v: View) bool {
58 return v.slice.len == 0;
59 }
60 };
61 pub fn view(block: PosixBlock) View {
62 return .{ .slice = @ptrCast(block.slice) };
63 }
64};
65
66pub const WindowsBlock = struct {
67 slice: [:0]const u16,
68
69 pub const empty: WindowsBlock = .{ .slice = &.{0} };
70
71 pub fn deinit(block: WindowsBlock, gpa: Allocator) void {
72 gpa.free(block.slice);
73 }
74
75 pub const View = struct {
76 ptr: [*:0]const u16,
77
78 pub fn isEmpty(v: View) bool {
79 return v.ptr[0] == 0;
80 }
81 };
82 pub fn view(block: WindowsBlock) View {
83 return .{ .ptr = block.slice.ptr };
84 }
3885};
3986
4087pub const Map = struct {
......@@ -46,47 +93,60 @@ pub const Map = struct {
4693 pub const Size = usize;
4794
4895 pub const EnvNameHashContext = struct {
49 fn upcase(c: u21) u21 {
50 if (c <= std.math.maxInt(u16))
51 return std.os.windows.ntdll.RtlUpcaseUnicodeChar(@as(u16, @intCast(c)));
52 return c;
53 }
54
5596 pub fn hash(self: @This(), s: []const u8) u32 {
5697 _ = self;
57 if (native_os == .windows) {
58 var h = std.hash.Wyhash.init(0);
59 var it = unicode.Wtf8View.initUnchecked(s).iterator();
60 while (it.nextCodepoint()) |cp| {
61 const cp_upper = upcase(cp);
62 h.update(&[_]u8{
63 @as(u8, @intCast((cp_upper >> 16) & 0xff)),
64 @as(u8, @intCast((cp_upper >> 8) & 0xff)),
65 @as(u8, @intCast((cp_upper >> 0) & 0xff)),
66 });
67 }
68 return @truncate(h.final());
98 switch (native_os) {
99 else => return std.array_hash_map.hashString(s),
100 .windows => {
101 var h = std.hash.Wyhash.init(0);
102 var it = unicode.Wtf8View.initUnchecked(s).iterator();
103 while (it.nextCodepoint()) |cp| {
104 const cp_upper = if (std.math.cast(u16, cp)) |wtf16|
105 std.os.windows.toUpperWtf16(wtf16)
106 else
107 cp;
108 h.update(&[_]u8{
109 @truncate(cp_upper >> 0),
110 @truncate(cp_upper >> 8),
111 @truncate(cp_upper >> 16),
112 });
113 }
114 return @truncate(h.final());
115 },
69116 }
70 return std.array_hash_map.hashString(s);
71117 }
72118
73119 pub fn eql(self: @This(), a: []const u8, b: []const u8, b_index: usize) bool {
74120 _ = self;
75121 _ = b_index;
76 if (native_os == .windows) {
77 var it_a = unicode.Wtf8View.initUnchecked(a).iterator();
78 var it_b = unicode.Wtf8View.initUnchecked(b).iterator();
79 while (true) {
80 const c_a = it_a.nextCodepoint() orelse break;
81 const c_b = it_b.nextCodepoint() orelse return false;
82 if (upcase(c_a) != upcase(c_b))
83 return false;
84 }
85 return if (it_b.nextCodepoint()) |_| false else true;
86 }
87 return std.array_hash_map.eqlString(a, b);
122 return eqlKeys(a, b);
88123 }
89124 };
125 fn eqlKeys(a: []const u8, b: []const u8) bool {
126 return switch (native_os) {
127 else => std.array_hash_map.eqlString(a, b),
128 .windows => std.os.windows.eqlIgnoreCaseWtf8(a, b),
129 };
130 }
131
132 pub fn validateKey(key: []const u8) bool {
133 switch (native_os) {
134 else => return key.len > 0 and mem.findAny(u8, key, &.{ 0, '=' }) == null,
135 .windows => {
136 if (!unicode.wtf8ValidateSlice(key)) return false;
137 var it = unicode.Wtf8View.initUnchecked(key).iterator();
138 switch (it.nextCodepoint() orelse return false) {
139 0 => return false,
140 else => {},
141 }
142 while (it.nextCodepoint()) |cp| switch (cp) {
143 0, '=' => return false,
144 else => {},
145 };
146 return true;
147 },
148 }
149 }
90150
91151 /// Create a Map backed by a specific allocator.
92152 /// That allocator will be used for both backing allocations
......@@ -99,30 +159,71 @@ pub const Map = struct {
99159 /// of the stored keys and values.
100160 pub fn deinit(self: *Map) void {
101161 const gpa = self.allocator;
102 var it = self.array_hash_map.iterator();
103 while (it.next()) |entry| {
104 gpa.free(entry.key_ptr.*);
105 gpa.free(entry.value_ptr.*);
106 }
162 for (self.keys()) |key| gpa.free(key);
163 for (self.values()) |value| gpa.free(value);
107164 self.array_hash_map.deinit(gpa);
108165 self.* = undefined;
109166 }
110167
111 pub fn keys(m: *const Map) [][]const u8 {
112 return m.array_hash_map.keys();
168 pub fn keys(map: *const Map) [][]const u8 {
169 return map.array_hash_map.keys();
170 }
171
172 pub fn values(map: *const Map) [][]const u8 {
173 return map.array_hash_map.values();
174 }
175
176 pub fn putPosixBlock(map: *Map, view: PosixBlock.View) Allocator.Error!void {
177 for (view.slice) |entry| {
178 var entry_i: usize = 0;
179 while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {}
180 const key = entry[0..entry_i];
181
182 var end_i: usize = entry_i;
183 while (entry[end_i] != 0) : (end_i += 1) {}
184 const value = entry[entry_i + 1 .. end_i];
185
186 try map.put(key, value);
187 }
113188 }
114189
115 pub fn values(m: *const Map) [][]const u8 {
116 return m.array_hash_map.values();
190 pub fn putWindowsBlock(map: *Map, view: WindowsBlock.View) Allocator.Error!void {
191 var i: usize = 0;
192 while (view.ptr[i] != 0) {
193 const key_start = i;
194
195 // There are some special environment variables that start with =,
196 // so we need a special case to not treat = as a key/value separator
197 // if it's the first character.
198 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
199 if (view.ptr[key_start] == '=') i += 1;
200
201 while (view.ptr[i] != 0 and view.ptr[i] != '=') : (i += 1) {}
202 const key_w = view.ptr[key_start..i];
203 const key = try unicode.wtf16LeToWtf8Alloc(map.allocator, key_w);
204 errdefer map.allocator.free(key);
205
206 if (view.ptr[i] == '=') i += 1;
207
208 const value_start = i;
209 while (view.ptr[i] != 0) : (i += 1) {}
210 const value_w = view.ptr[value_start..i];
211 const value = try unicode.wtf16LeToWtf8Alloc(map.allocator, value_w);
212 errdefer map.allocator.free(value);
213
214 i += 1; // skip over null byte
215
216 try map.putMove(key, value);
217 }
117218 }
118219
119220 /// Same as `put` but the key and value become owned by the Map rather
120221 /// than being copied.
121222 /// If `putMove` fails, the ownership of key and value does not transfer.
122223 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
123 pub fn putMove(self: *Map, key: []u8, value: []u8) !void {
224 pub fn putMove(self: *Map, key: []u8, value: []u8) Allocator.Error!void {
225 assert(validateKey(key));
124226 const gpa = self.allocator;
125 assert(unicode.wtf8ValidateSlice(key));
126227 const get_or_put = try self.array_hash_map.getOrPut(gpa, key);
127228 if (get_or_put.found_existing) {
128229 gpa.free(get_or_put.key_ptr.*);
......@@ -134,8 +235,8 @@ pub const Map = struct {
134235
135236 /// `key` and `value` are copied into the Map.
136237 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
137 pub fn put(self: *Map, key: []const u8, value: []const u8) !void {
138 assert(unicode.wtf8ValidateSlice(key));
238 pub fn put(self: *Map, key: []const u8, value: []const u8) Allocator.Error!void {
239 assert(validateKey(key));
139240 const gpa = self.allocator;
140241 const value_copy = try gpa.dupe(u8, value);
141242 errdefer gpa.free(value_copy);
......@@ -155,7 +256,7 @@ pub const Map = struct {
155256 /// The returned pointer is invalidated if the map resizes.
156257 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
157258 pub fn getPtr(self: Map, key: []const u8) ?*[]const u8 {
158 assert(unicode.wtf8ValidateSlice(key));
259 assert(validateKey(key));
159260 return self.array_hash_map.getPtr(key);
160261 }
161262
......@@ -164,11 +265,12 @@ pub const Map = struct {
164265 /// key is removed from the map.
165266 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
166267 pub fn get(self: Map, key: []const u8) ?[]const u8 {
167 assert(unicode.wtf8ValidateSlice(key));
268 assert(validateKey(key));
168269 return self.array_hash_map.get(key);
169270 }
170271
171272 pub fn contains(m: *const Map, key: []const u8) bool {
273 assert(validateKey(key));
172274 return m.array_hash_map.contains(key);
173275 }
174276
......@@ -181,7 +283,7 @@ pub const Map = struct {
181283 /// This invalidates the value returned by get() for this key.
182284 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
183285 pub fn swapRemove(self: *Map, key: []const u8) bool {
184 assert(unicode.wtf8ValidateSlice(key));
286 assert(validateKey(key));
185287 const kv = self.array_hash_map.fetchSwapRemove(key) orelse return false;
186288 const gpa = self.allocator;
187289 gpa.free(kv.key);
......@@ -198,7 +300,7 @@ pub const Map = struct {
198300 /// This invalidates the value returned by get() for this key.
199301 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
200302 pub fn orderedRemove(self: *Map, key: []const u8) bool {
201 assert(unicode.wtf8ValidateSlice(key));
303 assert(validateKey(key));
202304 const kv = self.array_hash_map.fetchOrderedRemove(key) orelse return false;
203305 const gpa = self.allocator;
204306 gpa.free(kv.key);
......@@ -233,105 +335,120 @@ pub const Map = struct {
233335
234336 /// Creates a null-delimited environment variable block in the format
235337 /// expected by POSIX, from a hash map plus options.
236 pub fn createBlockPosix(
338 pub fn createPosixBlock(
237339 map: *const Map,
238 arena: Allocator,
239 options: CreateBlockPosixOptions,
240 ) Allocator.Error![:null]?[*:0]u8 {
340 gpa: Allocator,
341 options: CreatePosixBlockOptions,
342 ) Allocator.Error!PosixBlock {
241343 const ZigProgressAction = enum { nothing, edit, delete, add };
242 const zig_progress_action: ZigProgressAction = a: {
243 const fd = options.zig_progress_fd orelse break :a .nothing;
244 const exists = map.get("ZIG_PROGRESS") != null;
344 const zig_progress_action: ZigProgressAction = action: {
345 const fd = options.zig_progress_fd orelse break :action .nothing;
346 const exists = map.contains("ZIG_PROGRESS");
245347 if (fd >= 0) {
246 break :a if (exists) .edit else .add;
348 break :action if (exists) .edit else .add;
247349 } else {
248 if (exists) break :a .delete;
350 if (exists) break :action .delete;
249351 }
250 break :a .nothing;
352 break :action .nothing;
251353 };
252354
253 const envp_count: usize = c: {
254 var c: usize = map.count();
355 const envp = try gpa.allocSentinel(?[*:0]u8, len: {
356 var len: usize = map.count();
255357 switch (zig_progress_action) {
256 .add => c += 1,
257 .delete => c -= 1,
358 .add => len += 1,
359 .delete => len -= 1,
258360 .nothing, .edit => {},
259361 }
260 break :c c;
261 };
262
263 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
264 var i: usize = 0;
362 break :len len;
363 }, null);
364 var envp_len: usize = 0;
365 errdefer {
366 envp[envp_len] = null;
367 PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa);
368 }
265369
266370 if (zig_progress_action == .add) {
267 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
268 i += 1;
371 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
372 envp_len += 1;
269373 }
270374
271 {
272 var it = map.iterator();
273 while (it.next()) |pair| {
274 if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) {
275 .add => unreachable,
276 .delete => continue,
277 .edit => {
278 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{
279 pair.key_ptr.*, options.zig_progress_fd.?,
280 }, 0);
281 i += 1;
282 continue;
283 },
284 .nothing => {},
285 };
286
287 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0);
288 i += 1;
289 }
375 for (map.keys(), map.values()) |key, value| {
376 if (mem.eql(u8, key, "ZIG_PROGRESS")) switch (zig_progress_action) {
377 .add => unreachable,
378 .delete => continue,
379 .edit => {
380 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={d}", .{
381 key, options.zig_progress_fd.?,
382 }, 0);
383 envp_len += 1;
384 continue;
385 },
386 .nothing => {},
387 };
388
389 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={s}", .{ key, value }, 0);
390 envp_len += 1;
290391 }
291392
292 assert(i == envp_count);
293 return envp_buf;
393 assert(envp_len == envp.len);
394 return .{ .slice = envp };
294395 }
295396
296397 /// Caller owns result.
297 pub fn createBlockWindows(map: *const Map, gpa: Allocator) error{ OutOfMemory, InvalidWtf8 }![:0]u16 {
398 pub fn createWindowsBlock(
399 map: *const Map,
400 gpa: Allocator,
401 options: CreateWindowsBlockOptions,
402 ) error{ OutOfMemory, InvalidWtf8 }!WindowsBlock {
298403 // count bytes needed
299 const max_chars_needed = x: {
300 // Only need 2 trailing NUL code units for an empty environment
301 var max_chars_needed: usize = if (map.count() == 0) 2 else 1;
302 var it = map.iterator();
303 while (it.next()) |pair| {
304 // +1 for '='
305 // +1 for null byte
306 max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2;
404 const max_chars_needed = max_chars_needed: {
405 var max_chars_needed: usize = "\x00".len;
406 if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
407 max_chars_needed += std.fmt.count("ZIG_PROGRESS={d}\x00", .{@intFromPtr(handle)});
408 };
409 for (map.keys(), map.values()) |key, value| {
410 if (options.zig_progress_handle != null and eqlKeys(key, "ZIG_PROGRESS")) continue;
411 max_chars_needed += key.len + "=".len + value.len + "\x00".len;
307412 }
308 break :x max_chars_needed;
413 break :max_chars_needed @max("\x00\x00".len, max_chars_needed);
309414 };
310 const result = try gpa.alloc(u16, max_chars_needed);
311 errdefer gpa.free(result);
415 const block = try gpa.alloc(u16, max_chars_needed);
416 errdefer gpa.free(block);
312417
313 var it = map.iterator();
314418 var i: usize = 0;
315 while (it.next()) |pair| {
316 i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
317 result[i] = '=';
419 if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
420 @memcpy(
421 block[i..][0.."ZIG_PROGRESS=".len],
422 &[_]u16{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S', '=' },
423 );
424 i += "ZIG_PROGRESS=".len;
425 var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined;
426 const value = std.fmt.bufPrint(&value_buf, "{d}", .{@intFromPtr(handle)}) catch unreachable;
427 for (block[i..][0..value.len], value) |*r, v| r.* = v;
428 i += value.len;
429 block[i] = 0;
318430 i += 1;
319 i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
320 result[i] = 0;
431 };
432 for (map.keys(), map.values()) |key, value| {
433 if (options.zig_progress_handle != null and eqlKeys(key, "ZIG_PROGRESS")) continue;
434 i += try unicode.wtf8ToWtf16Le(block[i..], key);
435 block[i] = '=';
436 i += 1;
437 i += try unicode.wtf8ToWtf16Le(block[i..], value);
438 block[i] = 0;
321439 i += 1;
322440 }
323 result[i] = 0;
324 i += 1;
325441 // An empty environment is a special case that requires a redundant
326442 // NUL terminator. CreateProcess will read the second code unit even
327443 // though theoretically the first should be enough to recognize that the
328444 // environment is empty (see https://nullprogram.com/blog/2023/08/23/)
329 if (map.count() == 0) {
330 result[i] = 0;
445 for (0..2) |_| {
446 block[i] = 0;
331447 i += 1;
332 }
333 const reallocated = try gpa.realloc(result, i);
334 return reallocated[0 .. i - 1 :0];
448 if (i >= 2) break;
449 } else unreachable;
450 const reallocated = try gpa.realloc(block, i);
451 return .{ .slice = reallocated[0 .. i - 1 :0] };
335452 }
336453};
337454
......@@ -344,13 +461,18 @@ pub const CreateMapError = error{
344461
345462/// Allocates a `Map` and copies environment block into it.
346463pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
347 if (native_os == .windows)
348 return createMapWide(std.os.windows.peb().ProcessParameters.Environment, allocator);
349
350 var result = Map.init(allocator);
351 errdefer result.deinit();
464 var map = Map.init(allocator);
465 errdefer map.deinit();
466 if (native_os == .windows) empty: {
467 if (!env.block.use_global) break :empty;
468
469 const peb = std.os.windows.peb();
470 assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
471 defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
472 try map.putWindowsBlock(.{ .ptr = peb.ProcessParameters.Environment });
473 } else if (native_os == .wasi and !builtin.link_libc) empty: {
474 if (!env.block.use_global) break :empty;
352475
353 if (native_os == .wasi and !builtin.link_libc) {
354476 var environ_count: usize = undefined;
355477 var environ_buf_size: usize = undefined;
356478
......@@ -360,7 +482,7 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
360482 }
361483
362484 if (environ_count == 0) {
363 return result;
485 return map;
364486 }
365487
366488 const environ = try allocator.alloc([*:0]u8, environ_count);
......@@ -373,63 +495,9 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
373495 return posix.unexpectedErrno(environ_get_ret);
374496 }
375497
376 for (environ) |line| {
377 const pair = mem.sliceTo(line, 0);
378 var parts = mem.splitScalar(u8, pair, '=');
379 const key = parts.first();
380 const value = parts.rest();
381 try result.put(key, value);
382 }
383 return result;
384 } else {
385 for (env.block) |opt_line| {
386 const line = opt_line.?;
387 var line_i: usize = 0;
388 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
389 const key = line[0..line_i];
390
391 var end_i: usize = line_i;
392 while (line[end_i] != 0) : (end_i += 1) {}
393 const value = line[line_i + 1 .. end_i];
394
395 try result.put(key, value);
396 }
397 return result;
398 }
399}
400
401pub fn createMapWide(ptr: [*:0]u16, gpa: Allocator) CreateMapError!Map {
402 var result = Map.init(gpa);
403 errdefer result.deinit();
404
405 var i: usize = 0;
406 while (ptr[i] != 0) {
407 const key_start = i;
408
409 // There are some special environment variables that start with =,
410 // so we need a special case to not treat = as a key/value separator
411 // if it's the first character.
412 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
413 if (ptr[key_start] == '=') i += 1;
414
415 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
416 const key_w = ptr[key_start..i];
417 const key = try unicode.wtf16LeToWtf8Alloc(gpa, key_w);
418 errdefer gpa.free(key);
419
420 if (ptr[i] == '=') i += 1;
421
422 const value_start = i;
423 while (ptr[i] != 0) : (i += 1) {}
424 const value_w = ptr[value_start..i];
425 const value = try unicode.wtf16LeToWtf8Alloc(gpa, value_w);
426 errdefer gpa.free(value);
427
428 i += 1; // skip over null byte
429
430 try result.putMove(key, value);
431 }
432 return result;
498 try map.putPosixBlock(.{ .slice = environ });
499 } else try map.putPosixBlock(env.block.view());
500 return map;
433501}
434502
435503pub const ContainsError = error{
......@@ -451,6 +519,7 @@ pub const ContainsError = error{
451519/// * `containsConstant`
452520/// * `containsUnempty`
453521pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool {
522 if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8;
454523 var map = try createMap(environ, gpa);
455524 defer map.deinit();
456525 return map.contains(key);
......@@ -464,6 +533,7 @@ pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError
464533/// * `containsUnemptyConstant`
465534/// * `contains`
466535pub fn containsUnempty(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool {
536 if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8;
467537 var map = try createMap(environ, gpa);
468538 defer map.deinit();
469539 const value = map.get(key) orelse return false;
......@@ -516,16 +586,15 @@ pub inline fn containsUnemptyConstant(environ: Environ, comptime key: []const u8
516586/// * `createMap`
517587pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 {
518588 if (mem.findScalar(u8, key, '=') != null) return null;
519 for (environ.block) |opt_line| {
520 const line = opt_line.?;
521 var line_i: usize = 0;
522 while (line[line_i] != 0) : (line_i += 1) {
523 if (line_i == key.len) break;
524 if (line[line_i] != key[line_i]) break;
589 for (environ.block.view().slice) |entry| {
590 var entry_i: usize = 0;
591 while (entry[entry_i] != 0) : (entry_i += 1) {
592 if (entry_i == key.len) break;
593 if (entry[entry_i] != key[entry_i]) break;
525594 }
526 if ((line_i != key.len) or (line[line_i] != '=')) continue;
595 if ((entry_i != key.len) or (entry[entry_i] != '=')) continue;
527596
528 return mem.sliceTo(line + line_i + 1, 0);
597 return mem.sliceTo(entry + entry_i + 1, 0);
529598 }
530599 return null;
531600}
......@@ -541,14 +610,16 @@ pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 {
541610/// * `containsConstant`
542611/// * `contains`
543612pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 {
544 comptime assert(native_os == .windows);
545 comptime assert(@TypeOf(environ.block) == void);
546
547613 // '=' anywhere but the start makes this an invalid environment variable name.
548614 const key_slice = mem.sliceTo(key, 0);
549 if (key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') != null) return null;
615 assert(key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') == null);
550616
551 const ptr = std.os.windows.peb().ProcessParameters.Environment;
617 if (!environ.block.use_global) return null;
618
619 const peb = std.os.windows.peb();
620 assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
621 defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
622 const ptr = peb.ProcessParameters.Environment;
552623
553624 var i: usize = 0;
554625 while (ptr[i] != 0) {
......@@ -558,8 +629,7 @@ pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 {
558629 // so we need a special case to not treat = as a key/value separator
559630 // if it's the first character.
560631 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
561 const equal_search_start: usize = if (key_value[0] == '=') 1 else 0;
562 const equal_index = mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse {
632 const equal_index = mem.findScalarPos(u16, key_value, 1, '=') orelse {
563633 // This is enforced by CreateProcess.
564634 // If violated, CreateProcess will fail with INVALID_PARAMETER.
565635 unreachable; // must contain a =
......@@ -598,13 +668,14 @@ pub const GetAllocError = error{
598668/// See also:
599669/// * `createMap`
600670pub fn getAlloc(environ: Environ, gpa: Allocator, key: []const u8) GetAllocError![]u8 {
671 if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8;
601672 var map = createMap(environ, gpa) catch return error.OutOfMemory;
602673 defer map.deinit();
603674 const val = map.get(key) orelse return error.EnvironmentVariableMissing;
604675 return gpa.dupe(u8, val);
605676}
606677
607pub const CreateBlockPosixOptions = struct {
678pub const CreatePosixBlockOptions = struct {
608679 /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
609680 /// If non-null, negative means to remove the environment variable, and >= 0
610681 /// means to provide it with the given integer.
......@@ -613,67 +684,147 @@ pub const CreateBlockPosixOptions = struct {
613684
614685/// Creates a null-delimited environment variable block in the format expected
615686/// by POSIX, from a different one.
616pub fn createBlockPosix(
687pub fn createPosixBlock(
617688 existing: Environ,
618 arena: Allocator,
619 options: CreateBlockPosixOptions,
620) Allocator.Error![:null]?[*:0]u8 {
621 const contains_zig_progress = for (existing.block) |opt_line| {
622 if (mem.eql(u8, mem.sliceTo(opt_line.?, '='), "ZIG_PROGRESS")) break true;
689 gpa: Allocator,
690 options: CreatePosixBlockOptions,
691) Allocator.Error!PosixBlock {
692 const contains_zig_progress = for (existing.block.view().slice) |entry| {
693 if (mem.eql(u8, mem.sliceTo(entry, '='), "ZIG_PROGRESS")) break true;
623694 } else false;
624695
625696 const ZigProgressAction = enum { nothing, edit, delete, add };
626 const zig_progress_action: ZigProgressAction = a: {
627 const fd = options.zig_progress_fd orelse break :a .nothing;
697 const zig_progress_action: ZigProgressAction = action: {
698 const fd = options.zig_progress_fd orelse break :action .nothing;
628699 if (fd >= 0) {
629 break :a if (contains_zig_progress) .edit else .add;
700 break :action if (contains_zig_progress) .edit else .add;
630701 } else {
631 if (contains_zig_progress) break :a .delete;
702 if (contains_zig_progress) break :action .delete;
632703 }
633 break :a .nothing;
704 break :action .nothing;
634705 };
635706
636 const envp_count: usize = c: {
637 var count: usize = existing.block.len;
707 const envp = try gpa.allocSentinel(?[*:0]u8, len: {
708 var len: usize = existing.block.slice.len;
638709 switch (zig_progress_action) {
639 .add => count += 1,
640 .delete => count -= 1,
710 .add => len += 1,
711 .delete => len -= 1,
641712 .nothing, .edit => {},
642713 }
643 break :c count;
644 };
645
646 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
647 var i: usize = 0;
648 var existing_index: usize = 0;
649
714 break :len len;
715 }, null);
716 var envp_len: usize = 0;
717 errdefer {
718 envp[envp_len] = null;
719 PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa);
720 }
650721 if (zig_progress_action == .add) {
651 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
652 i += 1;
722 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
723 envp_len += 1;
653724 }
654725
655 while (existing.block[existing_index]) |line| : (existing_index += 1) {
656 if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
726 var existing_index: usize = 0;
727 while (existing.block.slice[existing_index]) |entry| : (existing_index += 1) {
728 if (mem.eql(u8, mem.sliceTo(entry, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
657729 .add => unreachable,
658730 .delete => continue,
659731 .edit => {
660 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
661 i += 1;
732 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
733 envp_len += 1;
662734 continue;
663735 },
664736 .nothing => {},
665737 };
666 envp_buf[i] = try arena.dupeZ(u8, mem.span(line));
667 i += 1;
738 envp[envp_len] = try gpa.dupeZ(u8, mem.span(entry));
739 envp_len += 1;
668740 }
669741
670 assert(i == envp_count);
671 return envp_buf;
742 assert(envp_len == envp.len);
743 return .{ .slice = envp };
672744}
673745
674test "Map.createBlock" {
675 const allocator = testing.allocator;
676 var envmap = Map.init(allocator);
746pub const CreateWindowsBlockOptions = struct {
747 /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
748 /// If non-null, `std.os.windows.INVALID_HANDLE_VALUE` means to remove the
749 /// environment variable, otherwise provide it with the given handle as an integer.
750 zig_progress_handle: ?std.os.windows.HANDLE = null,
751};
752
753/// Creates a null-delimited environment variable block in the format expected
754/// by POSIX, from a different one.
755pub fn createWindowsBlock(
756 existing: Environ,
757 gpa: Allocator,
758 options: CreateWindowsBlockOptions,
759) Allocator.Error!WindowsBlock {
760 if (!existing.block.use_global) return .{
761 .slice = try gpa.dupeSentinel(u16, WindowsBlock.empty.slice, 0),
762 };
763 const peb = std.os.windows.peb();
764 assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
765 defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
766 const existing_block = peb.ProcessParameters.Environment;
767 var ranges: [2]struct { start: usize, end: usize } = undefined;
768 var ranges_len: usize = 0;
769 ranges[ranges_len].start = 0;
770 const zig_progress_key = [_]u16{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S', '=' };
771 const needed_len = needed_len: {
772 var needed_len: usize = "\x00".len;
773 if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
774 needed_len += std.fmt.count("ZIG_PROGRESS={d}\x00", .{@intFromPtr(handle)});
775 };
776 var i: usize = 0;
777 while (existing_block[i] != 0) {
778 const start = i;
779 const entry = mem.sliceTo(existing_block[start..], 0);
780 i += entry.len + "\x00".len;
781 if (options.zig_progress_handle != null and entry.len >= zig_progress_key.len and
782 std.os.windows.eqlIgnoreCaseWtf16(entry[0..zig_progress_key.len], &zig_progress_key))
783 {
784 ranges[ranges_len].end = start;
785 ranges_len += 1;
786 ranges[ranges_len].start = i;
787 } else needed_len += entry.len + "\x00".len;
788 }
789 ranges[ranges_len].end = i;
790 ranges_len += 1;
791 break :needed_len @max("\x00\x00".len, needed_len);
792 };
793 const block = try gpa.alloc(u16, needed_len);
794 errdefer gpa.free(block);
795 var i: usize = 0;
796 if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
797 @memcpy(block[i..][0..zig_progress_key.len], &zig_progress_key);
798 i += zig_progress_key.len;
799 var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined;
800 const value = std.fmt.bufPrint(&value_buf, "{d}", .{@intFromPtr(handle)}) catch unreachable;
801 for (block[i..][0..value.len], value) |*r, v| r.* = v;
802 i += value.len;
803 block[i] = 0;
804 i += 1;
805 };
806 for (ranges[0..ranges_len]) |range| {
807 const range_len = range.end - range.start;
808 @memcpy(block[i..][0..range_len], existing_block[range.start..range.end]);
809 i += range_len;
810 }
811 // An empty environment is a special case that requires a redundant
812 // NUL terminator. CreateProcess will read the second code unit even
813 // though theoretically the first should be enough to recognize that the
814 // environment is empty (see https://nullprogram.com/blog/2023/08/23/)
815 for (0..2) |_| {
816 block[i] = 0;
817 i += 1;
818 if (i >= 2) break;
819 } else unreachable;
820 assert(i == block.len);
821 return .{ .slice = block[0 .. i - 1 :0] };
822}
823
824test "Map.createPosixBlock" {
825 const gpa = testing.allocator;
826
827 var envmap = Map.init(gpa);
677828 defer envmap.deinit();
678829
679830 try envmap.put("HOME", "/home/ifreund");
......@@ -682,29 +833,24 @@ test "Map.createBlock" {
682833 try envmap.put("DEBUGINFOD_URLS", " ");
683834 try envmap.put("XCURSOR_SIZE", "24");
684835
685 var arena = std.heap.ArenaAllocator.init(allocator);
686 defer arena.deinit();
687 const environ = try envmap.createBlockPosix(arena.allocator(), .{});
836 const block = try envmap.createPosixBlock(gpa, .{});
837 defer block.deinit(gpa);
688838
689 try testing.expectEqual(@as(usize, 5), environ.len);
839 try testing.expectEqual(@as(usize, 5), block.slice.len);
690840
691 inline for (.{
841 for (&[_][]const u8{
692842 "HOME=/home/ifreund",
693843 "WAYLAND_DISPLAY=wayland-1",
694844 "DISPLAY=:1",
695845 "DEBUGINFOD_URLS= ",
696846 "XCURSOR_SIZE=24",
697 }) |target| {
698 for (environ) |variable| {
699 if (mem.eql(u8, mem.span(variable orelse continue), target)) break;
700 } else {
701 try testing.expect(false); // Environment variable not found
702 }
703 }
847 }, block.slice) |expected, actual| try testing.expectEqualStrings(expected, mem.span(actual.?));
704848}
705849
706850test Map {
707 var env = Map.init(testing.allocator);
851 const gpa = testing.allocator;
852
853 var env: Map = .init(gpa);
708854 defer env.deinit();
709855
710856 try env.put("SOMETHING_NEW", "hello");
......@@ -740,6 +886,7 @@ test Map {
740886 try testing.expect(env.swapRemove("SOMETHING_NEW"));
741887 try testing.expect(!env.swapRemove("SOMETHING_NEW"));
742888 try testing.expect(env.get("SOMETHING_NEW") == null);
889 try testing.expect(!env.contains("SOMETHING_NEW"));
743890
744891 try testing.expectEqual(@as(Map.Size, 1), env.count());
745892
......@@ -749,10 +896,10 @@ test Map {
749896 try testing.expectEqualStrings("something else", env.get("кириллица").?);
750897
751898 // and WTF-8 that's not valid UTF-8
752 const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{
899 const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(gpa, &[_]u16{
753900 mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate
754901 });
755 defer testing.allocator.free(wtf8_with_surrogate_pair);
902 defer gpa.free(wtf8_with_surrogate_pair);
756903
757904 try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair);
758905 try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?);
......@@ -769,13 +916,9 @@ test "convert from Environ to Map and back again" {
769916 defer map.deinit();
770917 try map.put("FOO", "BAR");
771918 try map.put("A", "");
772 try map.put("", "B");
773
774 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
775 defer arena_allocator.deinit();
776 const arena = arena_allocator.allocator();
777919
778 const environ: Environ = .{ .block = try map.createBlockPosix(arena, .{}) };
920 const environ: Environ = .{ .block = try map.createPosixBlock(gpa, .{}) };
921 defer environ.block.deinit(gpa);
779922
780923 try testing.expectEqual(true, environ.contains(gpa, "FOO"));
781924 try testing.expectEqual(false, environ.contains(gpa, "BAR"));
......@@ -783,7 +926,6 @@ test "convert from Environ to Map and back again" {
783926 try testing.expectEqual(true, environ.containsConstant("A"));
784927 try testing.expectEqual(false, environ.containsUnempty(gpa, "A"));
785928 try testing.expectEqual(false, environ.containsUnemptyConstant("A"));
786 try testing.expectEqual(true, environ.contains(gpa, ""));
787929 try testing.expectEqual(false, environ.contains(gpa, "B"));
788930
789931 try testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(gpa, "BOGUS"));
......@@ -800,23 +942,47 @@ test "convert from Environ to Map and back again" {
800942 try testing.expectEqualDeep(map.values(), map2.values());
801943}
802944
803test createMapWide {
804 if (builtin.cpu.arch.endian() == .big) return error.SkipZigTest; // TODO
945test "Map.putPosixBlock" {
946 const gpa = testing.allocator;
947
948 var map: Map = .init(gpa);
949 defer map.deinit();
950
951 try map.put("FOO", "BAR");
952 try map.put("A", "");
953 try map.put("ZIG_PROGRESS", "unchanged");
954
955 const block = try map.createPosixBlock(gpa, .{});
956 defer block.deinit(gpa);
957
958 var map2: Map = .init(gpa);
959 defer map2.deinit();
960 try map2.putPosixBlock(block.view());
961
962 try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "ZIG_PROGRESS" }, map2.keys());
963 try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "unchanged" }, map2.values());
964}
965
966test "Map.putWindowsBlock" {
967 if (native_os != .windows) return;
805968
806969 const gpa = testing.allocator;
807970
808971 var map: Map = .init(gpa);
809972 defer map.deinit();
973
810974 try map.put("FOO", "BAR");
811975 try map.put("A", "");
812 try map.put("", "B");
976 try map.put("=B", "");
977 try map.put("ZIG_PROGRESS", "unchanged");
813978
814 const environ: [:0]u16 = try map.createBlockWindows(gpa);
815 defer gpa.free(environ);
979 const block = try map.createWindowsBlock(gpa, .{});
980 defer block.deinit(gpa);
816981
817 var map2 = try createMapWide(environ, gpa);
982 var map2: Map = .init(gpa);
818983 defer map2.deinit();
984 try map2.putWindowsBlock(block.view());
819985
820 try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B" }, map2.keys());
821 try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "" }, map2.values());
986 try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B", "ZIG_PROGRESS" }, map2.keys());
987 try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "", "unchanged" }, map2.values());
822988}
lib/std/start.zig+9-8
......@@ -90,15 +90,15 @@ fn _DllMainCRTStartup(
9090fn wasm_freestanding_start() callconv(.c) void {
9191 // This is marked inline because for some reason LLVM in
9292 // release mode fails to inline it, and we want fewer call frames in stack traces.
93 _ = @call(.always_inline, callMain, .{ {}, {} });
93 _ = @call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global });
9494}
9595
9696fn startWasi() callconv(.c) void {
9797 // The function call is marked inline because for some reason LLVM in
9898 // release mode fails to inline it, and we want fewer call frames in stack traces.
9999 switch (builtin.wasi_exec_model) {
100 .reactor => _ = @call(.always_inline, callMain, .{ {}, {} }),
101 .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{ {}, {} })),
100 .reactor => _ = @call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global }),
101 .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global })),
102102 }
103103}
104104
......@@ -476,7 +476,7 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn {
476476 const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
477477 const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)];
478478
479 std.os.windows.ntdll.RtlExitUserProcess(callMain(cmd_line_w, {}));
479 std.os.windows.ntdll.RtlExitUserProcess(callMain(cmd_line_w, .global));
480480}
481481
482482fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn {
......@@ -620,13 +620,14 @@ fn expandStackSize(phdrs: []elf.Phdr) void {
620620}
621621
622622inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) u8 {
623 const env_block: std.process.Environ.Block = .{ .slice = envp };
623624 if (std.Options.debug_threaded_io) |t| {
624625 if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0];
625 t.environ = .{ .process_environ = .{ .block = envp } };
626 t.environ = .{ .process_environ = .{ .block = env_block } };
626627 }
627628 std.Thread.maybeAttachSignalStack();
628629 std.debug.maybeEnableSegfaultHandler();
629 return callMain(argv[0..argc], envp);
630 return callMain(argv[0..argc], env_block);
630631}
631632
632633fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int {
......@@ -648,7 +649,7 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal
648649 std.debug.maybeEnableSegfaultHandler();
649650 const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
650651 const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)];
651 return callMain(cmd_line_w, {});
652 return callMain(cmd_line_w, .global);
652653 },
653654 else => {},
654655 }
......@@ -661,7 +662,7 @@ fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int {
661662 if (@sizeOf(std.Io.Threaded.Argv0) != 0) {
662663 if (std.Options.debug_threaded_io) |t| t.argv0.value = argv[0];
663664 }
664 return callMain(argv, &.{});
665 return callMain(argv, .empty);
665666}
666667
667668/// General error message for a malformed return type
test/standalone/env_vars/main.zig-23
......@@ -12,14 +12,10 @@ pub fn main(init: std.process.Init) !void {
1212 // containsUnempty
1313 {
1414 try std.testing.expect(try environ.containsUnempty(allocator, "FOO"));
15 try std.testing.expect(!(try environ.containsUnempty(allocator, "FOO=")));
16 try std.testing.expect(!(try environ.containsUnempty(allocator, "FO")));
17 try std.testing.expect(!(try environ.containsUnempty(allocator, "FOOO")));
1815 if (builtin.os.tag == .windows) {
1916 try std.testing.expect(try environ.containsUnempty(allocator, "foo"));
2017 }
2118 try std.testing.expect(try environ.containsUnempty(allocator, "EQUALS"));
22 try std.testing.expect(!(try environ.containsUnempty(allocator, "EQUALS=ABC")));
2319 try std.testing.expect(try environ.containsUnempty(allocator, "КИРиллИЦА"));
2420 if (builtin.os.tag == .windows) {
2521 try std.testing.expect(try environ.containsUnempty(allocator, "кирИЛЛица"));
......@@ -35,14 +31,10 @@ pub fn main(init: std.process.Init) !void {
3531 // containsUnemptyConstant
3632 {
3733 try std.testing.expect(environ.containsUnemptyConstant("FOO"));
38 try std.testing.expect(!environ.containsUnemptyConstant("FOO="));
39 try std.testing.expect(!environ.containsUnemptyConstant("FO"));
40 try std.testing.expect(!environ.containsUnemptyConstant("FOOO"));
4134 if (builtin.os.tag == .windows) {
4235 try std.testing.expect(environ.containsUnemptyConstant("foo"));
4336 }
4437 try std.testing.expect(environ.containsUnemptyConstant("EQUALS"));
45 try std.testing.expect(!environ.containsUnemptyConstant("EQUALS=ABC"));
4638 try std.testing.expect(environ.containsUnemptyConstant("КИРиллИЦА"));
4739 if (builtin.os.tag == .windows) {
4840 try std.testing.expect(environ.containsUnemptyConstant("кирИЛЛица"));
......@@ -58,14 +50,10 @@ pub fn main(init: std.process.Init) !void {
5850 // contains
5951 {
6052 try std.testing.expect(try environ.contains(allocator, "FOO"));
61 try std.testing.expect(!(try environ.contains(allocator, "FOO=")));
62 try std.testing.expect(!(try environ.contains(allocator, "FO")));
63 try std.testing.expect(!(try environ.contains(allocator, "FOOO")));
6453 if (builtin.os.tag == .windows) {
6554 try std.testing.expect(try environ.contains(allocator, "foo"));
6655 }
6756 try std.testing.expect(try environ.contains(allocator, "EQUALS"));
68 try std.testing.expect(!(try environ.contains(allocator, "EQUALS=ABC")));
6957 try std.testing.expect(try environ.contains(allocator, "КИРиллИЦА"));
7058 if (builtin.os.tag == .windows) {
7159 try std.testing.expect(try environ.contains(allocator, "кирИЛЛица"));
......@@ -81,14 +69,10 @@ pub fn main(init: std.process.Init) !void {
8169 // containsConstant
8270 {
8371 try std.testing.expect(environ.containsConstant("FOO"));
84 try std.testing.expect(!environ.containsConstant("FOO="));
85 try std.testing.expect(!environ.containsConstant("FO"));
86 try std.testing.expect(!environ.containsConstant("FOOO"));
8772 if (builtin.os.tag == .windows) {
8873 try std.testing.expect(environ.containsConstant("foo"));
8974 }
9075 try std.testing.expect(environ.containsConstant("EQUALS"));
91 try std.testing.expect(!environ.containsConstant("EQUALS=ABC"));
9276 try std.testing.expect(environ.containsConstant("КИРиллИЦА"));
9377 if (builtin.os.tag == .windows) {
9478 try std.testing.expect(environ.containsConstant("кирИЛЛица"));
......@@ -104,14 +88,10 @@ pub fn main(init: std.process.Init) !void {
10488 // getAlloc
10589 {
10690 try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "FOO"));
107 try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOO="));
108 try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FO"));
109 try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOOO"));
11091 if (builtin.os.tag == .windows) {
11192 try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "foo"));
11293 }
11394 try std.testing.expectEqualSlices(u8, "ABC=123", try environ.getAlloc(arena, "EQUALS"));
114 try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "EQUALS=ABC"));
11595 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "КИРиллИЦА"));
11696 if (builtin.os.tag == .windows) {
11797 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "кирИЛЛица"));
......@@ -130,13 +110,10 @@ pub fn main(init: std.process.Init) !void {
130110 defer environ_map.deinit();
131111
132112 try std.testing.expectEqualSlices(u8, "123", environ_map.get("FOO").?);
133 try std.testing.expectEqual(null, environ_map.get("FO"));
134 try std.testing.expectEqual(null, environ_map.get("FOOO"));
135113 if (builtin.os.tag == .windows) {
136114 try std.testing.expectEqualSlices(u8, "123", environ_map.get("foo").?);
137115 }
138116 try std.testing.expectEqualSlices(u8, "ABC=123", environ_map.get("EQUALS").?);
139 try std.testing.expectEqual(null, environ_map.get("EQUALS=ABC"));
140117 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("КИРиллИЦА").?);
141118 if (builtin.os.tag == .windows) {
142119 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("кирИЛЛица").?);
test/standalone/windows_argv/fuzz.zig+7-2
......@@ -125,7 +125,7 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO
125125 .lpReserved2 = null,
126126 .hStdInput = null,
127127 .hStdOutput = null,
128 .hStdError = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null,
128 .hStdError = windows.peb().ProcessParameters.hStdError,
129129 };
130130 var proc_info: windows.PROCESS_INFORMATION = undefined;
131131
......@@ -149,7 +149,12 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO
149149 break :spawn proc_info.hProcess;
150150 };
151151 defer windows.CloseHandle(child_proc);
152 try windows.WaitForSingleObjectEx(child_proc, windows.INFINITE, false);
152 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
153 switch (windows.ntdll.NtWaitForSingleObject(child_proc, windows.FALSE, &infinite_timeout)) {
154 windows.NTSTATUS.WAIT_0 => {},
155 .TIMEOUT => return error.WaitTimeOut,
156 else => |status| return windows.unexpectedStatus(status),
157 }
153158
154159 var exit_code: windows.DWORD = undefined;
155160 if (windows.kernel32.GetExitCodeProcess(child_proc, &exit_code) == 0) {
test/standalone/windows_spawn/main.zig+4-3
......@@ -233,12 +233,13 @@ fn testExecWithCwdInner(gpa: Allocator, io: Io, command: []const u8, cwd: std.pr
233233}
234234
235235fn renameExe(dir: Io.Dir, io: Io, old_sub_path: []const u8, new_sub_path: []const u8) !void {
236 var attempt: u5 = 0;
236 var attempt: u5 = 10;
237237 while (true) break dir.rename(old_sub_path, dir, new_sub_path, io) catch |err| switch (err) {
238238 error.AccessDenied => {
239 if (attempt == 13) return error.AccessDenied;
239 if (attempt == 26) return error.AccessDenied;
240240 // give the kernel a chance to finish closing the executable handle
241 _ = std.os.windows.kernel32.SleepEx(@as(u32, 1) << attempt >> 1, std.os.windows.FALSE);
241 const interval = @as(std.os.windows.LARGE_INTEGER, -1) << attempt;
242 _ = std.os.windows.ntdll.NtDelayExecution(std.os.windows.FALSE, &interval);
242243 attempt += 1;
243244 continue;
244245 },