authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-01-30 13:07:41-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-31 23:45:52-08:00
log753e71e2f57d488c055bdd34e0f591b62fb8a7bd
treef875a5723cf9daad4205ba8918f0e2d22038ad26
parente5454ff780ae4571cfa71a2edb6f4287eb8cf4de

std.Io.Threaded: implement and cleanup windows codepaths


10 files changed, 841 insertions(+), 981 deletions(-)

lib/std/Build/Watch.zig+6-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,8 +377,11 @@ 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,
lib/std/Io/Threaded.zig+356-338
......@@ -73,6 +73,7 @@ environ: Environ,
7373
7474null_file: NullFile = .{},
7575random_file: RandomFile = .{},
76pipe_file: PipeFile = .{},
7677
7778csprng: Csprng = .{},
7879
......@@ -118,7 +119,7 @@ pub const Argv0 = switch (native_os) {
118119
119120const Environ = struct {
120121 /// Unmodified data directly from the OS.
121 process_environ: process.Environ = .empty,
122 process_environ: process.Environ,
122123 /// Protected by `mutex`. Determines whether the other fields have been
123124 /// memoized based on `process_environ`.
124125 initialized: bool = false,
......@@ -190,6 +191,24 @@ pub const RandomFile = switch (native_os) {
190191 },
191192};
192193
194pub const PipeFile = switch (native_os) {
195 .windows => struct {
196 handle: ?windows.HANDLE = null,
197
198 fn deinit(this: *@This()) void {
199 if (this.handle) |handle| {
200 windows.CloseHandle(handle);
201 this.handle = null;
202 }
203 }
204 },
205 else => struct {
206 fn deinit(this: @This()) void {
207 _ = this;
208 }
209 },
210};
211
193212pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
194213 unknown = 0,
195214 _,
......@@ -1467,7 +1486,9 @@ pub const init_single_threaded: Threaded = .{
14671486 .old_sig_pipe = undefined,
14681487 .have_signal_handler = false,
14691488 .argv0 = .empty,
1470 .environ = .{},
1489 .environ = .{ .process_environ = .{
1490 .block = if (process.Environ.Block == process.Environ.GlobalBlock) .global else .empty,
1491 } },
14711492 .worker_threads = .init(null),
14721493 .disable_memory_mapping = false,
14731494};
......@@ -1502,6 +1523,7 @@ pub fn deinit(t: *Threaded) void {
15021523 }
15031524 t.null_file.deinit();
15041525 t.random_file.deinit();
1526 t.pipe_file.deinit();
15051527 t.* = undefined;
15061528}
15071529
......@@ -1544,14 +1566,7 @@ fn worker(t: *Threaded) void {
15441566 },
15451567 },
15461568 },
1547 &.{
1548 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
1549 .RootDirectory = null,
1550 .ObjectName = null,
1551 .Attributes = .{},
1552 .SecurityDescriptor = null,
1553 .SecurityQualityOfService = null,
1554 },
1569 &.{ .ObjectName = null },
15551570 &windows.teb().ClientId,
15561571 ) == .SUCCESS);
15571572 }
......@@ -3288,12 +3303,8 @@ fn dirCreateDirPathOpenWindows(
32883303 },
32893304 },
32903305 &.{
3291 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
32923306 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
3293 .Attributes = .{},
32943307 .ObjectName = &nt_name,
3295 .SecurityDescriptor = null,
3296 .SecurityQualityOfService = null,
32973308 },
32983309 &io_status_block,
32993310 null,
......@@ -3975,13 +3986,9 @@ fn dirAccessWindows(
39753986 .MaximumLength = path_len_bytes,
39763987 .Buffer = @constCast(sub_path_w.ptr),
39773988 };
3978 var attr: windows.OBJECT_ATTRIBUTES = .{
3979 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
3989 const attr: windows.OBJECT_ATTRIBUTES = .{
39803990 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
3981 .Attributes = .{},
39823991 .ObjectName = &nt_name,
3983 .SecurityDescriptor = null,
3984 .SecurityQualityOfService = null,
39853992 };
39863993 var basic_info: windows.FILE.BASIC_INFORMATION = undefined;
39873994 const syscall: Syscall = try .start();
......@@ -4197,14 +4204,8 @@ fn dirCreateFileWindows(
41974204 .Buffer = @constCast(sub_path_w.ptr),
41984205 };
41994206 const attr: windows.OBJECT_ATTRIBUTES = .{
4200 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
42014207 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
4202 .Attributes = .{
4203 .INHERIT = false,
4204 },
42054208 .ObjectName = &nt_name,
4206 .SecurityDescriptor = null,
4207 .SecurityQualityOfService = null,
42084209 };
42094210 const create_disposition: windows.FILE.CREATE_DISPOSITION = if (flags.exclusive)
42104211 .CREATE
......@@ -4811,17 +4812,6 @@ pub fn dirOpenFileWtf16(
48114812 .MaximumLength = path_len_bytes,
48124813 .Buffer = @constCast(sub_path_w.ptr),
48134814 };
4814 var attr: w.OBJECT_ATTRIBUTES = .{
4815 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
4816 .RootDirectory = dir_handle,
4817 .Attributes = .{
4818 // TODO should we set INHERIT=false?
4819 //.INHERIT = false,
4820 },
4821 .ObjectName = &nt_name,
4822 .SecurityDescriptor = null,
4823 .SecurityQualityOfService = null,
4824 };
48254815 var io_status_block: w.IO_STATUS_BLOCK = undefined;
48264816
48274817 // There are multiple kernel bugs being worked around with retries.
......@@ -4840,7 +4830,10 @@ pub fn dirOpenFileWtf16(
48404830 .WRITE = flags.isWrite(),
48414831 },
48424832 },
4843 &attr,
4833 &.{
4834 .RootDirectory = dir_handle,
4835 .ObjectName = &nt_name,
4836 },
48444837 &io_status_block,
48454838 null,
48464839 .{ .NORMAL = true },
......@@ -5202,12 +5195,8 @@ pub fn dirOpenDirWindows(
52025195 },
52035196 },
52045197 &.{
5205 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
52065198 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
5207 .Attributes = .{},
52085199 .ObjectName = &nt_name,
5209 .SecurityDescriptor = null,
5210 .SecurityQualityOfService = null,
52115200 },
52125201 &io_status_block,
52135202 null,
......@@ -6417,12 +6406,8 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
64176406 .SYNCHRONIZE = true,
64186407 } },
64196408 &.{
6420 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
64216409 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
6422 .Attributes = .{},
64236410 .ObjectName = &nt_name,
6424 .SecurityDescriptor = null,
6425 .SecurityQualityOfService = null,
64266411 },
64276412 &io_status_block,
64286413 null,
......@@ -7242,14 +7227,8 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink
72427227 .Buffer = @constCast(sub_path_w.ptr),
72437228 };
72447229 const attr: windows.OBJECT_ATTRIBUTES = .{
7245 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
72467230 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
7247 .Attributes = .{
7248 .INHERIT = false,
7249 },
72507231 .ObjectName = &nt_name,
7251 .SecurityDescriptor = null,
7252 .SecurityQualityOfService = null,
72537232 };
72547233 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
72557234 var result_handle: windows.HANDLE = undefined;
......@@ -7800,24 +7779,19 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void {
78007779 const t: *Threaded = @ptrCast(@alignCast(userdata));
78017780 _ = t;
78027781
7782 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
78037783 const syscall: Syscall = try .start();
78047784 while (true) {
7805 if (windows.kernel32.FlushFileBuffers(file.handle) != 0) {
7806 return syscall.finish();
7807 }
7808 switch (windows.GetLastError()) {
7809 .SUCCESS => unreachable, // `FlushFileBuffers` returned nonzero
7810 .INVALID_HANDLE => unreachable,
7811 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time
7812 .UNEXP_NET_ERR => return syscall.fail(error.InputOutput),
7813 .OPERATION_ABORTED => {
7785 switch (windows.ntdll.NtFlushBuffersFile(file.handle, &io_status_block)) {
7786 .SUCCESS => break syscall.finish(),
7787 .CANCELLED => {
78147788 try syscall.checkCancel();
78157789 continue;
78167790 },
7817 else => |err| {
7818 syscall.finish();
7819 return windows.unexpectedError(err);
7820 },
7791 .INVALID_HANDLE => unreachable,
7792 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time
7793 .UNEXPECTED_NETWORK_ERROR => return syscall.fail(error.InputOutput),
7794 else => |status| return syscall.unexpectedNtstatus(status),
78217795 }
78227796 }
78237797}
......@@ -14364,15 +14338,15 @@ fn scanEnviron(t: *Threaded) void {
1436414338 comptime assert(@sizeOf(Environ.String) == 0);
1436514339 }
1436614340 } else {
14367 for (t.environ.process_environ.block) |opt_line| {
14368 const line = opt_line.?;
14369 var line_i: usize = 0;
14370 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
14371 const key = line[0..line_i];
14341 for (t.environ.process_environ.block.slice) |opt_entry| {
14342 const entry = opt_entry.?;
14343 var entry_i: usize = 0;
14344 while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {}
14345 const key = entry[0..entry_i];
1437214346
14373 var end_i: usize = line_i;
14374 while (line[end_i] != 0) : (end_i += 1) {}
14375 const value = line[line_i + 1 .. end_i :0];
14347 var end_i: usize = entry_i;
14348 while (entry[end_i] != 0) : (end_i += 1) {}
14349 const value = entry[entry_i + 1 .. end_i :0];
1437614350
1437714351 if (std.mem.eql(u8, key, "NO_COLOR")) {
1437814352 t.environ.exist.NO_COLOR = true;
......@@ -14402,19 +14376,17 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
1440214376 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);
1440314377 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
1440414378
14405 const envp: [*:null]const ?[*:0]const u8 = m: {
14379 const env_block = env_block: {
1440614380 const prog_fd: i32 = -1;
14407 if (options.environ_map) |environ_map| {
14408 break :m (try environ_map.createBlockPosix(arena, .{
14409 .zig_progress_fd = prog_fd,
14410 })).ptr;
14411 }
14412 break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{
14381 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
14382 .zig_progress_fd = prog_fd,
14383 });
14384 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{
1441314385 .zig_progress_fd = prog_fd,
14414 })).ptr;
14386 });
1441514387 };
1441614388
14417 return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH);
14389 return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
1441814390}
1441914391
1442014392fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError {
......@@ -14513,16 +14485,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1451314485 const prog_fileno = 3;
1451414486 comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno);
1451514487
14516 const envp: [*:null]const ?[*:0]const u8 = m: {
14488 const env_block = env_block: {
1451714489 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
14518 if (options.environ_map) |environ_map| {
14519 break :m (try environ_map.createBlockPosix(arena, .{
14520 .zig_progress_fd = prog_fd,
14521 })).ptr;
14522 }
14523 break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{
14490 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
14491 .zig_progress_fd = prog_fd,
14492 });
14493 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{
1452414494 .zig_progress_fd = prog_fd,
14525 })).ptr;
14495 });
1452614496 };
1452714497
1452814498 // This pipe communicates to the parent errors in the child between `fork` and `execvpe`.
......@@ -14601,7 +14571,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1460114571 }
1460214572 }
1460314573
14604 const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH);
14574 const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
1460514575 forkBail(ep1, err);
1460614576 }
1460714577
......@@ -14615,7 +14585,6 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1461514585 if (options.stderr == .pipe) posix.close(stderr_pipe[1]);
1461614586
1461714587 if (prog_pipe[1] != -1) posix.close(prog_pipe[1]);
14618
1461914588 options.progress_node.setIpcFd(prog_pipe[0]);
1462014589
1462114590 return .{
......@@ -14739,42 +14708,44 @@ fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT
1473914708 // some rare edge cases where our process handle no longer has the
1474014709 // PROCESS_TERMINATE access right, so let's do another check to make
1474114710 // sure the process is really no longer running:
14742 windows.WaitForSingleObjectEx(handle, 0, false) catch return error.AccessDenied;
14743 return error.AlreadyTerminated;
14711 const minimal_timeout: windows.LARGE_INTEGER = -1;
14712 switch (windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &minimal_timeout)) {
14713 .SUCCESS => return error.AlreadyTerminated,
14714 else => return error.AccessDenied,
14715 }
1474414716 },
1474514717 else => |err| return windows.unexpectedError(err),
1474614718 }
1474714719 }
14748 _ = windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE);
14720 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
14721 _ = windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &infinite_timeout);
1474914722 childCleanupWindows(child);
1475014723}
1475114724
1475214725fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term {
1475314726 const handle = child.id.?;
1475414727
14755 const syscall: Syscall = try .start();
14756 while (true) switch (windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE)) {
14757 windows.WAIT_OBJECT_0 => break syscall.finish(),
14758 windows.WAIT_ABANDONED, windows.WAIT_TIMEOUT => {
14759 try syscall.checkCancel();
14728 const alertable_syscall: AlertableSyscall = try .start();
14729 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
14730 while (true) switch (windows.ntdll.NtWaitForSingleObject(handle, windows.TRUE, &infinite_timeout)) {
14731 .WAIT_0 => break alertable_syscall.finish(),
14732 .ABANDONED_WAIT_0, .TIMEOUT => {
14733 try alertable_syscall.checkCancel();
1476014734 continue;
1476114735 },
14762 windows.WAIT_FAILED => {
14763 syscall.finish();
14764 switch (windows.GetLastError()) {
14765 else => |err| return windows.unexpectedError(err),
14766 }
14767 },
14768 else => return syscall.fail(error.Unexpected),
14736 else => |status| return alertable_syscall.unexpectedNtstatus(status),
1476914737 };
1477014738
14771 const term: process.Child.Term = x: {
14772 var exit_code: windows.DWORD = undefined;
14773 if (windows.kernel32.GetExitCodeProcess(handle, &exit_code) == 0) {
14774 break :x .{ .unknown = 0 };
14775 } else {
14776 break :x .{ .exited = @as(u8, @truncate(exit_code)) };
14777 }
14739 var info: windows.PROCESS_BASIC_INFORMATION = undefined;
14740 const term: process.Child.Term = switch (windows.ntdll.NtQueryInformationProcess(
14741 handle,
14742 .BasicInformation,
14743 &info,
14744 @sizeOf(windows.PROCESS_BASIC_INFORMATION),
14745 null,
14746 )) {
14747 .SUCCESS => .{ .exited = @as(u8, @truncate(@intFromEnum(info.ExitStatus))) },
14748 else => .{ .unknown = 0 },
1477814749 };
1477914750
1478014751 childCleanupWindows(child);
......@@ -15037,88 +15008,70 @@ fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32
1503715008fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
1503815009 const t: *Threaded = @ptrCast(@alignCast(userdata));
1503915010
15040 var saAttr: windows.SECURITY_ATTRIBUTES = .{
15041 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
15042 .bInheritHandle = windows.TRUE,
15043 .lpSecurityDescriptor = null,
15044 };
15045
1504615011 const any_ignore =
1504715012 options.stdin == .ignore or
1504815013 options.stdout == .ignore or
1504915014 options.stderr == .ignore;
15050
15051 const nul_handle = if (any_ignore) try getNulHandle(t) else undefined;
15052
15053 var g_hChildStd_IN_Rd: ?windows.HANDLE = null;
15054 var g_hChildStd_IN_Wr: ?windows.HANDLE = null;
15055 switch (options.stdin) {
15056 .pipe => {
15057 try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr);
15058 },
15059 .ignore => {
15060 g_hChildStd_IN_Rd = nul_handle;
15061 },
15062 .inherit => {
15063 g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE) catch null;
15064 },
15065 .close => {
15066 g_hChildStd_IN_Rd = null;
15067 },
15068 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
15069 }
15070 errdefer if (options.stdin == .pipe) {
15071 windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr);
15072 };
15073
15074 var g_hChildStd_OUT_Rd: ?windows.HANDLE = null;
15075 var g_hChildStd_OUT_Wr: ?windows.HANDLE = null;
15076 switch (options.stdout) {
15077 .pipe => {
15078 try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr);
15079 },
15080 .ignore => {
15081 g_hChildStd_OUT_Wr = nul_handle;
15082 },
15083 .inherit => {
15084 g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) catch null;
15085 },
15086 .close => {
15087 g_hChildStd_OUT_Wr = null;
15088 },
15089 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
15090 }
15091 errdefer if (options.stdout == .pipe) {
15092 windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr);
15093 };
15094
15095 var g_hChildStd_ERR_Rd: ?windows.HANDLE = null;
15096 var g_hChildStd_ERR_Wr: ?windows.HANDLE = null;
15097 switch (options.stderr) {
15098 .pipe => {
15099 try windowsMakeAsyncPipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr);
15100 },
15101 .ignore => {
15102 g_hChildStd_ERR_Wr = nul_handle;
15103 },
15104 .inherit => {
15105 g_hChildStd_ERR_Wr = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null;
15106 },
15107 .close => {
15108 g_hChildStd_ERR_Wr = null;
15109 },
15110 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
15111 }
15112 errdefer if (options.stderr == .pipe) {
15113 windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr);
15114 };
15015 const nul_handle = if (any_ignore) try getNulDevice(t) else undefined;
15016
15017 const any_inherit =
15018 options.stdin == .inherit or
15019 options.stdout == .inherit or
15020 options.stderr == .inherit;
15021 const peb = if (any_inherit) windows.peb() else undefined;
15022
15023 const stdin_pipe = if (options.stdin == .pipe) try t.windowsCreatePipe(.{
15024 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15025 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15026 .outbound = true,
15027 }) else undefined;
15028 errdefer if (options.stdin == .pipe) for (stdin_pipe) |handle| windows.CloseHandle(handle);
15029
15030 const stdout_pipe = if (options.stdout == .pipe) try t.windowsCreatePipe(.{
15031 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15032 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15033 .inbound = true,
15034 }) else undefined;
15035 errdefer if (options.stdout == .pipe) for (stdout_pipe) |handle| windows.CloseHandle(handle);
15036
15037 const stderr_pipe = if (options.stderr == .pipe) try t.windowsCreatePipe(.{
15038 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15039 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15040 .inbound = true,
15041 }) else undefined;
15042 errdefer if (options.stderr == .pipe) for (stderr_pipe) |handle| windows.CloseHandle(handle);
15043
15044 const prog_pipe = if (options.progress_node.index != .none) try t.windowsCreatePipe(.{
15045 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15046 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15047 .inbound = true,
15048 }) else undefined;
15049 errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle);
1511515050
1511615051 var siStartInfo: windows.STARTUPINFOW = .{
1511715052 .cb = @sizeOf(windows.STARTUPINFOW),
15118 .hStdError = g_hChildStd_ERR_Wr,
15119 .hStdOutput = g_hChildStd_OUT_Wr,
15120 .hStdInput = g_hChildStd_IN_Rd,
1512115053 .dwFlags = windows.STARTF_USESTDHANDLES,
15054 .hStdInput = switch (options.stdin) {
15055 .inherit => peb.ProcessParameters.hStdInput,
15056 .file => |file| file.handle,
15057 .ignore => nul_handle,
15058 .pipe => stdin_pipe[1],
15059 .close => null,
15060 },
15061 .hStdOutput = switch (options.stdout) {
15062 .inherit => peb.ProcessParameters.hStdOutput,
15063 .file => |file| file.handle,
15064 .ignore => nul_handle,
15065 .pipe => stdout_pipe[1],
15066 .close => null,
15067 },
15068 .hStdError = switch (options.stdin) {
15069 .inherit => peb.ProcessParameters.hStdError,
15070 .file => |file| file.handle,
15071 .ignore => nul_handle,
15072 .pipe => stderr_pipe[1],
15073 .close => null,
15074 },
1512215075
1512315076 .lpReserved = null,
1512415077 .lpDesktop = null,
......@@ -15143,8 +15096,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1514315096 const cwd_w = if (options.cwd) |cwd| try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd) else null;
1514415097 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
1514515098
15146 const maybe_envp_buf = if (options.environ_map) |environ_map| try environ_map.createBlockWindows(arena) else null;
15147 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
15099 const env_block = env_block: {
15100 const prog_handle = if (options.progress_node.index != .none)
15101 prog_pipe[1]
15102 else
15103 windows.INVALID_HANDLE_VALUE;
15104 if (options.environ_map) |environ_map| break :env_block try environ_map.createWindowsBlock(arena, .{
15105 .zig_progress_handle = prog_handle,
15106 });
15107 break :env_block try t.environ.process_environ.createWindowsBlock(arena, .{
15108 .zig_progress_handle = if (options.progress_node.index != .none) prog_pipe[1] else windows.INVALID_HANDLE_VALUE,
15109 });
15110 };
1514815111
1514915112 const app_name_wtf8 = options.argv[0];
1515015113 const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8);
......@@ -15222,7 +15185,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1522215185 &app_buf,
1522315186 PATHEXT,
1522415187 &cmd_line_cache,
15225 envp_ptr,
15188 env_block,
1522615189 cwd_w_ptr,
1522715190 flags,
1522815191 &siStartInfo,
......@@ -15257,7 +15220,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1525715220 &app_buf,
1525815221 PATHEXT,
1525915222 &cmd_line_cache,
15260 envp_ptr,
15223 env_block,
1526115224 cwd_w_ptr,
1526215225 flags,
1526315226 &siStartInfo,
......@@ -15277,21 +15240,40 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1527715240 };
1527815241 }
1527915242
15280 if (options.stdin == .pipe) windows.CloseHandle(g_hChildStd_IN_Rd.?);
15281 if (options.stderr == .pipe) windows.CloseHandle(g_hChildStd_ERR_Wr.?);
15282 if (options.stdout == .pipe) windows.CloseHandle(g_hChildStd_OUT_Wr.?);
15243 if (options.progress_node.index != .none) {
15244 windows.CloseHandle(prog_pipe[1]);
15245 options.progress_node.setIpcFd(prog_pipe[0]);
15246 }
1528315247
1528415248 return .{
1528515249 .id = piProcInfo.hProcess,
1528615250 .thread_handle = piProcInfo.hThread,
15287 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = false } } else null,
15288 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
15289 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
15251 .stdin = stdin: switch (options.stdin) {
15252 .pipe => {
15253 windows.CloseHandle(stdin_pipe[1]);
15254 break :stdin .{ .handle = stdin_pipe[0], .flags = .{ .nonblocking = false } };
15255 },
15256 else => null,
15257 },
15258 .stdout = stdout: switch (options.stdout) {
15259 .pipe => {
15260 windows.CloseHandle(stdout_pipe[1]);
15261 break :stdout .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = true } };
15262 },
15263 else => null,
15264 },
15265 .stderr = stderr: switch (options.stderr) {
15266 .pipe => {
15267 windows.CloseHandle(stderr_pipe[1]);
15268 break :stderr .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = true } };
15269 },
15270 else => null,
15271 },
1529015272 .request_resource_usage_statistics = options.request_resource_usage_statistics,
1529115273 };
1529215274}
1529315275
15294fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
15276fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1529515277 {
1529615278 t.mutex.lock();
1529715279 defer t.mutex.unlock();
......@@ -15299,12 +15281,6 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1529915281 }
1530015282
1530115283 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' };
15302
15303 var nt_name: windows.UNICODE_STRING = .{
15304 .Length = device_path.len * 2,
15305 .MaximumLength = 0,
15306 .Buffer = @constCast(&device_path),
15307 };
1530815284 var fresh_handle: windows.HANDLE = undefined;
1530915285 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1531015286 var syscall: Syscall = try .start();
......@@ -15315,12 +15291,11 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1531515291 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } },
1531615292 },
1531715293 &.{
15318 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
15319 .RootDirectory = null,
15320 .ObjectName = &nt_name,
15321 .Attributes = .{},
15322 .SecurityDescriptor = null,
15323 .SecurityQualityOfService = null,
15294 .ObjectName = @constCast(&windows.UNICODE_STRING{
15295 .Length = @sizeOf(@TypeOf(device_path)),
15296 .MaximumLength = 0,
15297 .Buffer = @constCast(&device_path),
15298 }),
1532415299 },
1532515300 &io_status_block,
1532615301 .VALID_FLAGS,
......@@ -15347,7 +15322,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1534715322 };
1534815323}
1534915324
15350fn getNulHandle(t: *Threaded) !windows.HANDLE {
15325fn getNulDevice(t: *Threaded) !windows.HANDLE {
1535115326 {
1535215327 t.mutex.lock();
1535315328 defer t.mutex.unlock();
......@@ -15355,44 +15330,26 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE {
1535515330 }
1535615331
1535715332 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' };
15358 var nt_name: windows.UNICODE_STRING = .{
15359 .Length = device_path.len * 2,
15360 .MaximumLength = 0,
15361 .Buffer = @constCast(&device_path),
15362 };
15363 const attr: windows.OBJECT_ATTRIBUTES = .{
15364 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
15365 .RootDirectory = null,
15366 .Attributes = .{
15367 .INHERIT = true,
15368 },
15369 .ObjectName = &nt_name,
15370 .SecurityDescriptor = null,
15371 .SecurityQualityOfService = null,
15372 };
15373 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1537415333 var fresh_handle: windows.HANDLE = undefined;
15334 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1537515335 var syscall: Syscall = try .start();
15376 while (true) switch (windows.ntdll.NtCreateFile(
15336 while (true) switch (windows.ntdll.NtOpenFile(
1537715337 &fresh_handle,
1537815338 .{
1537915339 .STANDARD = .{ .SYNCHRONIZE = true },
15380 .GENERIC = .{ .WRITE = true, .READ = true },
15340 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true, .WRITE_DATA = true } },
15341 },
15342 &.{
15343 .Attributes = .{ .INHERIT = true },
15344 .ObjectName = @constCast(&windows.UNICODE_STRING{
15345 .Length = @sizeOf(@TypeOf(device_path)),
15346 .MaximumLength = 0,
15347 .Buffer = @constCast(&device_path),
15348 }),
1538115349 },
15382 &attr,
1538315350 &io_status_block,
15384 null,
15385 .{ .NORMAL = true },
1538615351 .VALID_FLAGS,
15387 .OPEN,
15388 .{
15389 .DIRECTORY_FILE = false,
15390 .NON_DIRECTORY_FILE = true,
15391 .IO = .SYNCHRONOUS_NONALERT,
15392 .OPEN_REPARSE_POINT = false,
15393 },
15394 null,
15395 0,
15352 .{ .IO = .SYNCHRONOUS_NONALERT },
1539615353 )) {
1539715354 .SUCCESS => {
1539815355 syscall.finish();
......@@ -15406,18 +15363,64 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE {
1540615363 return fresh_handle;
1540715364 }
1540815365 },
15409 .DELETE_PENDING => {
15410 // This error means that there *was* a file in this location on
15411 // the file system, but it was deleted. However, the OS is not
15412 // finished with the deletion operation, and so this CreateFile
15413 // call has failed. There is not really a sane way to handle
15414 // this other than retrying the creation after the OS finishes
15415 // the deletion.
15416 syscall.finish();
15417 try parking_sleep.windowsRetrySleep(1);
15418 syscall = try .start();
15366 .CANCELLED => {
15367 try syscall.checkCancel();
1541915368 continue;
1542015369 },
15370 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
15371 .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status),
15372 .INVALID_HANDLE => |status| return syscall.ntstatusBug(status),
15373 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
15374 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
15375 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
15376 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
15377 .SHARING_VIOLATION => return syscall.fail(error.AccessDenied),
15378 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
15379 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice),
15380 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
15381 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
15382 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
15383 else => |status| return syscall.unexpectedNtstatus(status),
15384 };
15385}
15386
15387fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE {
15388 {
15389 t.mutex.lock();
15390 defer t.mutex.unlock();
15391 if (t.pipe_file.handle) |handle| return handle;
15392 }
15393
15394 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'a', 'm', 'e', 'd', 'P', 'i', 'p', 'e', '\\' };
15395 var fresh_handle: windows.HANDLE = undefined;
15396 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
15397 var syscall: Syscall = try .start();
15398 while (true) switch (windows.ntdll.NtOpenFile(
15399 &fresh_handle,
15400 .{ .STANDARD = .{ .SYNCHRONIZE = true } },
15401 &.{
15402 .ObjectName = @constCast(&windows.UNICODE_STRING{
15403 .Length = @sizeOf(@TypeOf(device_path)),
15404 .MaximumLength = 0,
15405 .Buffer = @constCast(&device_path),
15406 }),
15407 },
15408 &io_status_block,
15409 .VALID_FLAGS,
15410 .{ .IO = .SYNCHRONOUS_NONALERT },
15411 )) {
15412 .SUCCESS => {
15413 syscall.finish();
15414 t.mutex.lock(); // Another thread might have won the race.
15415 defer t.mutex.unlock();
15416 if (t.pipe_file.handle) |prev_handle| {
15417 windows.CloseHandle(fresh_handle);
15418 return prev_handle;
15419 } else {
15420 t.pipe_file.handle = fresh_handle;
15421 return fresh_handle;
15422 }
15423 },
1542115424 .CANCELLED => {
1542215425 try syscall.checkCancel();
1542315426 continue;
......@@ -15449,7 +15452,7 @@ fn windowsCreateProcessPathExt(
1544915452 app_buf: *std.ArrayList(u16),
1545015453 pathext: [:0]const u16,
1545115454 cmd_line_cache: *WindowsCommandLineCache,
15452 envp_ptr: ?[*:0]const u16,
15455 env_block: ?process.Environ.WindowsBlock,
1545315456 cwd_ptr: ?[*:0]u16,
1545415457 flags: windows.CreateProcessFlags,
1545515458 lpStartupInfo: *windows.STARTUPINFOW,
......@@ -15626,7 +15629,7 @@ fn windowsCreateProcessPathExt(
1562615629 if (windowsCreateProcess(
1562715630 app_name_w.ptr,
1562815631 cmd_line_w.ptr,
15629 envp_ptr,
15632 env_block,
1563015633 cwd_ptr,
1563115634 flags,
1563215635 lpStartupInfo,
......@@ -15686,7 +15689,7 @@ fn windowsCreateProcessPathExt(
1568615689 else
1568715690 full_app_name;
1568815691
15689 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {
15692 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, env_block, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {
1569015693 return;
1569115694 } else |err| switch (err) {
1569215695 error.FileNotFound => continue,
......@@ -15710,7 +15713,7 @@ fn windowsCreateProcessPathExt(
1571015713fn windowsCreateProcess(
1571115714 app_name: [*:0]u16,
1571215715 cmd_line: [*:0]u16,
15713 env_ptr: ?[*:0]const u16,
15716 env_block: ?process.Environ.WindowsBlock,
1571415717 cwd_ptr: ?[*:0]u16,
1571515718 flags: windows.CreateProcessFlags,
1571615719 lpStartupInfo: *windows.STARTUPINFOW,
......@@ -15725,7 +15728,7 @@ fn windowsCreateProcess(
1572515728 null,
1572615729 windows.TRUE,
1572715730 flags,
15728 env_ptr,
15731 if (env_block) |block| block.slice.ptr else null,
1572915732 cwd_ptr,
1573015733 lpStartupInfo,
1573115734 lpProcessInformation,
......@@ -16246,11 +16249,11 @@ fn posixExecv(
1624616249 arg0_expand: process.ArgExpansion,
1624716250 file: [*:0]const u8,
1624816251 child_argv: [*:null]?[*:0]const u8,
16249 envp: [*:null]const ?[*:0]const u8,
16252 env_block: process.Environ.PosixBlock,
1625016253 PATH: []const u8,
1625116254) process.ReplaceError {
1625216255 const file_slice = std.mem.sliceTo(file, 0);
16253 if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, envp);
16256 if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, env_block);
1625416257
1625516258 // Use of PATH_MAX here is valid as the path_buf will be passed
1625616259 // directly to the operating system in posixExecvPath.
......@@ -16278,7 +16281,7 @@ fn posixExecv(
1627816281 .expand => child_argv[0] = full_path,
1627916282 .no_expand => {},
1628016283 }
16281 err = posixExecvPath(full_path, child_argv, envp);
16284 err = posixExecvPath(full_path, child_argv, env_block);
1628216285 switch (err) {
1628316286 error.AccessDenied => seen_eacces = true,
1628416287 error.FileNotFound, error.NotDir => {},
......@@ -16293,10 +16296,10 @@ fn posixExecv(
1629316296pub fn posixExecvPath(
1629416297 path: [*:0]const u8,
1629516298 child_argv: [*:null]const ?[*:0]const u8,
16296 envp: [*:null]const ?[*:0]const u8,
16299 env_block: process.Environ.PosixBlock,
1629716300) process.ReplaceError {
1629816301 try Thread.checkCancel();
16299 switch (posix.errno(posix.system.execve(path, child_argv, envp))) {
16302 switch (posix.errno(posix.system.execve(path, child_argv, env_block.slice.ptr))) {
1630016303 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
1630116304 .@"2BIG" => return error.SystemResources,
1630216305 .MFILE => return error.ProcessFdQuotaExceeded,
......@@ -16328,85 +16331,100 @@ pub fn posixExecvPath(
1632816331 }
1632916332}
1633016333
16331fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
16332 var rd_h: windows.HANDLE = undefined;
16333 var wr_h: windows.HANDLE = undefined;
16334 try windows.CreatePipe(&rd_h, &wr_h, sattr);
16335 errdefer windowsDestroyPipe(rd_h, wr_h);
16336 try windows.SetHandleInformation(wr_h, windows.HANDLE_FLAG_INHERIT, 0);
16337 rd.* = rd_h;
16338 wr.* = wr_h;
16339}
16340
16341fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void {
16342 if (rd) |h| posix.close(h);
16343 if (wr) |h| posix.close(h);
16344}
16345
16346fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
16347 var tmp_bufw: [128]u16 = undefined;
16348
16349 // Anonymous pipes are built upon Named pipes.
16350 // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe
16351 // Asynchronous (overlapped) read and write operations are not supported by anonymous pipes.
16352 // https://docs.microsoft.com/en-us/windows/win32/ipc/anonymous-pipe-operations
16353 const pipe_path = blk: {
16354 var tmp_buf: [128]u8 = undefined;
16355 // Forge a random path for the pipe.
16356 const pipe_path = std.fmt.bufPrintSentinel(
16357 &tmp_buf,
16358 "\\\\.\\pipe\\zig-childprocess-{d}-{d}",
16359 .{ windows.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) },
16360 0,
16361 ) catch unreachable;
16362 const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable;
16363 tmp_bufw[len] = 0;
16364 break :blk tmp_bufw[0..len :0];
16334const PipeOptions = struct {
16335 attributes: windows.OBJECT_ATTRIBUTES.ATTRIBUTES,
16336 mode: windows.FILE.MODE,
16337};
16338pub const CreatePipeOptions = struct {
16339 server: PipeOptions,
16340 client: PipeOptions,
16341 inbound: bool = false,
16342 outbound: bool = false,
16343 maximum_instances: u32 = 1,
16344 quota: u32 = 4096,
16345 default_timeout: windows.LARGE_INTEGER = -120 * std.time.ns_per_s / 100,
16346};
16347fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.HANDLE {
16348 const named_pipe_device = try t.getNamedPipeDevice();
16349 const server_handle = server_handle: {
16350 var handle: windows.HANDLE = undefined;
16351 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
16352 const syscall: Syscall = try .start();
16353 while (true) switch (windows.ntdll.NtCreateNamedPipeFile(
16354 &handle,
16355 .{
16356 .SPECIFIC = .{ .FILE_PIPE = .{
16357 .READ_DATA = options.inbound,
16358 .WRITE_DATA = options.outbound,
16359 .WRITE_ATTRIBUTES = true,
16360 } },
16361 .STANDARD = .{ .SYNCHRONIZE = true },
16362 },
16363 &.{
16364 .RootDirectory = named_pipe_device,
16365 .Attributes = options.server.attributes,
16366 },
16367 &io_status_block,
16368 .{ .READ = true, .WRITE = true },
16369 .CREATE,
16370 options.server.mode,
16371 .{ .TYPE = .BYTE_STREAM },
16372 .{ .MODE = .BYTE_STREAM },
16373 .{ .OPERATION = .QUEUE },
16374 options.maximum_instances,
16375 if (options.inbound) options.quota else 0,
16376 if (options.outbound) options.quota else 0,
16377 &options.default_timeout,
16378 )) {
16379 .SUCCESS => syscall.finish(),
16380 .CANCELLED => {
16381 try syscall.checkCancel();
16382 continue;
16383 },
16384 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
16385 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
16386 else => |status| return syscall.unexpectedNtstatus(status),
16387 };
16388 break :server_handle handle;
1636516389 };
16366
16367 // Create the read handle that can be used with overlapped IO ops.
16368 const read_handle = windows.kernel32.CreateNamedPipeW(
16369 pipe_path.ptr,
16370 windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED,
16371 windows.PIPE_TYPE_BYTE,
16372 1,
16373 4096,
16374 4096,
16375 0,
16376 sattr,
16377 );
16378 if (read_handle == windows.INVALID_HANDLE_VALUE) {
16379 switch (windows.GetLastError()) {
16380 else => |err| return windows.unexpectedError(err),
16381 }
16382 }
16383 errdefer posix.close(read_handle);
16384
16385 var sattr_copy = sattr.*;
16386 const write_handle = windows.kernel32.CreateFileW(
16387 pipe_path.ptr,
16388 .{ .GENERIC = .{ .WRITE = true } },
16389 0,
16390 &sattr_copy,
16391 windows.OPEN_EXISTING,
16392 @bitCast(windows.FILE.ATTRIBUTE{ .NORMAL = true }),
16393 null,
16394 );
16395 if (write_handle == windows.INVALID_HANDLE_VALUE) {
16396 switch (windows.GetLastError()) {
16397 else => |err| return windows.unexpectedError(err),
16398 }
16399 }
16400 errdefer posix.close(write_handle);
16401
16402 try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0);
16403
16404 rd.* = read_handle;
16405 wr.* = write_handle;
16390 errdefer windows.CloseHandle(server_handle);
16391 const client_handle = client_handle: {
16392 var handle: windows.HANDLE = undefined;
16393 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
16394 const syscall: Syscall = try .start();
16395 while (true) switch (windows.ntdll.NtOpenFile(
16396 &handle,
16397 .{
16398 .SPECIFIC = .{ .FILE_PIPE = .{
16399 .READ_DATA = options.outbound,
16400 .WRITE_DATA = options.inbound,
16401 .WRITE_ATTRIBUTES = true,
16402 } },
16403 .STANDARD = .{ .SYNCHRONIZE = true },
16404 },
16405 &.{
16406 .RootDirectory = server_handle,
16407 .Attributes = options.client.attributes,
16408 },
16409 &io_status_block,
16410 .{ .READ = true, .WRITE = true },
16411 options.client.mode,
16412 )) {
16413 .SUCCESS => syscall.finish(),
16414 .CANCELED => {
16415 try syscall.checkCancel();
16416 continue;
16417 },
16418 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
16419 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
16420 else => |status| return syscall.unexpectedNtstatus(status),
16421 };
16422 break :client_handle handle;
16423 };
16424 errdefer windows.CloseHandle(client_handle);
16425 return .{ server_handle, client_handle };
1640616426}
1640716427
16408var pipe_name_counter = std.atomic.Value(u32).init(1);
16409
1641016428fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
1641116429 const t: *Threaded = @ptrCast(@alignCast(userdata));
1641216430
......@@ -16517,7 +16535,7 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void {
1651716535 // despite the function being documented to always return TRUE
1651816536 // * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG
1651916537 // Therefore, that function is avoided in favor of using the device directly.
16520 const cng_device = try getCngHandle(t);
16538 const cng_device = try getCngDevice(t);
1652116539 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1652216540 var i: usize = 0;
1652316541 const syscall: Syscall = try .start();
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
......@@ -726,7 +726,11 @@ const WindowsThreadImpl = struct {
726726 }
727727
728728 fn join(self: Impl) void {
729 windows.WaitForSingleObjectEx(self.thread.thread_handle, windows.INFINITE, false) catch unreachable;
729 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
730 switch (windows.ntdll.NtWaitForSingleObject(self.thread.thread_handle, windows.FALSE, &infinite_timeout)) {
731 .WAIT_0 => {},
732 else => |status| windows.unexpectedStatus(status) catch unreachable,
733 }
730734 windows.CloseHandle(self.thread.thread_handle);
731735 assert(self.thread.completion.load(.seq_cst) == .completed);
732736 self.thread.free();
lib/std/os/windows.zig+18-249
......@@ -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+411-245
......@@ -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,13 +14,6 @@ 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};
23
2417/// On WASI without libc, this is `void` because the environment has to be
2518/// queried and heap-allocated at runtime.
2619///
......@@ -28,13 +21,62 @@ pub const empty: Environ = .{
2821/// is modified, so a long-lived pointer cannot be used. Therefore, on this
2922/// operating system `void` is also used.
3023pub const Block = switch (native_os) {
31 .windows => void,
24 .windows => GlobalBlock,
3225 .wasi => switch (builtin.link_libc) {
33 false => void,
34 true => [:null]const ?[*:0]const u8,
26 false => GlobalBlock,
27 true => PosixBlock,
3528 },
36 .freestanding, .other => void,
37 else => [:null]const ?[*:0]const u8,
29 .freestanding, .other => GlobalBlock,
30 else => PosixBlock,
31};
32
33pub const GlobalBlock = struct {
34 pub const global: GlobalBlock = .{};
35
36 pub fn deinit(_: GlobalBlock, _: Allocator) void {}
37};
38
39pub const PosixBlock = struct {
40 slice: [:null]const ?[*:0]const u8,
41
42 pub const empty: PosixBlock = .{ .slice = &.{} };
43
44 pub fn deinit(block: PosixBlock, gpa: Allocator) void {
45 for (block.slice) |entry| gpa.free(mem.span(entry.?));
46 gpa.free(block.slice);
47 }
48
49 pub const View = struct {
50 slice: []const [*:0]const u8,
51
52 pub fn isEmpty(v: View) bool {
53 return v.slice.len == 0;
54 }
55 };
56 pub fn view(block: PosixBlock) View {
57 return .{ .slice = @ptrCast(block.slice) };
58 }
59};
60
61pub const WindowsBlock = struct {
62 slice: [:0]const u16,
63
64 pub const empty: WindowsBlock = .{ .slice = &.{0} };
65
66 pub fn deinit(block: WindowsBlock, gpa: Allocator) void {
67 gpa.free(block.slice);
68 }
69
70 pub const View = struct {
71 ptr: [*:0]const u16,
72
73 pub fn isEmpty(v: View) bool {
74 return v.ptr[0] == 0;
75 }
76 };
77 pub fn view(block: WindowsBlock) View {
78 return .{ .ptr = block.slice.ptr };
79 }
3880};
3981
4082pub const Map = struct {
......@@ -46,47 +88,64 @@ pub const Map = struct {
4688 pub const Size = usize;
4789
4890 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
5591 pub fn hash(self: @This(), s: []const u8) u32 {
5692 _ = 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());
93 switch (native_os) {
94 else => return std.array_hash_map.hashString(s),
95 .windows => {
96 var h = std.hash.Wyhash.init(0);
97 var it = unicode.Wtf8View.initUnchecked(s).iterator();
98 while (it.nextCodepoint()) |cp| {
99 const cp_upper = if (std.math.cast(u16, cp)) |wtf16|
100 std.os.windows.toUpperWtf16(wtf16)
101 else
102 cp;
103 h.update(&[_]u8{
104 @truncate(cp_upper >> 0),
105 @truncate(cp_upper >> 8),
106 @truncate(cp_upper >> 16),
107 });
108 }
109 return @truncate(h.final());
110 },
69111 }
70 return std.array_hash_map.hashString(s);
71112 }
72113
73114 pub fn eql(self: @This(), a: []const u8, b: []const u8, b_index: usize) bool {
74115 _ = self;
75116 _ = 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);
117 return eqlKeys(a, b);
88118 }
89119 };
120 fn eqlKeys(a: []const u8, b: []const u8) bool {
121 return switch (native_os) {
122 else => std.array_hash_map.eqlString(a, b),
123 .windows => std.os.windows.eqlIgnoreCaseWtf8(a, b),
124 };
125 }
126
127 fn validateKey(key: []const u8) bool {
128 switch (builtin.mode) {
129 .Debug, .ReleaseSafe => {},
130 .ReleaseFast, .ReleaseSmall => return key.len > 0,
131 }
132 switch (native_os) {
133 else => return key.len > 0 and mem.findAny(u8, key, &.{ 0, '=' }) == null,
134 .windows => {
135 if (!unicode.wtf8ValidateSlice(key)) return false;
136 var it = unicode.Wtf8View.initUnchecked(key).iterator();
137 switch (it.nextCodepoint() orelse return false) {
138 0 => return false,
139 else => {},
140 }
141 while (it.nextCodepoint()) |cp| switch (cp) {
142 0, '=' => return false,
143 else => {},
144 };
145 return true;
146 },
147 }
148 }
90149
91150 /// Create a Map backed by a specific allocator.
92151 /// That allocator will be used for both backing allocations
......@@ -108,21 +167,65 @@ pub const Map = struct {
108167 self.* = undefined;
109168 }
110169
111 pub fn keys(m: *const Map) [][]const u8 {
112 return m.array_hash_map.keys();
170 pub fn keys(map: *const Map) [][]const u8 {
171 return map.array_hash_map.keys();
113172 }
114173
115 pub fn values(m: *const Map) [][]const u8 {
116 return m.array_hash_map.values();
174 pub fn values(map: *const Map) [][]const u8 {
175 return map.array_hash_map.values();
176 }
177
178 pub fn putPosixBlock(map: *Map, view: PosixBlock.View) Allocator.Error!void {
179 for (view.slice) |entry| {
180 var entry_i: usize = 0;
181 while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {}
182 const key = entry[0..entry_i];
183
184 var end_i: usize = entry_i;
185 while (entry[end_i] != 0) : (end_i += 1) {}
186 const value = entry[entry_i + 1 .. end_i];
187
188 try map.put(key, value);
189 }
190 }
191
192 pub fn putWindowsBlock(map: *Map, view: WindowsBlock.View) Allocator.Error!void {
193 var i: usize = 0;
194 while (view.ptr[i] != 0) {
195 const key_start = i;
196
197 // There are some special environment variables that start with =,
198 // so we need a special case to not treat = as a key/value separator
199 // if it's the first character.
200 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
201 if (view.ptr[key_start] == '=') i += 1;
202
203 while (view.ptr[i] != 0 and view.ptr[i] != '=') : (i += 1) {}
204 const key_w = view.ptr[key_start..i];
205 const key = try unicode.wtf16LeToWtf8Alloc(map.allocator, key_w);
206 errdefer map.allocator.free(key);
207
208 if (view.ptr[i] == '=') i += 1;
209
210 const value_start = i;
211 while (view.ptr[i] != 0) : (i += 1) {}
212 const value_w = view.ptr[value_start..i];
213 const value = try unicode.wtf16LeToWtf8Alloc(map.allocator, value_w);
214 errdefer map.allocator.free(value);
215
216 i += 1; // skip over null byte
217
218 try map.putMove(key, value);
219 }
117220 }
118221
119222 /// Same as `put` but the key and value become owned by the Map rather
120223 /// than being copied.
121224 /// If `putMove` fails, the ownership of key and value does not transfer.
122225 /// 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 {
226 pub fn putMove(self: *Map, key: []u8, value: []u8) Allocator.Error!void {
227 assert(validateKey(key));
124228 const gpa = self.allocator;
125 assert(unicode.wtf8ValidateSlice(key));
126229 const get_or_put = try self.array_hash_map.getOrPut(gpa, key);
127230 if (get_or_put.found_existing) {
128231 gpa.free(get_or_put.key_ptr.*);
......@@ -134,8 +237,8 @@ pub const Map = struct {
134237
135238 /// `key` and `value` are copied into the Map.
136239 /// 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));
240 pub fn put(self: *Map, key: []const u8, value: []const u8) Allocator.Error!void {
241 assert(validateKey(key));
139242 const gpa = self.allocator;
140243 const value_copy = try gpa.dupe(u8, value);
141244 errdefer gpa.free(value_copy);
......@@ -155,7 +258,7 @@ pub const Map = struct {
155258 /// The returned pointer is invalidated if the map resizes.
156259 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
157260 pub fn getPtr(self: Map, key: []const u8) ?*[]const u8 {
158 assert(unicode.wtf8ValidateSlice(key));
261 assert(validateKey(key));
159262 return self.array_hash_map.getPtr(key);
160263 }
161264
......@@ -164,11 +267,12 @@ pub const Map = struct {
164267 /// key is removed from the map.
165268 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
166269 pub fn get(self: Map, key: []const u8) ?[]const u8 {
167 assert(unicode.wtf8ValidateSlice(key));
270 assert(validateKey(key));
168271 return self.array_hash_map.get(key);
169272 }
170273
171274 pub fn contains(m: *const Map, key: []const u8) bool {
275 assert(validateKey(key));
172276 return m.array_hash_map.contains(key);
173277 }
174278
......@@ -181,7 +285,7 @@ pub const Map = struct {
181285 /// This invalidates the value returned by get() for this key.
182286 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
183287 pub fn swapRemove(self: *Map, key: []const u8) bool {
184 assert(unicode.wtf8ValidateSlice(key));
288 assert(validateKey(key));
185289 const kv = self.array_hash_map.fetchSwapRemove(key) orelse return false;
186290 const gpa = self.allocator;
187291 gpa.free(kv.key);
......@@ -198,7 +302,7 @@ pub const Map = struct {
198302 /// This invalidates the value returned by get() for this key.
199303 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
200304 pub fn orderedRemove(self: *Map, key: []const u8) bool {
201 assert(unicode.wtf8ValidateSlice(key));
305 assert(validateKey(key));
202306 const kv = self.array_hash_map.fetchOrderedRemove(key) orelse return false;
203307 const gpa = self.allocator;
204308 gpa.free(kv.key);
......@@ -233,39 +337,41 @@ pub const Map = struct {
233337
234338 /// Creates a null-delimited environment variable block in the format
235339 /// expected by POSIX, from a hash map plus options.
236 pub fn createBlockPosix(
340 pub fn createPosixBlock(
237341 map: *const Map,
238 arena: Allocator,
239 options: CreateBlockPosixOptions,
240 ) Allocator.Error![:null]?[*:0]u8 {
342 gpa: Allocator,
343 options: CreatePosixBlockOptions,
344 ) Allocator.Error!PosixBlock {
241345 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;
346 const zig_progress_action: ZigProgressAction = action: {
347 const fd = options.zig_progress_fd orelse break :action .nothing;
348 const exists = map.contains("ZIG_PROGRESS");
245349 if (fd >= 0) {
246 break :a if (exists) .edit else .add;
350 break :action if (exists) .edit else .add;
247351 } else {
248 if (exists) break :a .delete;
352 if (exists) break :action .delete;
249353 }
250 break :a .nothing;
354 break :action .nothing;
251355 };
252356
253 const envp_count: usize = c: {
254 var c: usize = map.count();
357 const envp = try gpa.allocSentinel(?[*:0]u8, len: {
358 var len: usize = map.count();
255359 switch (zig_progress_action) {
256 .add => c += 1,
257 .delete => c -= 1,
360 .add => len += 1,
361 .delete => len -= 1,
258362 .nothing, .edit => {},
259363 }
260 break :c c;
261 };
262
263 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
264 var i: usize = 0;
364 break :len len;
365 }, null);
366 var envp_len: usize = 0;
367 errdefer {
368 envp[envp_len] = null;
369 PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa);
370 }
265371
266372 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;
373 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
374 envp_len += 1;
269375 }
270376
271377 {
......@@ -275,63 +381,81 @@ pub const Map = struct {
275381 .add => unreachable,
276382 .delete => continue,
277383 .edit => {
278 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{
384 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={d}", .{
279385 pair.key_ptr.*, options.zig_progress_fd.?,
280386 }, 0);
281 i += 1;
387 envp_len += 1;
282388 continue;
283389 },
284390 .nothing => {},
285391 };
286392
287 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0);
288 i += 1;
393 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0);
394 envp_len += 1;
289395 }
290396 }
291397
292 assert(i == envp_count);
293 return envp_buf;
398 assert(envp_len == envp.len);
399 return .{ .slice = envp };
294400 }
295401
296402 /// Caller owns result.
297 pub fn createBlockWindows(map: *const Map, gpa: Allocator) error{ OutOfMemory, InvalidWtf8 }![:0]u16 {
403 pub fn createWindowsBlock(
404 map: *const Map,
405 gpa: Allocator,
406 options: CreateWindowsBlockOptions,
407 ) error{ OutOfMemory, InvalidWtf8 }!WindowsBlock {
298408 // 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;
409 const max_chars_needed = max_chars_needed: {
410 var max_chars_needed: usize = "\x00".len;
302411 var it = map.iterator();
412 if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
413 max_chars_needed += std.fmt.count("ZIG_PROGRESS={d}\x00", .{@intFromPtr(handle)});
414 };
303415 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;
416 if (options.zig_progress_handle != null and
417 eqlKeys(pair.key_ptr.*, "ZIG_PROGRESS")) continue;
418 max_chars_needed += pair.key_ptr.len + "=".len + pair.value_ptr.len + "\x00".len;
307419 }
308 break :x max_chars_needed;
420 break :max_chars_needed @max("\x00\x00".len, max_chars_needed);
309421 };
310 const result = try gpa.alloc(u16, max_chars_needed);
311 errdefer gpa.free(result);
422 const block = try gpa.alloc(u16, max_chars_needed);
423 errdefer gpa.free(block);
312424
313 var it = map.iterator();
314425 var i: usize = 0;
426 if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
427 @memcpy(
428 block[i..][0.."ZIG_PROGRESS=".len],
429 &[_]u16{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S', '=' },
430 );
431 i += "ZIG_PROGRESS=".len;
432 var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined;
433 const value = std.fmt.bufPrint(&value_buf, "{d}", .{@intFromPtr(handle)}) catch unreachable;
434 for (block[i..][0..value.len], value) |*r, v| r.* = v;
435 i += value.len;
436 block[i] = 0;
437 i += 1;
438 };
439 var it = map.iterator();
315440 while (it.next()) |pair| {
316 i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
317 result[i] = '=';
441 i += try unicode.wtf8ToWtf16Le(block[i..], pair.key_ptr.*);
442 block[i] = '=';
318443 i += 1;
319 i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
320 result[i] = 0;
444 i += try unicode.wtf8ToWtf16Le(block[i..], pair.value_ptr.*);
445 block[i] = 0;
321446 i += 1;
322447 }
323 result[i] = 0;
324 i += 1;
325448 // An empty environment is a special case that requires a redundant
326449 // NUL terminator. CreateProcess will read the second code unit even
327450 // though theoretically the first should be enough to recognize that the
328451 // environment is empty (see https://nullprogram.com/blog/2023/08/23/)
329 if (map.count() == 0) {
330 result[i] = 0;
452 for (0..2) |_| {
453 block[i] = 0;
331454 i += 1;
332 }
333 const reallocated = try gpa.realloc(result, i);
334 return reallocated[0 .. i - 1 :0];
455 if (i >= 2) break;
456 } else unreachable;
457 const reallocated = try gpa.realloc(block, i);
458 return .{ .slice = reallocated[0 .. i - 1 :0] };
335459 }
336460};
337461
......@@ -344,13 +468,14 @@ pub const CreateMapError = error{
344468
345469/// Allocates a `Map` and copies environment block into it.
346470pub 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();
352
353 if (native_os == .wasi and !builtin.link_libc) {
471 var map = Map.init(allocator);
472 errdefer map.deinit();
473 if (native_os == .windows) {
474 const peb = std.os.windows.peb();
475 assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
476 defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
477 try map.putWindowsBlock(.{ .ptr = peb.ProcessParameters.Environment });
478 } else if (native_os == .wasi and !builtin.link_libc) {
354479 var environ_count: usize = undefined;
355480 var environ_buf_size: usize = undefined;
356481
......@@ -360,7 +485,7 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
360485 }
361486
362487 if (environ_count == 0) {
363 return result;
488 return map;
364489 }
365490
366491 const environ = try allocator.alloc([*:0]u8, environ_count);
......@@ -373,63 +498,9 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
373498 return posix.unexpectedErrno(environ_get_ret);
374499 }
375500
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;
501 try map.putPosixBlock(.{ .slice = environ });
502 } else try map.putPosixBlock(env.block.view());
503 return map;
433504}
434505
435506pub const ContainsError = error{
......@@ -516,16 +587,15 @@ pub inline fn containsUnemptyConstant(environ: Environ, comptime key: []const u8
516587/// * `createMap`
517588pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 {
518589 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;
590 for (environ.block.view().slice) |entry| {
591 var entry_i: usize = 0;
592 while (entry[entry_i] != 0) : (entry_i += 1) {
593 if (entry_i == key.len) break;
594 if (entry[entry_i] != key[entry_i]) break;
525595 }
526 if ((line_i != key.len) or (line[line_i] != '=')) continue;
596 if ((entry_i != key.len) or (entry[entry_i] != '=')) continue;
527597
528 return mem.sliceTo(line + line_i + 1, 0);
598 return mem.sliceTo(entry + entry_i + 1, 0);
529599 }
530600 return null;
531601}
......@@ -548,7 +618,10 @@ pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 {
548618 const key_slice = mem.sliceTo(key, 0);
549619 if (key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') != null) return null;
550620
551 const ptr = std.os.windows.peb().ProcessParameters.Environment;
621 const peb = std.os.windows.peb();
622 assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
623 defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
624 const ptr = peb.ProcessParameters.Environment;
552625
553626 var i: usize = 0;
554627 while (ptr[i] != 0) {
......@@ -604,7 +677,7 @@ pub fn getAlloc(environ: Environ, gpa: Allocator, key: []const u8) GetAllocError
604677 return gpa.dupe(u8, val);
605678}
606679
607pub const CreateBlockPosixOptions = struct {
680pub const CreatePosixBlockOptions = struct {
608681 /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
609682 /// If non-null, negative means to remove the environment variable, and >= 0
610683 /// means to provide it with the given integer.
......@@ -613,67 +686,145 @@ pub const CreateBlockPosixOptions = struct {
613686
614687/// Creates a null-delimited environment variable block in the format expected
615688/// by POSIX, from a different one.
616pub fn createBlockPosix(
689pub fn createPosixBlock(
617690 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;
691 gpa: Allocator,
692 options: CreatePosixBlockOptions,
693) Allocator.Error!PosixBlock {
694 const contains_zig_progress = for (existing.block.view().slice) |entry| {
695 if (mem.eql(u8, mem.sliceTo(entry, '='), "ZIG_PROGRESS")) break true;
623696 } else false;
624697
625698 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;
699 const zig_progress_action: ZigProgressAction = action: {
700 const fd = options.zig_progress_fd orelse break :action .nothing;
628701 if (fd >= 0) {
629 break :a if (contains_zig_progress) .edit else .add;
702 break :action if (contains_zig_progress) .edit else .add;
630703 } else {
631 if (contains_zig_progress) break :a .delete;
704 if (contains_zig_progress) break :action .delete;
632705 }
633 break :a .nothing;
706 break :action .nothing;
634707 };
635708
636 const envp_count: usize = c: {
637 var count: usize = existing.block.len;
709 const envp = try gpa.allocSentinel(?[*:0]u8, len: {
710 var len: usize = existing.block.slice.len;
638711 switch (zig_progress_action) {
639 .add => count += 1,
640 .delete => count -= 1,
712 .add => len += 1,
713 .delete => len -= 1,
641714 .nothing, .edit => {},
642715 }
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
716 break :len len;
717 }, null);
718 var envp_len: usize = 0;
719 errdefer {
720 envp[envp_len] = null;
721 PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa);
722 }
650723 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;
724 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
725 envp_len += 1;
653726 }
654727
655 while (existing.block[existing_index]) |line| : (existing_index += 1) {
656 if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
728 var existing_index: usize = 0;
729 while (existing.block.slice[existing_index]) |entry| : (existing_index += 1) {
730 if (mem.eql(u8, mem.sliceTo(entry, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
657731 .add => unreachable,
658732 .delete => continue,
659733 .edit => {
660 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
661 i += 1;
734 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
735 envp_len += 1;
662736 continue;
663737 },
664738 .nothing => {},
665739 };
666 envp_buf[i] = try arena.dupeZ(u8, mem.span(line));
667 i += 1;
740 envp[envp_len] = try gpa.dupeZ(u8, mem.span(entry));
741 envp_len += 1;
668742 }
669743
670 assert(i == envp_count);
671 return envp_buf;
744 assert(envp_len == envp.len);
745 return .{ .slice = envp };
746}
747
748pub const CreateWindowsBlockOptions = struct {
749 /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
750 /// If non-null, `std.os.windows.INVALID_HANDLE_VALUE` means to remove the
751 /// environment variable, otherwise provide it with the given handle as an integer.
752 zig_progress_handle: ?std.os.windows.HANDLE = null,
753};
754
755/// Creates a null-delimited environment variable block in the format expected
756/// by POSIX, from a different one.
757pub fn createWindowsBlock(
758 existing: Environ,
759 gpa: Allocator,
760 options: CreateWindowsBlockOptions,
761) Allocator.Error!WindowsBlock {
762 _ = existing;
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] };
672822}
673823
674test "Map.createBlock" {
675 const allocator = testing.allocator;
676 var envmap = Map.init(allocator);
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/windows_argv/fuzz.zig+7
......@@ -149,6 +149,13 @@ 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 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
153 switch (windows.ntdll.NtWaitForSingleObject(child_proc, windows.FALSE, &infinite_timeout)) {
154 .WAIT_0 => {},
155 .ABANDONED_WAIT_0 => return error.WaitAbandoned,
156 .TIMEOUT => return error.WaitTimeOut,
157 else => |status| return windows.unexpectedStatus(status),
158 }
152159 try windows.WaitForSingleObjectEx(child_proc, windows.INFINITE, false);
153160
154161 var exit_code: windows.DWORD = undefined;