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

std.Io.Threaded: implement and cleanup windows codepaths


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

build.zig+1
...@@ -1498,6 +1498,7 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {...@@ -1498,6 +1498,7 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {
1498 defer dir.close(io);1498 defer dir.close(io);
14991499
1500 var wf = b.addWriteFiles();1500 var wf = b.addWriteFiles();
1501 b.step("test-docs", "Test code snippets from the docs").dependOn(&wf.step);
15011502
1502 var it = dir.iterateAssumeFirstIteration();1503 var it = dir.iterateAssumeFirstIteration();
1503 while (it.next(io) catch @panic("failed to read dir")) |entry| {1504 while (it.next(io) catch @panic("failed to read dir")) |entry| {
lib/std/Build/Watch.zig+7-11
...@@ -366,15 +366,7 @@ const Os = switch (builtin.os.tag) {...@@ -366,15 +366,7 @@ const Os = switch (builtin.os.tag) {
366 .MaximumLength = @intCast(path_len_bytes),366 .MaximumLength = @intCast(path_len_bytes),
367 .Buffer = @constCast(sub_path_w.span().ptr),367 .Buffer = @constCast(sub_path_w.span().ptr),
368 };368 };
369 var attr = windows.OBJECT_ATTRIBUTES{369 var iosb: windows.IO_STATUS_BLOCK = undefined;
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;
378370
379 switch (windows.ntdll.NtCreateFile(371 switch (windows.ntdll.NtCreateFile(
380 &dir_handle,372 &dir_handle,
...@@ -385,14 +377,18 @@ const Os = switch (builtin.os.tag) {...@@ -385,14 +377,18 @@ const Os = switch (builtin.os.tag) {
385 .STANDARD = .{ .SYNCHRONIZE = true },377 .STANDARD = .{ .SYNCHRONIZE = true },
386 .GENERIC = .{ .READ = true },378 .GENERIC = .{ .READ = true },
387 },379 },
388 &attr,380 &.{
389 &io,381 .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd,
382 .ObjectName = &nt_name,
383 },
384 &iosb,
390 null,385 null,
391 .{},386 .{},
392 .VALID_FLAGS,387 .VALID_FLAGS,
393 .OPEN,388 .OPEN,
394 .{389 .{
395 .DIRECTORY_FILE = true,390 .DIRECTORY_FILE = true,
391 .IO = .ASYNCHRONOUS,
396 .OPEN_FOR_BACKUP_INTENT = true,392 .OPEN_FOR_BACKUP_INTENT = true,
397 },393 },
398 null,394 null,
lib/std/Io/Threaded.zig+381-341
...@@ -76,6 +76,7 @@ environ: Environ,...@@ -76,6 +76,7 @@ environ: Environ,
7676
77null_file: NullFile = .{},77null_file: NullFile = .{},
78random_file: RandomFile = .{},78random_file: RandomFile = .{},
79pipe_file: PipeFile = .{},
7980
80csprng: Csprng = .{},81csprng: Csprng = .{},
8182
...@@ -121,7 +122,7 @@ pub const Argv0 = switch (native_os) {...@@ -121,7 +122,7 @@ pub const Argv0 = switch (native_os) {
121122
122const Environ = struct {123const Environ = struct {
123 /// Unmodified data directly from the OS.124 /// Unmodified data directly from the OS.
124 process_environ: process.Environ = .empty,125 process_environ: process.Environ,
125 /// Protected by `mutex`. Determines whether the other fields have been126 /// Protected by `mutex`. Determines whether the other fields have been
126 /// memoized based on `process_environ`.127 /// memoized based on `process_environ`.
127 initialized: bool = false,128 initialized: bool = false,
...@@ -131,13 +132,15 @@ const Environ = struct {...@@ -131,13 +132,15 @@ const Environ = struct {
131 /// Protected by `mutex`. Memoized based on `process_environ`.132 /// Protected by `mutex`. Memoized based on `process_environ`.
132 string: String = .{},133 string: String = .{},
133 /// ZIG_PROGRESS134 /// ZIG_PROGRESS
134 zig_progress_handle: std.Progress.ParentFileError!u31 = error.EnvironmentVariableMissing,135 zig_progress_file: std.Progress.ParentFileError!File = error.EnvironmentVariableMissing,
135 /// Protected by `mutex`. Tracks the problem, if any, that occurred when136 /// Protected by `mutex`. Tracks the problem, if any, that occurred when
136 /// trying to scan environment variables.137 /// trying to scan environment variables.
137 ///138 ///
138 /// Errors are only possible on WASI.139 /// Errors are only possible on WASI.
139 err: ?Error = null,140 err: ?Error = null,
140141
142 pub const empty: Environ = .{ .process_environ = .empty };
143
141 pub const Error = Allocator.Error || Io.UnexpectedError;144 pub const Error = Allocator.Error || Io.UnexpectedError;
142145
143 pub const Exist = struct {146 pub const Exist = struct {
...@@ -193,6 +196,24 @@ pub const RandomFile = switch (native_os) {...@@ -193,6 +196,24 @@ pub const RandomFile = switch (native_os) {
193 },196 },
194};197};
195198
199pub const PipeFile = switch (native_os) {
200 .windows => struct {
201 handle: ?windows.HANDLE = null,
202
203 fn deinit(this: *@This()) void {
204 if (this.handle) |handle| {
205 windows.CloseHandle(handle);
206 this.handle = null;
207 }
208 }
209 },
210 else => struct {
211 fn deinit(this: @This()) void {
212 _ = this;
213 }
214 },
215};
216
196pub const Pid = if (native_os == .linux) enum(posix.pid_t) {217pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
197 unknown = 0,218 unknown = 0,
198 _,219 _,
...@@ -1496,7 +1517,9 @@ pub const init_single_threaded: Threaded = .{...@@ -1496,7 +1517,9 @@ pub const init_single_threaded: Threaded = .{
1496 .old_sig_pipe = undefined,1517 .old_sig_pipe = undefined,
1497 .have_signal_handler = false,1518 .have_signal_handler = false,
1498 .argv0 = .empty,1519 .argv0 = .empty,
1499 .environ = .{},1520 .environ = .{ .process_environ = .{
1521 .block = if (process.Environ.Block == process.Environ.GlobalBlock) .global else .empty,
1522 } },
1500 .worker_threads = .init(null),1523 .worker_threads = .init(null),
1501 .disable_memory_mapping = false,1524 .disable_memory_mapping = false,
1502};1525};
...@@ -1531,6 +1554,7 @@ pub fn deinit(t: *Threaded) void {...@@ -1531,6 +1554,7 @@ pub fn deinit(t: *Threaded) void {
1531 }1554 }
1532 t.null_file.deinit();1555 t.null_file.deinit();
1533 t.random_file.deinit();1556 t.random_file.deinit();
1557 t.pipe_file.deinit();
1534 t.* = undefined;1558 t.* = undefined;
1535}1559}
15361560
...@@ -1573,14 +1597,7 @@ fn worker(t: *Threaded) void {...@@ -1573,14 +1597,7 @@ fn worker(t: *Threaded) void {
1573 },1597 },
1574 },1598 },
1575 },1599 },
1576 &.{1600 &.{ .ObjectName = null },
1577 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
1578 .RootDirectory = null,
1579 .ObjectName = null,
1580 .Attributes = .{},
1581 .SecurityDescriptor = null,
1582 .SecurityQualityOfService = null,
1583 },
1584 &windows.teb().ClientId,1601 &windows.teb().ClientId,
1585 ) == .SUCCESS);1602 ) == .SUCCESS);
1586 }1603 }
...@@ -3376,12 +3393,8 @@ fn dirCreateDirPathOpenWindows(...@@ -3376,12 +3393,8 @@ fn dirCreateDirPathOpenWindows(
3376 },3393 },
3377 },3394 },
3378 &.{3395 &.{
3379 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
3380 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,3396 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
3381 .Attributes = .{},
3382 .ObjectName = &nt_name,3397 .ObjectName = &nt_name,
3383 .SecurityDescriptor = null,
3384 .SecurityQualityOfService = null,
3385 },3398 },
3386 &io_status_block,3399 &io_status_block,
3387 null,3400 null,
...@@ -4063,13 +4076,9 @@ fn dirAccessWindows(...@@ -4063,13 +4076,9 @@ fn dirAccessWindows(
4063 .MaximumLength = path_len_bytes,4076 .MaximumLength = path_len_bytes,
4064 .Buffer = @constCast(sub_path_w.ptr),4077 .Buffer = @constCast(sub_path_w.ptr),
4065 };4078 };
4066 var attr: windows.OBJECT_ATTRIBUTES = .{4079 const attr: windows.OBJECT_ATTRIBUTES = .{
4067 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
4068 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,4080 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
4069 .Attributes = .{},
4070 .ObjectName = &nt_name,4081 .ObjectName = &nt_name,
4071 .SecurityDescriptor = null,
4072 .SecurityQualityOfService = null,
4073 };4082 };
4074 var basic_info: windows.FILE.BASIC_INFORMATION = undefined;4083 var basic_info: windows.FILE.BASIC_INFORMATION = undefined;
4075 const syscall: Syscall = try .start();4084 const syscall: Syscall = try .start();
...@@ -4285,14 +4294,8 @@ fn dirCreateFileWindows(...@@ -4285,14 +4294,8 @@ fn dirCreateFileWindows(
4285 .Buffer = @constCast(sub_path_w.ptr),4294 .Buffer = @constCast(sub_path_w.ptr),
4286 };4295 };
4287 const attr: windows.OBJECT_ATTRIBUTES = .{4296 const attr: windows.OBJECT_ATTRIBUTES = .{
4288 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
4289 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,4297 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
4290 .Attributes = .{
4291 .INHERIT = false,
4292 },
4293 .ObjectName = &nt_name,4298 .ObjectName = &nt_name,
4294 .SecurityDescriptor = null,
4295 .SecurityQualityOfService = null,
4296 };4299 };
4297 const create_disposition: windows.FILE.CREATE_DISPOSITION = if (flags.exclusive)4300 const create_disposition: windows.FILE.CREATE_DISPOSITION = if (flags.exclusive)
4298 .CREATE4301 .CREATE
...@@ -4905,17 +4908,6 @@ pub fn dirOpenFileWtf16(...@@ -4905,17 +4908,6 @@ pub fn dirOpenFileWtf16(
4905 .MaximumLength = path_len_bytes,4908 .MaximumLength = path_len_bytes,
4906 .Buffer = @constCast(sub_path_w.ptr),4909 .Buffer = @constCast(sub_path_w.ptr),
4907 };4910 };
4908 var attr: w.OBJECT_ATTRIBUTES = .{
4909 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
4910 .RootDirectory = dir_handle,
4911 .Attributes = .{
4912 // TODO should we set INHERIT=false?
4913 //.INHERIT = false,
4914 },
4915 .ObjectName = &nt_name,
4916 .SecurityDescriptor = null,
4917 .SecurityQualityOfService = null,
4918 };
4919 var io_status_block: w.IO_STATUS_BLOCK = undefined;4911 var io_status_block: w.IO_STATUS_BLOCK = undefined;
49204912
4921 // There are multiple kernel bugs being worked around with retries.4913 // There are multiple kernel bugs being worked around with retries.
...@@ -4934,7 +4926,10 @@ pub fn dirOpenFileWtf16(...@@ -4934,7 +4926,10 @@ pub fn dirOpenFileWtf16(
4934 .WRITE = flags.isWrite(),4926 .WRITE = flags.isWrite(),
4935 },4927 },
4936 },4928 },
4937 &attr,4929 &.{
4930 .RootDirectory = dir_handle,
4931 .ObjectName = &nt_name,
4932 },
4938 &io_status_block,4933 &io_status_block,
4939 null,4934 null,
4940 .{ .NORMAL = true },4935 .{ .NORMAL = true },
...@@ -5302,12 +5297,8 @@ pub fn dirOpenDirWindows(...@@ -5302,12 +5297,8 @@ pub fn dirOpenDirWindows(
5302 },5297 },
5303 },5298 },
5304 &.{5299 &.{
5305 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
5306 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,5300 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
5307 .Attributes = .{},
5308 .ObjectName = &nt_name,5301 .ObjectName = &nt_name,
5309 .SecurityDescriptor = null,
5310 .SecurityQualityOfService = null,
5311 },5302 },
5312 &io_status_block,5303 &io_status_block,
5313 null,5304 null,
...@@ -6517,12 +6508,8 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov...@@ -6517,12 +6508,8 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
6517 .SYNCHRONIZE = true,6508 .SYNCHRONIZE = true,
6518 } },6509 } },
6519 &.{6510 &.{
6520 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
6521 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,6511 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
6522 .Attributes = .{},
6523 .ObjectName = &nt_name,6512 .ObjectName = &nt_name,
6524 .SecurityDescriptor = null,
6525 .SecurityQualityOfService = null,
6526 },6513 },
6527 &io_status_block,6514 &io_status_block,
6528 null,6515 null,
...@@ -6531,6 +6518,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov...@@ -6531,6 +6518,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
6531 .OPEN,6518 .OPEN,
6532 .{6519 .{
6533 .DIRECTORY_FILE = remove_dir,6520 .DIRECTORY_FILE = remove_dir,
6521 .IO = .SYNCHRONOUS_NONALERT,
6534 .NON_DIRECTORY_FILE = !remove_dir,6522 .NON_DIRECTORY_FILE = !remove_dir,
6535 .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead?6523 .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead?
6536 },6524 },
...@@ -7342,14 +7330,8 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink...@@ -7342,14 +7330,8 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink
7342 .Buffer = @constCast(sub_path_w.ptr),7330 .Buffer = @constCast(sub_path_w.ptr),
7343 };7331 };
7344 const attr: windows.OBJECT_ATTRIBUTES = .{7332 const attr: windows.OBJECT_ATTRIBUTES = .{
7345 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
7346 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,7333 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
7347 .Attributes = .{
7348 .INHERIT = false,
7349 },
7350 .ObjectName = &nt_name,7334 .ObjectName = &nt_name,
7351 .SecurityDescriptor = null,
7352 .SecurityQualityOfService = null,
7353 };7335 };
7354 var io_status_block: windows.IO_STATUS_BLOCK = undefined;7336 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
7355 var result_handle: windows.HANDLE = undefined;7337 var result_handle: windows.HANDLE = undefined;
...@@ -7906,24 +7888,19 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void {...@@ -7906,24 +7888,19 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void {
7906 const t: *Threaded = @ptrCast(@alignCast(userdata));7888 const t: *Threaded = @ptrCast(@alignCast(userdata));
7907 _ = t;7889 _ = t;
79087890
7891 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
7909 const syscall: Syscall = try .start();7892 const syscall: Syscall = try .start();
7910 while (true) {7893 while (true) {
7911 if (windows.kernel32.FlushFileBuffers(file.handle) != 0) {7894 switch (windows.ntdll.NtFlushBuffersFile(file.handle, &io_status_block)) {
7912 return syscall.finish();7895 .SUCCESS => break syscall.finish(),
7913 }7896 .CANCELLED => {
7914 switch (windows.GetLastError()) {
7915 .SUCCESS => unreachable, // `FlushFileBuffers` returned nonzero
7916 .INVALID_HANDLE => unreachable,
7917 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time
7918 .UNEXP_NET_ERR => return syscall.fail(error.InputOutput),
7919 .OPERATION_ABORTED => {
7920 try syscall.checkCancel();7897 try syscall.checkCancel();
7921 continue;7898 continue;
7922 },7899 },
7923 else => |err| {7900 .INVALID_HANDLE => unreachable,
7924 syscall.finish();7901 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time
7925 return windows.unexpectedError(err);7902 .UNEXPECTED_NETWORK_ERROR => return syscall.fail(error.InputOutput),
7926 },7903 else => |status| return syscall.unexpectedNtstatus(status),
7927 }7904 }
7928 }7905 }
7929}7906}
...@@ -14556,22 +14533,39 @@ fn scanEnviron(t: *Threaded) void {...@@ -14556,22 +14533,39 @@ fn scanEnviron(t: *Threaded) void {
14556 comptime assert(@sizeOf(Environ.String) == 0);14533 comptime assert(@sizeOf(Environ.String) == 0);
14557 }14534 }
14558 } else {14535 } else {
14559 for (t.environ.process_environ.block) |opt_line| {14536 for (t.environ.process_environ.block.slice) |opt_entry| {
14560 const line = opt_line.?;14537 const entry = opt_entry.?;
14561 var line_i: usize = 0;14538 var entry_i: usize = 0;
14562 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}14539 while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {}
14563 const key = line[0..line_i];14540 const key = entry[0..entry_i];
1456414541
14565 var end_i: usize = line_i;14542 var end_i: usize = entry_i;
14566 while (line[end_i] != 0) : (end_i += 1) {}14543 while (entry[end_i] != 0) : (end_i += 1) {}
14567 const value = line[line_i + 1 .. end_i :0];14544 const value = entry[entry_i + 1 .. end_i :0];
1456814545
14569 if (std.mem.eql(u8, key, "NO_COLOR")) {14546 if (std.mem.eql(u8, key, "NO_COLOR")) {
14570 t.environ.exist.NO_COLOR = true;14547 t.environ.exist.NO_COLOR = true;
14571 } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) {14548 } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) {
14572 t.environ.exist.CLICOLOR_FORCE = true;14549 t.environ.exist.CLICOLOR_FORCE = true;
14573 } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) {14550 } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) {
14574 t.environ.zig_progress_handle = std.fmt.parseInt(u31, value, 10) catch error.UnrecognizedFormat;14551 t.environ.zig_progress_file = file: {
14552 const int = std.fmt.parseInt(switch (@typeInfo(File.Handle)) {
14553 .int => |int_info| @Int(
14554 .unsigned,
14555 int_info.bits - @intFromBool(int_info.signedness == .signed),
14556 ),
14557 .pointer => usize,
14558 else => break :file error.UnsupportedOperation,
14559 }, value, 10) catch break :file error.UnrecognizedFormat;
14560 break :file .{
14561 .handle = switch (@typeInfo(File.Handle)) {
14562 .int => int,
14563 .pointer => @ptrFromInt(int),
14564 else => comptime unreachable,
14565 },
14566 .flags = .{ .nonblocking = true },
14567 };
14568 };
14575 } else inline for (@typeInfo(Environ.String).@"struct".fields) |field| {14569 } else inline for (@typeInfo(Environ.String).@"struct".fields) |field| {
14576 if (std.mem.eql(u8, key, field.name)) @field(t.environ.string, field.name) = value;14570 if (std.mem.eql(u8, key, field.name)) @field(t.environ.string, field.name) = value;
14577 }14571 }
...@@ -14594,19 +14588,17 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces...@@ -14594,19 +14588,17 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
14594 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);14588 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);
14595 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;14589 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
1459614590
14597 const envp: [*:null]const ?[*:0]const u8 = m: {14591 const env_block = env_block: {
14598 const prog_fd: i32 = -1;14592 const prog_fd: i32 = -1;
14599 if (options.environ_map) |environ_map| {14593 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
14600 break :m (try environ_map.createBlockPosix(arena, .{
14601 .zig_progress_fd = prog_fd,
14602 })).ptr;
14603 }
14604 break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{
14605 .zig_progress_fd = prog_fd,14594 .zig_progress_fd = prog_fd,
14606 })).ptr;14595 });
14596 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{
14597 .zig_progress_fd = prog_fd,
14598 });
14607 };14599 };
1460814600
14609 return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH);14601 return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
14610}14602}
1461114603
14612fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError {14604fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError {
...@@ -14705,16 +14697,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp...@@ -14705,16 +14697,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
14705 const prog_fileno = 3;14697 const prog_fileno = 3;
14706 comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno);14698 comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno);
1470714699
14708 const envp: [*:null]const ?[*:0]const u8 = m: {14700 const env_block = env_block: {
14709 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;14701 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
14710 if (options.environ_map) |environ_map| {14702 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
14711 break :m (try environ_map.createBlockPosix(arena, .{
14712 .zig_progress_fd = prog_fd,
14713 })).ptr;
14714 }
14715 break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{
14716 .zig_progress_fd = prog_fd,14703 .zig_progress_fd = prog_fd,
14717 })).ptr;14704 });
14705 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{
14706 .zig_progress_fd = prog_fd,
14707 });
14718 };14708 };
1471914709
14720 // This pipe communicates to the parent errors in the child between `fork` and `execvpe`.14710 // This pipe communicates to the parent errors in the child between `fork` and `execvpe`.
...@@ -14797,7 +14787,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp...@@ -14797,7 +14787,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
14797 }14787 }
14798 }14788 }
1479914789
14800 const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH);14790 const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
14801 forkBail(ep1, err);14791 forkBail(ep1, err);
14802 }14792 }
1480314793
...@@ -14811,7 +14801,6 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp...@@ -14811,7 +14801,6 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
14811 if (options.stderr == .pipe) posix.close(stderr_pipe[1]);14801 if (options.stderr == .pipe) posix.close(stderr_pipe[1]);
1481214802
14813 if (prog_pipe[1] != -1) posix.close(prog_pipe[1]);14803 if (prog_pipe[1] != -1) posix.close(prog_pipe[1]);
14814
14815 options.progress_node.setIpcFd(prog_pipe[0]);14804 options.progress_node.setIpcFd(prog_pipe[0]);
1481614805
14817 return .{14806 return .{
...@@ -14935,42 +14924,44 @@ fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT...@@ -14935,42 +14924,44 @@ fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT
14935 // some rare edge cases where our process handle no longer has the14924 // some rare edge cases where our process handle no longer has the
14936 // PROCESS_TERMINATE access right, so let's do another check to make14925 // PROCESS_TERMINATE access right, so let's do another check to make
14937 // sure the process is really no longer running:14926 // sure the process is really no longer running:
14938 windows.WaitForSingleObjectEx(handle, 0, false) catch return error.AccessDenied;14927 const minimal_timeout: windows.LARGE_INTEGER = -1;
14939 return error.AlreadyTerminated;14928 switch (windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &minimal_timeout)) {
14929 .SUCCESS => return error.AlreadyTerminated,
14930 else => return error.AccessDenied,
14931 }
14940 },14932 },
14941 else => |err| return windows.unexpectedError(err),14933 else => |err| return windows.unexpectedError(err),
14942 }14934 }
14943 }14935 }
14944 _ = windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE);14936 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
14937 _ = windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &infinite_timeout);
14945 childCleanupWindows(child);14938 childCleanupWindows(child);
14946}14939}
1494714940
14948fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term {14941fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term {
14949 const handle = child.id.?;14942 const handle = child.id.?;
1495014943
14951 const syscall: Syscall = try .start();14944 const alertable_syscall: AlertableSyscall = try .start();
14952 while (true) switch (windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE)) {14945 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
14953 windows.WAIT_OBJECT_0 => break syscall.finish(),14946 while (true) switch (windows.ntdll.NtWaitForSingleObject(handle, windows.TRUE, &infinite_timeout)) {
14954 windows.WAIT_ABANDONED, windows.WAIT_TIMEOUT => {14947 windows.NTSTATUS.WAIT_0 => break alertable_syscall.finish(),
14955 try syscall.checkCancel();14948 .USER_APC, .ALERTED, .TIMEOUT => {
14949 try alertable_syscall.checkCancel();
14956 continue;14950 continue;
14957 },14951 },
14958 windows.WAIT_FAILED => {14952 else => |status| return alertable_syscall.unexpectedNtstatus(status),
14959 syscall.finish();
14960 switch (windows.GetLastError()) {
14961 else => |err| return windows.unexpectedError(err),
14962 }
14963 },
14964 else => return syscall.fail(error.Unexpected),
14965 };14953 };
1496614954
14967 const term: process.Child.Term = x: {14955 var info: windows.PROCESS_BASIC_INFORMATION = undefined;
14968 var exit_code: windows.DWORD = undefined;14956 const term: process.Child.Term = switch (windows.ntdll.NtQueryInformationProcess(
14969 if (windows.kernel32.GetExitCodeProcess(handle, &exit_code) == 0) {14957 handle,
14970 break :x .{ .unknown = 0 };14958 .BasicInformation,
14971 } else {14959 &info,
14972 break :x .{ .exited = @as(u8, @truncate(exit_code)) };14960 @sizeOf(windows.PROCESS_BASIC_INFORMATION),
14973 }14961 null,
14962 )) {
14963 .SUCCESS => .{ .exited = @as(u8, @truncate(@intFromEnum(info.ExitStatus))) },
14964 else => .{ .unknown = 0 },
14974 };14965 };
1497514966
14976 childCleanupWindows(child);14967 childCleanupWindows(child);
...@@ -15233,88 +15224,70 @@ fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32...@@ -15233,88 +15224,70 @@ fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32
15233fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {15224fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
15234 const t: *Threaded = @ptrCast(@alignCast(userdata));15225 const t: *Threaded = @ptrCast(@alignCast(userdata));
1523515226
15236 var saAttr: windows.SECURITY_ATTRIBUTES = .{
15237 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
15238 .bInheritHandle = windows.TRUE,
15239 .lpSecurityDescriptor = null,
15240 };
15241
15242 const any_ignore =15227 const any_ignore =
15243 options.stdin == .ignore or15228 options.stdin == .ignore or
15244 options.stdout == .ignore or15229 options.stdout == .ignore or
15245 options.stderr == .ignore;15230 options.stderr == .ignore;
1524615231 const nul_handle = if (any_ignore) try getNulDevice(t) else undefined;
15247 const nul_handle = if (any_ignore) try getNulHandle(t) else undefined;15232
1524815233 const any_inherit =
15249 var g_hChildStd_IN_Rd: ?windows.HANDLE = null;15234 options.stdin == .inherit or
15250 var g_hChildStd_IN_Wr: ?windows.HANDLE = null;15235 options.stdout == .inherit or
15251 switch (options.stdin) {15236 options.stderr == .inherit;
15252 .pipe => {15237 const peb = if (any_inherit) windows.peb() else undefined;
15253 try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr);15238
15254 },15239 const stdin_pipe = if (options.stdin == .pipe) try t.windowsCreatePipe(.{
15255 .ignore => {15240 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15256 g_hChildStd_IN_Rd = nul_handle;15241 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15257 },15242 .outbound = true,
15258 .inherit => {15243 }) else undefined;
15259 g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE) catch null;15244 errdefer if (options.stdin == .pipe) for (stdin_pipe) |handle| windows.CloseHandle(handle);
15260 },15245
15261 .close => {15246 const stdout_pipe = if (options.stdout == .pipe) try t.windowsCreatePipe(.{
15262 g_hChildStd_IN_Rd = null;15247 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15263 },15248 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15264 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),15249 .inbound = true,
15265 }15250 }) else undefined;
15266 errdefer if (options.stdin == .pipe) {15251 errdefer if (options.stdout == .pipe) for (stdout_pipe) |handle| windows.CloseHandle(handle);
15267 windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr);15252
15268 };15253 const stderr_pipe = if (options.stderr == .pipe) try t.windowsCreatePipe(.{
1526915254 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15270 var g_hChildStd_OUT_Rd: ?windows.HANDLE = null;15255 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15271 var g_hChildStd_OUT_Wr: ?windows.HANDLE = null;15256 .inbound = true,
15272 switch (options.stdout) {15257 }) else undefined;
15273 .pipe => {15258 errdefer if (options.stderr == .pipe) for (stderr_pipe) |handle| windows.CloseHandle(handle);
15274 try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr);15259
15275 },15260 const prog_pipe = if (options.progress_node.index != .none) try t.windowsCreatePipe(.{
15276 .ignore => {15261 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15277 g_hChildStd_OUT_Wr = nul_handle;15262 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15278 },15263 .inbound = true,
15279 .inherit => {15264 }) else undefined;
15280 g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) catch null;15265 errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle);
15281 },
15282 .close => {
15283 g_hChildStd_OUT_Wr = null;
15284 },
15285 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
15286 }
15287 errdefer if (options.stdout == .pipe) {
15288 windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr);
15289 };
15290
15291 var g_hChildStd_ERR_Rd: ?windows.HANDLE = null;
15292 var g_hChildStd_ERR_Wr: ?windows.HANDLE = null;
15293 switch (options.stderr) {
15294 .pipe => {
15295 try windowsMakeAsyncPipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr);
15296 },
15297 .ignore => {
15298 g_hChildStd_ERR_Wr = nul_handle;
15299 },
15300 .inherit => {
15301 g_hChildStd_ERR_Wr = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null;
15302 },
15303 .close => {
15304 g_hChildStd_ERR_Wr = null;
15305 },
15306 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
15307 }
15308 errdefer if (options.stderr == .pipe) {
15309 windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr);
15310 };
1531115266
15312 var siStartInfo: windows.STARTUPINFOW = .{15267 var siStartInfo: windows.STARTUPINFOW = .{
15313 .cb = @sizeOf(windows.STARTUPINFOW),15268 .cb = @sizeOf(windows.STARTUPINFOW),
15314 .hStdError = g_hChildStd_ERR_Wr,
15315 .hStdOutput = g_hChildStd_OUT_Wr,
15316 .hStdInput = g_hChildStd_IN_Rd,
15317 .dwFlags = windows.STARTF_USESTDHANDLES,15269 .dwFlags = windows.STARTF_USESTDHANDLES,
15270 .hStdInput = switch (options.stdin) {
15271 .inherit => peb.ProcessParameters.hStdInput,
15272 .file => |file| file.handle,
15273 .ignore => nul_handle,
15274 .pipe => stdin_pipe[1],
15275 .close => null,
15276 },
15277 .hStdOutput = switch (options.stdout) {
15278 .inherit => peb.ProcessParameters.hStdOutput,
15279 .file => |file| file.handle,
15280 .ignore => nul_handle,
15281 .pipe => stdout_pipe[1],
15282 .close => null,
15283 },
15284 .hStdError = switch (options.stderr) {
15285 .inherit => peb.ProcessParameters.hStdError,
15286 .file => |file| file.handle,
15287 .ignore => nul_handle,
15288 .pipe => stderr_pipe[1],
15289 .close => null,
15290 },
1531815291
15319 .lpReserved = null,15292 .lpReserved = null,
15320 .lpDesktop = null,15293 .lpDesktop = null,
...@@ -15360,8 +15333,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -15360,8 +15333,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
15360 };15333 };
15361 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;15334 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
1536215335
15363 const maybe_envp_buf = if (options.environ_map) |environ_map| try environ_map.createBlockWindows(arena) else null;15336 const env_block = env_block: {
15364 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;15337 const prog_handle = if (options.progress_node.index != .none)
15338 prog_pipe[1]
15339 else
15340 windows.INVALID_HANDLE_VALUE;
15341 if (options.environ_map) |environ_map| break :env_block try environ_map.createWindowsBlock(arena, .{
15342 .zig_progress_handle = prog_handle,
15343 });
15344 break :env_block try t.environ.process_environ.createWindowsBlock(arena, .{
15345 .zig_progress_handle = if (options.progress_node.index != .none) prog_pipe[1] else windows.INVALID_HANDLE_VALUE,
15346 });
15347 };
1536515348
15366 const app_name_wtf8 = options.argv[0];15349 const app_name_wtf8 = options.argv[0];
15367 const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8);15350 const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8);
...@@ -15436,7 +15419,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -15436,7 +15419,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
15436 &app_buf,15419 &app_buf,
15437 PATHEXT,15420 PATHEXT,
15438 &cmd_line_cache,15421 &cmd_line_cache,
15439 envp_ptr,15422 env_block,
15440 cwd_w_ptr,15423 cwd_w_ptr,
15441 flags,15424 flags,
15442 &siStartInfo,15425 &siStartInfo,
...@@ -15471,7 +15454,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -15471,7 +15454,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
15471 &app_buf,15454 &app_buf,
15472 PATHEXT,15455 PATHEXT,
15473 &cmd_line_cache,15456 &cmd_line_cache,
15474 envp_ptr,15457 env_block,
15475 cwd_w_ptr,15458 cwd_w_ptr,
15476 flags,15459 flags,
15477 &siStartInfo,15460 &siStartInfo,
...@@ -15491,21 +15474,40 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -15491,21 +15474,40 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
15491 };15474 };
15492 }15475 }
1549315476
15494 if (options.stdin == .pipe) windows.CloseHandle(g_hChildStd_IN_Rd.?);15477 if (options.progress_node.index != .none) {
15495 if (options.stderr == .pipe) windows.CloseHandle(g_hChildStd_ERR_Wr.?);15478 windows.CloseHandle(prog_pipe[1]);
15496 if (options.stdout == .pipe) windows.CloseHandle(g_hChildStd_OUT_Wr.?);15479 options.progress_node.setIpcFd(prog_pipe[0]);
15480 }
1549715481
15498 return .{15482 return .{
15499 .id = piProcInfo.hProcess,15483 .id = piProcInfo.hProcess,
15500 .thread_handle = piProcInfo.hThread,15484 .thread_handle = piProcInfo.hThread,
15501 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = false } } else null,15485 .stdin = stdin: switch (options.stdin) {
15502 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,15486 .pipe => {
15503 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,15487 windows.CloseHandle(stdin_pipe[1]);
15488 break :stdin .{ .handle = stdin_pipe[0], .flags = .{ .nonblocking = false } };
15489 },
15490 else => null,
15491 },
15492 .stdout = stdout: switch (options.stdout) {
15493 .pipe => {
15494 windows.CloseHandle(stdout_pipe[1]);
15495 break :stdout .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = true } };
15496 },
15497 else => null,
15498 },
15499 .stderr = stderr: switch (options.stderr) {
15500 .pipe => {
15501 windows.CloseHandle(stderr_pipe[1]);
15502 break :stderr .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = true } };
15503 },
15504 else => null,
15505 },
15504 .request_resource_usage_statistics = options.request_resource_usage_statistics,15506 .request_resource_usage_statistics = options.request_resource_usage_statistics,
15505 };15507 };
15506}15508}
1550715509
15508fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {15510fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
15509 {15511 {
15510 mutexLock(&t.mutex);15512 mutexLock(&t.mutex);
15511 defer mutexUnlock(&t.mutex);15513 defer mutexUnlock(&t.mutex);
...@@ -15513,12 +15515,6 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {...@@ -15513,12 +15515,6 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
15513 }15515 }
1551415516
15515 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' };15517 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' };
15516
15517 var nt_name: windows.UNICODE_STRING = .{
15518 .Length = device_path.len * 2,
15519 .MaximumLength = 0,
15520 .Buffer = @constCast(&device_path),
15521 };
15522 var fresh_handle: windows.HANDLE = undefined;15518 var fresh_handle: windows.HANDLE = undefined;
15523 var io_status_block: windows.IO_STATUS_BLOCK = undefined;15519 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
15524 var syscall: Syscall = try .start();15520 var syscall: Syscall = try .start();
...@@ -15529,12 +15525,11 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {...@@ -15529,12 +15525,11 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
15529 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } },15525 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } },
15530 },15526 },
15531 &.{15527 &.{
15532 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),15528 .ObjectName = @constCast(&windows.UNICODE_STRING{
15533 .RootDirectory = null,15529 .Length = @sizeOf(@TypeOf(device_path)),
15534 .ObjectName = &nt_name,15530 .MaximumLength = 0,
15535 .Attributes = .{},15531 .Buffer = @constCast(&device_path),
15536 .SecurityDescriptor = null,15532 }),
15537 .SecurityQualityOfService = null,
15538 },15533 },
15539 &io_status_block,15534 &io_status_block,
15540 .VALID_FLAGS,15535 .VALID_FLAGS,
...@@ -15561,7 +15556,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {...@@ -15561,7 +15556,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
15561 };15556 };
15562}15557}
1556315558
15564fn getNulHandle(t: *Threaded) !windows.HANDLE {15559fn getNulDevice(t: *Threaded) !windows.HANDLE {
15565 {15560 {
15566 mutexLock(&t.mutex);15561 mutexLock(&t.mutex);
15567 defer mutexUnlock(&t.mutex);15562 defer mutexUnlock(&t.mutex);
...@@ -15569,44 +15564,26 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE {...@@ -15569,44 +15564,26 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE {
15569 }15564 }
1557015565
15571 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' };15566 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' };
15572 var nt_name: windows.UNICODE_STRING = .{
15573 .Length = device_path.len * 2,
15574 .MaximumLength = 0,
15575 .Buffer = @constCast(&device_path),
15576 };
15577 const attr: windows.OBJECT_ATTRIBUTES = .{
15578 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
15579 .RootDirectory = null,
15580 .Attributes = .{
15581 .INHERIT = true,
15582 },
15583 .ObjectName = &nt_name,
15584 .SecurityDescriptor = null,
15585 .SecurityQualityOfService = null,
15586 };
15587 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
15588 var fresh_handle: windows.HANDLE = undefined;15567 var fresh_handle: windows.HANDLE = undefined;
15568 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
15589 var syscall: Syscall = try .start();15569 var syscall: Syscall = try .start();
15590 while (true) switch (windows.ntdll.NtCreateFile(15570 while (true) switch (windows.ntdll.NtOpenFile(
15591 &fresh_handle,15571 &fresh_handle,
15592 .{15572 .{
15593 .STANDARD = .{ .SYNCHRONIZE = true },15573 .STANDARD = .{ .SYNCHRONIZE = true },
15594 .GENERIC = .{ .WRITE = true, .READ = true },15574 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true, .WRITE_DATA = true } },
15575 },
15576 &.{
15577 .Attributes = .{ .INHERIT = true },
15578 .ObjectName = @constCast(&windows.UNICODE_STRING{
15579 .Length = @sizeOf(@TypeOf(device_path)),
15580 .MaximumLength = 0,
15581 .Buffer = @constCast(&device_path),
15582 }),
15595 },15583 },
15596 &attr,
15597 &io_status_block,15584 &io_status_block,
15598 null,
15599 .{ .NORMAL = true },
15600 .VALID_FLAGS,15585 .VALID_FLAGS,
15601 .OPEN,15586 .{ .IO = .SYNCHRONOUS_NONALERT },
15602 .{
15603 .DIRECTORY_FILE = false,
15604 .NON_DIRECTORY_FILE = true,
15605 .IO = .SYNCHRONOUS_NONALERT,
15606 .OPEN_REPARSE_POINT = false,
15607 },
15608 null,
15609 0,
15610 )) {15587 )) {
15611 .SUCCESS => {15588 .SUCCESS => {
15612 syscall.finish();15589 syscall.finish();
...@@ -15620,6 +15597,64 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE {...@@ -15620,6 +15597,64 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE {
15620 return fresh_handle;15597 return fresh_handle;
15621 }15598 }
15622 },15599 },
15600 .CANCELLED => {
15601 try syscall.checkCancel();
15602 continue;
15603 },
15604 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
15605 .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status),
15606 .INVALID_HANDLE => |status| return syscall.ntstatusBug(status),
15607 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
15608 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
15609 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
15610 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
15611 .SHARING_VIOLATION => return syscall.fail(error.AccessDenied),
15612 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
15613 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice),
15614 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
15615 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
15616 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
15617 else => |status| return syscall.unexpectedNtstatus(status),
15618 };
15619}
15620
15621fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE {
15622 {
15623 mutexLock(&t.mutex);
15624 defer mutexUnlock(&t.mutex);
15625 if (t.pipe_file.handle) |handle| return handle;
15626 }
15627
15628 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'a', 'm', 'e', 'd', 'P', 'i', 'p', 'e', '\\' };
15629 var fresh_handle: windows.HANDLE = undefined;
15630 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
15631 var syscall: Syscall = try .start();
15632 while (true) switch (windows.ntdll.NtOpenFile(
15633 &fresh_handle,
15634 .{ .STANDARD = .{ .SYNCHRONIZE = true } },
15635 &.{
15636 .ObjectName = @constCast(&windows.UNICODE_STRING{
15637 .Length = @sizeOf(@TypeOf(device_path)),
15638 .MaximumLength = 0,
15639 .Buffer = @constCast(&device_path),
15640 }),
15641 },
15642 &io_status_block,
15643 .VALID_FLAGS,
15644 .{ .IO = .SYNCHRONOUS_NONALERT },
15645 )) {
15646 .SUCCESS => {
15647 syscall.finish();
15648 mutexLock(&t.mutex); // Another thread might have won the race.
15649 defer mutexUnlock(&t.mutex);
15650 if (t.pipe_file.handle) |prev_handle| {
15651 windows.CloseHandle(fresh_handle);
15652 return prev_handle;
15653 } else {
15654 t.pipe_file.handle = fresh_handle;
15655 return fresh_handle;
15656 }
15657 },
15623 .DELETE_PENDING => {15658 .DELETE_PENDING => {
15624 // This error means that there *was* a file in this location on15659 // This error means that there *was* a file in this location on
15625 // the file system, but it was deleted. However, the OS is not15660 // the file system, but it was deleted. However, the OS is not
...@@ -15666,7 +15701,7 @@ fn windowsCreateProcessPathExt(...@@ -15666,7 +15701,7 @@ fn windowsCreateProcessPathExt(
15666 app_buf: *std.ArrayList(u16),15701 app_buf: *std.ArrayList(u16),
15667 pathext: [:0]const u16,15702 pathext: [:0]const u16,
15668 cmd_line_cache: *WindowsCommandLineCache,15703 cmd_line_cache: *WindowsCommandLineCache,
15669 envp_ptr: ?[*:0]const u16,15704 env_block: ?process.Environ.WindowsBlock,
15670 cwd_ptr: ?[*:0]u16,15705 cwd_ptr: ?[*:0]u16,
15671 flags: windows.CreateProcessFlags,15706 flags: windows.CreateProcessFlags,
15672 lpStartupInfo: *windows.STARTUPINFOW,15707 lpStartupInfo: *windows.STARTUPINFOW,
...@@ -15843,7 +15878,7 @@ fn windowsCreateProcessPathExt(...@@ -15843,7 +15878,7 @@ fn windowsCreateProcessPathExt(
15843 if (windowsCreateProcess(15878 if (windowsCreateProcess(
15844 app_name_w.ptr,15879 app_name_w.ptr,
15845 cmd_line_w.ptr,15880 cmd_line_w.ptr,
15846 envp_ptr,15881 env_block,
15847 cwd_ptr,15882 cwd_ptr,
15848 flags,15883 flags,
15849 lpStartupInfo,15884 lpStartupInfo,
...@@ -15903,7 +15938,7 @@ fn windowsCreateProcessPathExt(...@@ -15903,7 +15938,7 @@ fn windowsCreateProcessPathExt(
15903 else15938 else
15904 full_app_name;15939 full_app_name;
1590515940
15906 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {15941 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, env_block, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {
15907 return;15942 return;
15908 } else |err| switch (err) {15943 } else |err| switch (err) {
15909 error.FileNotFound => continue,15944 error.FileNotFound => continue,
...@@ -15927,7 +15962,7 @@ fn windowsCreateProcessPathExt(...@@ -15927,7 +15962,7 @@ fn windowsCreateProcessPathExt(
15927fn windowsCreateProcess(15962fn windowsCreateProcess(
15928 app_name: [*:0]u16,15963 app_name: [*:0]u16,
15929 cmd_line: [*:0]u16,15964 cmd_line: [*:0]u16,
15930 env_ptr: ?[*:0]const u16,15965 env_block: ?process.Environ.WindowsBlock,
15931 cwd_ptr: ?[*:0]u16,15966 cwd_ptr: ?[*:0]u16,
15932 flags: windows.CreateProcessFlags,15967 flags: windows.CreateProcessFlags,
15933 lpStartupInfo: *windows.STARTUPINFOW,15968 lpStartupInfo: *windows.STARTUPINFOW,
...@@ -15942,7 +15977,7 @@ fn windowsCreateProcess(...@@ -15942,7 +15977,7 @@ fn windowsCreateProcess(
15942 null,15977 null,
15943 windows.TRUE,15978 windows.TRUE,
15944 flags,15979 flags,
15945 env_ptr,15980 if (env_block) |block| block.slice.ptr else null,
15946 cwd_ptr,15981 cwd_ptr,
15947 lpStartupInfo,15982 lpStartupInfo,
15948 lpProcessInformation,15983 lpProcessInformation,
...@@ -16463,11 +16498,11 @@ fn posixExecv(...@@ -16463,11 +16498,11 @@ fn posixExecv(
16463 arg0_expand: process.ArgExpansion,16498 arg0_expand: process.ArgExpansion,
16464 file: [*:0]const u8,16499 file: [*:0]const u8,
16465 child_argv: [*:null]?[*:0]const u8,16500 child_argv: [*:null]?[*:0]const u8,
16466 envp: [*:null]const ?[*:0]const u8,16501 env_block: process.Environ.PosixBlock,
16467 PATH: []const u8,16502 PATH: []const u8,
16468) process.ReplaceError {16503) process.ReplaceError {
16469 const file_slice = std.mem.sliceTo(file, 0);16504 const file_slice = std.mem.sliceTo(file, 0);
16470 if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, envp);16505 if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, env_block);
1647116506
16472 // Use of PATH_MAX here is valid as the path_buf will be passed16507 // Use of PATH_MAX here is valid as the path_buf will be passed
16473 // directly to the operating system in posixExecvPath.16508 // directly to the operating system in posixExecvPath.
...@@ -16495,7 +16530,7 @@ fn posixExecv(...@@ -16495,7 +16530,7 @@ fn posixExecv(
16495 .expand => child_argv[0] = full_path,16530 .expand => child_argv[0] = full_path,
16496 .no_expand => {},16531 .no_expand => {},
16497 }16532 }
16498 err = posixExecvPath(full_path, child_argv, envp);16533 err = posixExecvPath(full_path, child_argv, env_block);
16499 switch (err) {16534 switch (err) {
16500 error.AccessDenied => seen_eacces = true,16535 error.AccessDenied => seen_eacces = true,
16501 error.FileNotFound, error.NotDir => {},16536 error.FileNotFound, error.NotDir => {},
...@@ -16510,10 +16545,10 @@ fn posixExecv(...@@ -16510,10 +16545,10 @@ fn posixExecv(
16510pub fn posixExecvPath(16545pub fn posixExecvPath(
16511 path: [*:0]const u8,16546 path: [*:0]const u8,
16512 child_argv: [*:null]const ?[*:0]const u8,16547 child_argv: [*:null]const ?[*:0]const u8,
16513 envp: [*:null]const ?[*:0]const u8,16548 env_block: process.Environ.PosixBlock,
16514) process.ReplaceError {16549) process.ReplaceError {
16515 try Thread.checkCancel();16550 try Thread.checkCancel();
16516 switch (posix.errno(posix.system.execve(path, child_argv, envp))) {16551 switch (posix.errno(posix.system.execve(path, child_argv, env_block.slice.ptr))) {
16517 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.16552 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
16518 .@"2BIG" => return error.SystemResources,16553 .@"2BIG" => return error.SystemResources,
16519 .MFILE => return error.ProcessFdQuotaExceeded,16554 .MFILE => return error.ProcessFdQuotaExceeded,
...@@ -16545,100 +16580,105 @@ pub fn posixExecvPath(...@@ -16545,100 +16580,105 @@ pub fn posixExecvPath(
16545 }16580 }
16546}16581}
1654716582
16548fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {16583pub const CreatePipeOptions = struct {
16549 var rd_h: windows.HANDLE = undefined;16584 server: End,
16550 var wr_h: windows.HANDLE = undefined;16585 client: End,
16551 try windows.CreatePipe(&rd_h, &wr_h, sattr);16586 inbound: bool = false,
16552 errdefer windowsDestroyPipe(rd_h, wr_h);16587 outbound: bool = false,
16553 try windows.SetHandleInformation(wr_h, windows.HANDLE_FLAG_INHERIT, 0);16588 maximum_instances: u32 = 1,
16554 rd.* = rd_h;16589 quota: u32 = 4096,
16555 wr.* = wr_h;16590 default_timeout: windows.LARGE_INTEGER = -120 * std.time.ns_per_s / 100,
16556}
16557
16558fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void {
16559 if (rd) |h| posix.close(h);
16560 if (wr) |h| posix.close(h);
16561}
16562
16563fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
16564 var tmp_bufw: [128]u16 = undefined;
1656516591
16566 // Anonymous pipes are built upon Named pipes.16592 pub const End = struct {
16567 // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe16593 attributes: windows.OBJECT_ATTRIBUTES.ATTRIBUTES = .{},
16568 // Asynchronous (overlapped) read and write operations are not supported by anonymous pipes.16594 mode: windows.FILE.MODE,
16569 // https://docs.microsoft.com/en-us/windows/win32/ipc/anonymous-pipe-operations
16570 const pipe_path = blk: {
16571 var tmp_buf: [128]u8 = undefined;
16572 // Forge a random path for the pipe.
16573 const pipe_path = std.fmt.bufPrintSentinel(
16574 &tmp_buf,
16575 "\\\\.\\pipe\\zig-childprocess-{d}-{d}",
16576 .{ windows.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) },
16577 0,
16578 ) catch unreachable;
16579 const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable;
16580 tmp_bufw[len] = 0;
16581 break :blk tmp_bufw[0..len :0];
16582 };16595 };
1658316596};
16584 // Create the read handle that can be used with overlapped IO ops.16597pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.HANDLE {
16585 const read_handle = windows.kernel32.CreateNamedPipeW(16598 const named_pipe_device = try t.getNamedPipeDevice();
16586 pipe_path.ptr,16599 const server_handle = server_handle: {
16587 windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED,16600 var handle: windows.HANDLE = undefined;
16588 windows.PIPE_TYPE_BYTE,16601 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
16589 1,16602 const syscall: Syscall = try .start();
16590 4096,16603 while (true) switch (windows.ntdll.NtCreateNamedPipeFile(
16591 4096,16604 &handle,
16592 0,16605 .{
16593 sattr,16606 .SPECIFIC = .{ .FILE_PIPE = .{
16594 );16607 .READ_DATA = options.inbound,
16595 if (read_handle == windows.INVALID_HANDLE_VALUE) {16608 .WRITE_DATA = options.outbound,
16596 switch (windows.GetLastError()) {16609 .WRITE_ATTRIBUTES = true,
16597 else => |err| return windows.unexpectedError(err),16610 } },
16598 }16611 .STANDARD = .{ .SYNCHRONIZE = true },
16599 }16612 },
16600 errdefer posix.close(read_handle);16613 &.{
1660116614 .RootDirectory = named_pipe_device,
16602 var sattr_copy = sattr.*;16615 .Attributes = options.server.attributes,
16603 const write_handle = windows.kernel32.CreateFileW(16616 },
16604 pipe_path.ptr,16617 &io_status_block,
16605 .{ .GENERIC = .{ .WRITE = true } },16618 .{ .READ = true, .WRITE = true },
16606 0,16619 .CREATE,
16607 &sattr_copy,16620 options.server.mode,
16608 windows.OPEN_EXISTING,16621 .{ .TYPE = .BYTE_STREAM },
16609 @bitCast(windows.FILE.ATTRIBUTE{ .NORMAL = true }),16622 .{ .MODE = .BYTE_STREAM },
16610 null,16623 .{ .OPERATION = .QUEUE },
16611 );16624 options.maximum_instances,
16612 if (write_handle == windows.INVALID_HANDLE_VALUE) {16625 if (options.inbound) options.quota else 0,
16613 switch (windows.GetLastError()) {16626 if (options.outbound) options.quota else 0,
16614 else => |err| return windows.unexpectedError(err),16627 &options.default_timeout,
16615 }16628 )) {
16616 }16629 .SUCCESS => break syscall.finish(),
16617 errdefer posix.close(write_handle);16630 .CANCELLED => {
1661816631 try syscall.checkCancel();
16619 try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0);16632 continue;
1662016633 },
16621 rd.* = read_handle;16634 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
16622 wr.* = write_handle;16635 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
16636 else => |status| return syscall.unexpectedNtstatus(status),
16637 };
16638 break :server_handle handle;
16639 };
16640 errdefer windows.CloseHandle(server_handle);
16641 const client_handle = client_handle: {
16642 var handle: windows.HANDLE = undefined;
16643 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
16644 const syscall: Syscall = try .start();
16645 while (true) switch (windows.ntdll.NtOpenFile(
16646 &handle,
16647 .{
16648 .SPECIFIC = .{ .FILE_PIPE = .{
16649 .READ_DATA = options.outbound,
16650 .WRITE_DATA = options.inbound,
16651 .WRITE_ATTRIBUTES = true,
16652 } },
16653 .STANDARD = .{ .SYNCHRONIZE = true },
16654 },
16655 &.{
16656 .RootDirectory = server_handle,
16657 .Attributes = options.client.attributes,
16658 },
16659 &io_status_block,
16660 .{ .READ = true, .WRITE = true },
16661 options.client.mode,
16662 )) {
16663 .SUCCESS => break syscall.finish(),
16664 .CANCELLED => {
16665 try syscall.checkCancel();
16666 continue;
16667 },
16668 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
16669 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
16670 else => |status| return syscall.unexpectedNtstatus(status),
16671 };
16672 break :client_handle handle;
16673 };
16674 errdefer windows.CloseHandle(client_handle);
16675 return .{ server_handle, client_handle };
16623}16676}
1662416677
16625var pipe_name_counter = std.atomic.Value(u32).init(1);
16626
16627fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {16678fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
16628 const t: *Threaded = @ptrCast(@alignCast(userdata));16679 const t: *Threaded = @ptrCast(@alignCast(userdata));
16629
16630 t.scanEnviron();16680 t.scanEnviron();
1663116681 return t.environ.zig_progress_file;
16632 const int = try t.environ.zig_progress_handle;
16633
16634 return .{
16635 .handle = switch (@typeInfo(Io.File.Handle)) {
16636 .int => int,
16637 .pointer => @ptrFromInt(int),
16638 else => return error.UnsupportedOperation,
16639 },
16640 .flags = .{ .nonblocking = false },
16641 };
16642}16682}
1664316683
16644pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {16684pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {
...@@ -16734,7 +16774,7 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void {...@@ -16734,7 +16774,7 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void {
16734 // despite the function being documented to always return TRUE16774 // despite the function being documented to always return TRUE
16735 // * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG16775 // * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG
16736 // Therefore, that function is avoided in favor of using the device directly.16776 // Therefore, that function is avoided in favor of using the device directly.
16737 const cng_device = try getCngHandle(t);16777 const cng_device = try getCngDevice(t);
16738 var io_status_block: windows.IO_STATUS_BLOCK = undefined;16778 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
16739 var i: usize = 0;16779 var i: usize = 0;
16740 const syscall: Syscall = try .start();16780 const syscall: Syscall = try .start();
lib/std/Io/Threaded/test.zig+10-6
...@@ -181,13 +181,17 @@ test "cancel blocked read from pipe" {...@@ -181,13 +181,17 @@ test "cancel blocked read from pipe" {
181 var write_end: Io.File = undefined;181 var write_end: Io.File = undefined;
182 switch (builtin.target.os.tag) {182 switch (builtin.target.os.tag) {
183 .wasi => return error.SkipZigTest,183 .wasi => return error.SkipZigTest,
184 .windows => try std.os.windows.CreatePipe(&read_end.handle, &write_end.handle, &.{184 .windows => {
185 .nLength = @sizeOf(std.os.windows.SECURITY_ATTRIBUTES),185 const pipe = try threaded.windowsCreatePipe(.{
186 .lpSecurityDescriptor = null,186 .server = .{ .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
187 .bInheritHandle = std.os.windows.FALSE,187 .client = .{ .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
188 }),188 .inbound = true,
189 });
190 read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } };
191 write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } };
192 },
189 else => {193 else => {
190 const pipe = try std.Io.Threaded.pipe2(.{});194 const pipe = try std.Io.Threaded.pipe2(.{ .CLOEXEC = true });
191 read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } };195 read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } };
192 write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } };196 write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } };
193 },197 },
lib/std/Progress.zig+20-17
...@@ -139,7 +139,7 @@ pub const Node = struct {...@@ -139,7 +139,7 @@ pub const Node = struct {
139 fn setIpcFd(s: *Storage, fd: Io.File.Handle) void {139 fn setIpcFd(s: *Storage, fd: Io.File.Handle) void {
140 const integer: u32 = switch (@typeInfo(Io.File.Handle)) {140 const integer: u32 = switch (@typeInfo(Io.File.Handle)) {
141 .int => @bitCast(fd),141 .int => @bitCast(fd),
142 .pointer => @intFromPtr(fd),142 .pointer => @intCast(@intFromPtr(fd)),
143 else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),143 else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
144 };144 };
145 // `estimated_total_count` max int indicates the special state that145 // `estimated_total_count` max int indicates the special state that
...@@ -342,10 +342,18 @@ pub const Node = struct {...@@ -342,10 +342,18 @@ pub const Node = struct {
342 /// Posix-only. Used by `std.process.Child`. Thread-safe.342 /// Posix-only. Used by `std.process.Child`. Thread-safe.
343 pub fn setIpcFd(node: Node, fd: Io.File.Handle) void {343 pub fn setIpcFd(node: Node, fd: Io.File.Handle) void {
344 const index = node.index.unwrap() orelse return;344 const index = node.index.unwrap() orelse return;
345 assert(fd >= 0);345 switch (@typeInfo(Io.File.Handle)) {
346 assert(fd != posix.STDOUT_FILENO);346 .int => {
347 assert(fd != posix.STDIN_FILENO);347 assert(fd >= 0);
348 assert(fd != posix.STDERR_FILENO);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 }
349 storageByIndex(index).setIpcFd(fd);357 storageByIndex(index).setIpcFd(fd);
350 }358 }
351359
...@@ -477,21 +485,18 @@ pub fn start(io: Io, options: Options) Node {...@@ -477,21 +485,18 @@ pub fn start(io: Io, options: Options) Node {
477 global_progress.refresh_rate_ns = @intCast(options.refresh_rate_ns.toNanoseconds());485 global_progress.refresh_rate_ns = @intCast(options.refresh_rate_ns.toNanoseconds());
478 global_progress.initial_delay_ns = @intCast(options.initial_delay_ns.toNanoseconds());486 global_progress.initial_delay_ns = @intCast(options.initial_delay_ns.toNanoseconds());
479487
480 if (noop_impl)488 if (noop_impl) return .none;
481 return Node.none;
482489
483 global_progress.io = io;490 global_progress.io = io;
484491
485 if (io.vtable.progressParentFile(io.userdata)) |ipc_file| {492 if (io.vtable.progressParentFile(io.userdata)) |ipc_file| {
486 global_progress.update_worker = io.concurrent(ipcThreadRun, .{ io, ipc_file }) catch |err| {493 global_progress.update_worker = io.concurrent(ipcThreadRun, .{ io, ipc_file }) catch |err| {
487 global_progress.start_failure = .{ .spawn_ipc_worker = err };494 global_progress.start_failure = .{ .spawn_ipc_worker = err };
488 return Node.none;495 return .none;
489 };496 };
490 } else |env_err| switch (env_err) {497 } else |env_err| switch (env_err) {
491 error.EnvironmentVariableMissing => {498 error.EnvironmentVariableMissing => {
492 if (options.disable_printing) {499 if (options.disable_printing) return .none;
493 return Node.none;
494 }
495 const stderr: Io.File = .stderr();500 const stderr: Io.File = .stderr();
496 global_progress.terminal = stderr;501 global_progress.terminal = stderr;
497 if (stderr.enableAnsiEscapeCodes(io)) |_| {502 if (stderr.enableAnsiEscapeCodes(io)) |_| {
...@@ -504,14 +509,12 @@ pub fn start(io: Io, options: Options) Node {...@@ -504,14 +509,12 @@ pub fn start(io: Io, options: Options) Node {
504 } else |err| switch (err) {509 } else |err| switch (err) {
505 error.Canceled => {510 error.Canceled => {
506 io.recancel();511 io.recancel();
507 return Node.none;512 return .none;
508 },513 },
509 }514 }
510 }515 }
511516
512 if (global_progress.terminal_mode == .off) {517 if (global_progress.terminal_mode == .off) return .none;
513 return Node.none;
514 }
515518
516 if (have_sigwinch) {519 if (have_sigwinch) {
517 const act: posix.Sigaction = .{520 const act: posix.Sigaction = .{
...@@ -530,12 +533,12 @@ pub fn start(io: Io, options: Options) Node {...@@ -530,12 +533,12 @@ pub fn start(io: Io, options: Options) Node {
530 global_progress.update_worker = future;533 global_progress.update_worker = future;
531 } else |err| {534 } else |err| {
532 global_progress.start_failure = .{ .spawn_update_worker = err };535 global_progress.start_failure = .{ .spawn_update_worker = err };
533 return Node.none;536 return .none;
534 }537 }
535 },538 },
536 else => |e| {539 else => |e| {
537 global_progress.start_failure = .{ .parent_ipc = e };540 global_progress.start_failure = .{ .parent_ipc = e };
538 return Node.none;541 return .none;
539 },542 },
540 }543 }
541544
lib/std/Thread.zig+5-1
...@@ -598,7 +598,11 @@ const WindowsThreadImpl = struct {...@@ -598,7 +598,11 @@ const WindowsThreadImpl = struct {
598 }598 }
599599
600 fn join(self: Impl) void {600 fn join(self: Impl) void {
601 windows.WaitForSingleObjectEx(self.thread.thread_handle, windows.INFINITE, false) catch unreachable;601 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
602 switch (windows.ntdll.NtWaitForSingleObject(self.thread.thread_handle, windows.FALSE, &infinite_timeout)) {
603 windows.NTSTATUS.WAIT_0 => {},
604 else => |status| windows.unexpectedStatus(status) catch unreachable,
605 }
602 windows.CloseHandle(self.thread.thread_handle);606 windows.CloseHandle(self.thread.thread_handle);
603 assert(self.thread.completion.load(.seq_cst) == .completed);607 assert(self.thread.completion.load(.seq_cst) == .completed);
604 self.thread.free();608 self.thread.free();
lib/std/mem/Allocator.zig+13-2
...@@ -452,12 +452,23 @@ pub fn dupe(allocator: Allocator, comptime T: type, m: []const T) Error![]T {...@@ -452,12 +452,23 @@ pub fn dupe(allocator: Allocator, comptime T: type, m: []const T) Error![]T {
452 return new_buf;452 return new_buf;
453}453}
454454
455/// Deprecated in favor of `dupeSentinel`
455/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.456/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
456pub fn dupeZ(allocator: Allocator, comptime T: type, m: []const T) Error![:0]T {457pub fn dupeZ(allocator: Allocator, comptime T: type, m: []const T) Error![:0]T {
458 return allocator.dupeSentinel(T, m, 0);
459}
460
461/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
462pub fn dupeSentinel(
463 allocator: Allocator,
464 comptime T: type,
465 m: []const T,
466 comptime sentinel: T,
467) Error![:sentinel]T {
457 const new_buf = try allocator.alloc(T, m.len + 1);468 const new_buf = try allocator.alloc(T, m.len + 1);
458 @memcpy(new_buf[0..m.len], m);469 @memcpy(new_buf[0..m.len], m);
459 new_buf[m.len] = 0;470 new_buf[m.len] = sentinel;
460 return new_buf[0..m.len :0];471 return new_buf[0..m.len :sentinel];
461}472}
462473
463/// An allocator that always fails to allocate.474/// An allocator that always fails to allocate.
lib/std/os/windows.zig+19-250
...@@ -521,7 +521,7 @@ pub const FILE = struct {...@@ -521,7 +521,7 @@ pub const FILE = struct {
521 _,521 _,
522522
523 pub const VALID_FLAGS: @This() = @enumFromInt(0b11);523 pub const VALID_FLAGS: @This() = @enumFromInt(0b11);
524 } = .ASYNCHRONOUS,524 },
525 /// The file being opened must not be a directory file or this call525 /// The file being opened must not be a directory file or this call
526 /// fails. The file object being opened can represent a data file, a526 /// fails. The file object being opened can represent a data file, a
527 /// logical, virtual, or physical device, or a volume.527 /// logical, virtual, or physical device, or a volume.
...@@ -2324,12 +2324,12 @@ pub fn GetProcessHeap() ?*HEAP {...@@ -2324,12 +2324,12 @@ pub fn GetProcessHeap() ?*HEAP {
2324// ref: um/winternl.h2324// ref: um/winternl.h
23252325
2326pub const OBJECT_ATTRIBUTES = extern struct {2326pub const OBJECT_ATTRIBUTES = extern struct {
2327 Length: ULONG,2327 Length: ULONG = @sizeOf(OBJECT_ATTRIBUTES),
2328 RootDirectory: ?HANDLE,2328 RootDirectory: ?HANDLE = null,
2329 ObjectName: ?*UNICODE_STRING,2329 ObjectName: ?*UNICODE_STRING = @constCast(&UNICODE_STRING.empty),
2330 Attributes: ATTRIBUTES,2330 Attributes: ATTRIBUTES = .{},
2331 SecurityDescriptor: ?*anyopaque,2331 SecurityDescriptor: ?*anyopaque = null,
2332 SecurityQualityOfService: ?*anyopaque,2332 SecurityQualityOfService: ?*anyopaque = null,
23332333
2334 // Valid values for the Attributes field2334 // Valid values for the Attributes field
2335 pub const ATTRIBUTES = packed struct(ULONG) {2335 pub const ATTRIBUTES = packed struct(ULONG) {
...@@ -2420,14 +2420,10 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN...@@ -2420,14 +2420,10 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
2420 .Buffer = @constCast(sub_path_w.ptr),2420 .Buffer = @constCast(sub_path_w.ptr),
2421 };2421 };
2422 const attr: OBJECT_ATTRIBUTES = .{2422 const attr: OBJECT_ATTRIBUTES = .{
2423 .Length = @sizeOf(OBJECT_ATTRIBUTES),
2424 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir,2423 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir,
2425 .Attributes = .{2424 .Attributes = .{ .INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false },
2426 .INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false,
2427 },
2428 .ObjectName = &nt_name,2425 .ObjectName = &nt_name,
2429 .SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null,2426 .SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null,
2430 .SecurityQualityOfService = null,
2431 };2427 };
2432 var io: IO_STATUS_BLOCK = undefined;2428 var io: IO_STATUS_BLOCK = undefined;
2433 while (true) {2429 while (true) {
...@@ -2475,7 +2471,8 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN...@@ -2475,7 +2471,8 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
2475 // call has failed. There is not really a sane way to handle2471 // call has failed. There is not really a sane way to handle
2476 // this other than retrying the creation after the OS finishes2472 // this other than retrying the creation after the OS finishes
2477 // the deletion.2473 // 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);
2479 continue;2476 continue;
2480 },2477 },
2481 .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,2478 .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
...@@ -2506,151 +2503,6 @@ pub fn GetCurrentThreadId() DWORD {...@@ -2506,151 +2503,6 @@ pub fn GetCurrentThreadId() DWORD {
2506pub fn GetLastError() Win32Error {2503pub fn GetLastError() Win32Error {
2507 return @enumFromInt(teb().LastErrorValue);2504 return @enumFromInt(teb().LastErrorValue);
2508}2505}
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
2654/// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls.2506/// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls.
2655/// It implements similar behavior to `DeviceIoControl` and is meant to serve2507/// It implements similar behavior to `DeviceIoControl` and is meant to serve
2656/// as a direct substitute for that call.2508/// as a direct substitute for that call.
...@@ -2707,66 +2559,6 @@ pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWOR...@@ -2707,66 +2559,6 @@ pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWOR
2707 return bytes;2559 return bytes;
2708}2560}
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
2770pub const CreateIoCompletionPortError = error{Unexpected};2562pub const CreateIoCompletionPortError = error{Unexpected};
27712563
2772pub fn CreateIoCompletionPort(2564pub fn CreateIoCompletionPort(
...@@ -2878,21 +2670,6 @@ pub fn CloseHandle(hObject: HANDLE) void {...@@ -2878,21 +2670,6 @@ pub fn CloseHandle(hObject: HANDLE) void {
2878 assert(ntdll.NtClose(hObject) == .SUCCESS);2670 assert(ntdll.NtClose(hObject) == .SUCCESS);
2879}2671}
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
2896pub const QueryObjectNameError = error{2673pub const QueryObjectNameError = error{
2897 AccessDenied,2674 AccessDenied,
2898 InvalidHandle,2675 InvalidHandle,
...@@ -3545,6 +3322,12 @@ pub fn nanoSecondsToFileTime(ns: Io.Timestamp) FILETIME {...@@ -3545,6 +3322,12 @@ pub fn nanoSecondsToFileTime(ns: Io.Timestamp) FILETIME {
3545 };3322 };
3546}3323}
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
3548/// Compares two WTF16 strings using the equivalent functionality of3331/// Compares two WTF16 strings using the equivalent functionality of
3549/// `RtlEqualUnicodeString` (with case insensitive comparison enabled).3332/// `RtlEqualUnicodeString` (with case insensitive comparison enabled).
3550/// This function can be called on any target.3333/// This function can be called on any target.
...@@ -3598,19 +3381,12 @@ pub fn eqlIgnoreCaseWtf8(a: []const u8, b: []const u8) bool {...@@ -3598,19 +3381,12 @@ pub fn eqlIgnoreCaseWtf8(a: []const u8, b: []const u8) bool {
3598 var a_wtf8_it = std.unicode.Wtf8View.initUnchecked(a).iterator();3381 var a_wtf8_it = std.unicode.Wtf8View.initUnchecked(a).iterator();
3599 var b_wtf8_it = std.unicode.Wtf8View.initUnchecked(b).iterator();3382 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
3608 while (true) {3384 while (true) {
3609 const a_cp = a_wtf8_it.nextCodepoint() orelse break;3385 const a_cp = a_wtf8_it.nextCodepoint() orelse break;
3610 const b_cp = b_wtf8_it.nextCodepoint() orelse return false;3386 const b_cp = b_wtf8_it.nextCodepoint() orelse return false;
36113387
3612 if (a_cp <= maxInt(u16) and b_cp <= maxInt(u16)) {3388 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))) {
3614 return false;3390 return false;
3615 }3391 }
3616 } else if (a_cp != b_cp) {3392 } else if (a_cp != b_cp) {
...@@ -4098,15 +3874,6 @@ pub const Win32Error = @import("windows/win32error.zig").Win32Error;...@@ -4098,15 +3874,6 @@ pub const Win32Error = @import("windows/win32error.zig").Win32Error;
4098pub const LANG = @import("windows/lang.zig");3874pub const LANG = @import("windows/lang.zig");
4099pub const SUBLANG = @import("windows/sublang.zig");3875pub 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
4110pub const BOOL = c_int;3877pub const BOOL = c_int;
4111pub const BOOLEAN = BYTE;3878pub const BOOLEAN = BYTE;
4112pub const BYTE = u8;3879pub const BYTE = u8;
...@@ -5244,6 +5011,8 @@ pub const UNICODE_STRING = extern struct {...@@ -5244,6 +5011,8 @@ pub const UNICODE_STRING = extern struct {
5244 Length: c_ushort,5011 Length: c_ushort,
5245 MaximumLength: c_ushort,5012 MaximumLength: c_ushort,
5246 Buffer: ?[*]WCHAR,5013 Buffer: ?[*]WCHAR,
5014
5015 pub const empty: UNICODE_STRING = .{ .Length = 0, .MaximumLength = 0, .Buffer = null };
5247};5016};
52485017
5249pub const ACTIVATION_CONTEXT_DATA = opaque {};5018pub const ACTIVATION_CONTEXT_DATA = opaque {};
lib/std/os/windows/kernel32.zig-108
...@@ -12,8 +12,6 @@ const FILETIME = windows.FILETIME;...@@ -12,8 +12,6 @@ const FILETIME = windows.FILETIME;
12const HANDLE = windows.HANDLE;12const HANDLE = windows.HANDLE;
13const HANDLER_ROUTINE = windows.HANDLER_ROUTINE;13const HANDLER_ROUTINE = windows.HANDLER_ROUTINE;
14const HMODULE = windows.HMODULE;14const HMODULE = windows.HMODULE;
15const INIT_ONCE = windows.INIT_ONCE;
16const INIT_ONCE_FN = windows.INIT_ONCE_FN;
17const LARGE_INTEGER = windows.LARGE_INTEGER;15const LARGE_INTEGER = windows.LARGE_INTEGER;
18const LPCSTR = windows.LPCSTR;16const LPCSTR = windows.LPCSTR;
19const LPCVOID = windows.LPCVOID;17const LPCVOID = windows.LPCVOID;
...@@ -24,7 +22,6 @@ const LPWSTR = windows.LPWSTR;...@@ -24,7 +22,6 @@ const LPWSTR = windows.LPWSTR;
24const MODULEENTRY32 = windows.MODULEENTRY32;22const MODULEENTRY32 = windows.MODULEENTRY32;
25const OVERLAPPED = windows.OVERLAPPED;23const OVERLAPPED = windows.OVERLAPPED;
26const OVERLAPPED_ENTRY = windows.OVERLAPPED_ENTRY;24const OVERLAPPED_ENTRY = windows.OVERLAPPED_ENTRY;
27const PMEMORY_BASIC_INFORMATION = windows.PMEMORY_BASIC_INFORMATION;
28const PROCESS_INFORMATION = windows.PROCESS_INFORMATION;25const PROCESS_INFORMATION = windows.PROCESS_INFORMATION;
29const SECURITY_ATTRIBUTES = windows.SECURITY_ATTRIBUTES;26const SECURITY_ATTRIBUTES = windows.SECURITY_ATTRIBUTES;
30const SIZE_T = windows.SIZE_T;27const SIZE_T = windows.SIZE_T;
...@@ -37,7 +34,6 @@ const ULONG = windows.ULONG;...@@ -37,7 +34,6 @@ const ULONG = windows.ULONG;
37const ULONG_PTR = windows.ULONG_PTR;34const ULONG_PTR = windows.ULONG_PTR;
38const va_list = windows.va_list;35const va_list = windows.va_list;
39const WCHAR = windows.WCHAR;36const WCHAR = windows.WCHAR;
40const WIN32_FIND_DATAW = windows.WIN32_FIND_DATAW;
41const Win32Error = windows.Win32Error;37const Win32Error = windows.Win32Error;
42const WORD = windows.WORD;38const WORD = windows.WORD;
4339
...@@ -59,39 +55,6 @@ pub extern "kernel32" fn CancelIo(...@@ -59,39 +55,6 @@ pub extern "kernel32" fn CancelIo(
59 hFile: HANDLE,55 hFile: HANDLE,
60) callconv(.winapi) BOOL;56) 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
95// TODO: Wrapper around NtSetInformationFile + `FILE_POSITION_INFORMATION`.58// TODO: Wrapper around NtSetInformationFile + `FILE_POSITION_INFORMATION`.
96// `FILE_STANDARD_INFORMATION` is also used if dwMoveMethod is `FILE_END`59// `FILE_STANDARD_INFORMATION` is also used if dwMoveMethod is `FILE_END`
97pub extern "kernel32" fn SetFilePointerEx(60pub extern "kernel32" fn SetFilePointerEx(
...@@ -117,11 +80,6 @@ pub extern "kernel32" fn WriteFile(...@@ -117,11 +80,6 @@ pub extern "kernel32" fn WriteFile(
117 in_out_lpOverlapped: ?*OVERLAPPED,80 in_out_lpOverlapped: ?*OVERLAPPED,
118) callconv(.winapi) BOOL;81) callconv(.winapi) BOOL;
11982
120// TODO: Wrapper around GetStdHandle + NtFlushBuffersFile.
121pub extern "kernel32" fn FlushFileBuffers(
122 hFile: HANDLE,
123) callconv(.winapi) BOOL;
124
125// TODO: Wrapper around NtSetInformationFile + `FILE_IO_COMPLETION_NOTIFICATION_INFORMATION`.83// TODO: Wrapper around NtSetInformationFile + `FILE_IO_COMPLETION_NOTIFICATION_INFORMATION`.
126pub extern "kernel32" fn SetFileCompletionNotificationModes(84pub extern "kernel32" fn SetFileCompletionNotificationModes(
127 FileHandle: HANDLE,85 FileHandle: HANDLE,
...@@ -143,24 +101,6 @@ pub extern "kernel32" fn GetSystemDirectoryW(...@@ -143,24 +101,6 @@ pub extern "kernel32" fn GetSystemDirectoryW(
143101
144// I/O - Kernel Objects102// 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
164// TODO: Wrapper around NtRemoveIoCompletion.104// TODO: Wrapper around NtRemoveIoCompletion.
165pub extern "kernel32" fn GetQueuedCompletionStatus(105pub extern "kernel32" fn GetQueuedCompletionStatus(
166 CompletionPort: HANDLE,106 CompletionPort: HANDLE,
...@@ -210,37 +150,6 @@ pub extern "kernel32" fn TerminateProcess(...@@ -210,37 +150,6 @@ pub extern "kernel32" fn TerminateProcess(
210 uExitCode: UINT,150 uExitCode: UINT,
211) callconv(.winapi) BOOL;151) 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
244// Process Management153// Process Management
245154
246pub extern "kernel32" fn CreateProcessW(155pub extern "kernel32" fn CreateProcessW(
...@@ -256,12 +165,6 @@ pub extern "kernel32" fn CreateProcessW(...@@ -256,12 +165,6 @@ pub extern "kernel32" fn CreateProcessW(
256 lpProcessInformation: *PROCESS_INFORMATION,165 lpProcessInformation: *PROCESS_INFORMATION,
257) callconv(.winapi) BOOL;166) 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
265// TODO: Wrapper around NtQueryInformationProcess with `PROCESS_BASIC_INFORMATION`.168// TODO: Wrapper around NtQueryInformationProcess with `PROCESS_BASIC_INFORMATION`.
266pub extern "kernel32" fn GetExitCodeProcess(169pub extern "kernel32" fn GetExitCodeProcess(
267 hProcess: HANDLE,170 hProcess: HANDLE,
...@@ -436,14 +339,3 @@ pub extern "kernel32" fn FormatMessageW(...@@ -436,14 +339,3 @@ pub extern "kernel32" fn FormatMessageW(
436339
437// TODO: Getter for teb().LastErrorValue.340// TODO: Getter for teb().LastErrorValue.
438pub extern "kernel32" fn GetLastError() callconv(.winapi) Win32Error;341pub 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(...@@ -407,6 +407,11 @@ pub extern "ntdll" fn NtCreateNamedPipeFile(
407 DefaultTimeout: ?*const LARGE_INTEGER,407 DefaultTimeout: ?*const LARGE_INTEGER,
408) callconv(.winapi) NTSTATUS;408) callconv(.winapi) NTSTATUS;
409409
410pub extern "ntdll" fn NtFlushBuffersFile(
411 FileHandle: HANDLE,
412 IoStatusBlock: *IO_STATUS_BLOCK,
413) callconv(.winapi) NTSTATUS;
414
410pub extern "ntdll" fn NtMapViewOfSection(415pub extern "ntdll" fn NtMapViewOfSection(
411 SectionHandle: HANDLE,416 SectionHandle: HANDLE,
412 ProcessHandle: HANDLE,417 ProcessHandle: HANDLE,
...@@ -590,7 +595,7 @@ pub extern "ntdll" fn NtOpenThread(...@@ -590,7 +595,7 @@ pub extern "ntdll" fn NtOpenThread(
590595
591pub extern "ntdll" fn NtCancelSynchronousIoFile(596pub extern "ntdll" fn NtCancelSynchronousIoFile(
592 ThreadHandle: HANDLE,597 ThreadHandle: HANDLE,
593 RequestToCancel: ?*IO_STATUS_BLOCK,598 IoRequestToCancel: ?*IO_STATUS_BLOCK,
594 IoStatusBlock: *IO_STATUS_BLOCK,599 IoStatusBlock: *IO_STATUS_BLOCK,
595) callconv(.winapi) NTSTATUS;600) callconv(.winapi) NTSTATUS;
596601
...@@ -606,13 +611,13 @@ pub extern "ntdll" fn NtDelayExecution(...@@ -606,13 +611,13 @@ pub extern "ntdll" fn NtDelayExecution(
606 DelayInterval: *const LARGE_INTEGER,611 DelayInterval: *const LARGE_INTEGER,
607) callconv(.winapi) NTSTATUS;612) callconv(.winapi) NTSTATUS;
608613
609pub extern "ntdll" fn NtCancelIoFileEx(614pub extern "ntdll" fn NtCancelIoFile(
610 FileHandle: HANDLE,615 FileHandle: HANDLE,
611 IoRequestToCancel: *const IO_STATUS_BLOCK,
612 IoStatusBlock: *IO_STATUS_BLOCK,616 IoStatusBlock: *IO_STATUS_BLOCK,
613) callconv(.winapi) NTSTATUS;617) callconv(.winapi) NTSTATUS;
614618
615pub extern "ntdll" fn NtCancelIoFile(619pub extern "ntdll" fn NtCancelIoFileEx(
616 FileHandle: HANDLE,620 FileHandle: HANDLE,
621 IoRequestToCancel: *const IO_STATUS_BLOCK,
617 IoStatusBlock: *IO_STATUS_BLOCK,622 IoStatusBlock: *IO_STATUS_BLOCK,
618) callconv(.winapi) NTSTATUS;623) callconv(.winapi) NTSTATUS;
lib/std/process/Environ.zig+438-272
...@@ -4,7 +4,7 @@ const builtin = @import("builtin");...@@ -4,7 +4,7 @@ const builtin = @import("builtin");
4const native_os = builtin.os.tag;4const native_os = builtin.os.tag;
55
6const std = @import("../std.zig");6const std = @import("../std.zig");
7const Allocator = std.mem.Allocator;7const Allocator = mem.Allocator;
8const assert = std.debug.assert;8const assert = std.debug.assert;
9const testing = std.testing;9const testing = std.testing;
10const unicode = std.unicode;10const unicode = std.unicode;
...@@ -14,12 +14,7 @@ const mem = std.mem;...@@ -14,12 +14,7 @@ const mem = std.mem;
14/// Unmodified, unprocessed data provided by the operating system.14/// Unmodified, unprocessed data provided by the operating system.
15block: Block,15block: Block,
1616
17pub const empty: Environ = .{17pub const empty: Environ = .{ .block = .empty };
18 .block = switch (Block) {
19 void => {},
20 else => &.{},
21 },
22};
2318
24/// On WASI without libc, this is `void` because the environment has to be19/// On WASI without libc, this is `void` because the environment has to be
25/// queried and heap-allocated at runtime.20/// queried and heap-allocated at runtime.
...@@ -28,13 +23,65 @@ pub const empty: Environ = .{...@@ -28,13 +23,65 @@ pub const empty: Environ = .{
28/// is modified, so a long-lived pointer cannot be used. Therefore, on this23/// is modified, so a long-lived pointer cannot be used. Therefore, on this
29/// operating system `void` is also used.24/// operating system `void` is also used.
30pub const Block = switch (native_os) {25pub const Block = switch (native_os) {
31 .windows => void,26 .windows => GlobalBlock,
32 .wasi => switch (builtin.link_libc) {27 .wasi => switch (builtin.link_libc) {
33 false => void,28 false => GlobalBlock,
34 true => [:null]const ?[*:0]const u8,29 true => PosixBlock,
35 },30 },
36 .freestanding, .other => void,31 .freestanding, .other => GlobalBlock,
37 else => [:null]const ?[*:0]const u8,32 else => PosixBlock,
33};
34
35pub const GlobalBlock = struct {
36 use_global: bool,
37
38 pub const empty: GlobalBlock = .{ .use_global = false };
39 pub const global: GlobalBlock = .{ .use_global = true };
40
41 pub fn deinit(_: GlobalBlock, _: Allocator) void {}
42};
43
44pub const PosixBlock = struct {
45 slice: [:null]const ?[*:0]const u8,
46
47 pub const empty: PosixBlock = .{ .slice = &.{} };
48
49 pub fn deinit(block: PosixBlock, gpa: Allocator) void {
50 for (block.slice) |entry| gpa.free(mem.span(entry.?));
51 gpa.free(block.slice);
52 }
53
54 pub const View = struct {
55 slice: []const [*:0]const u8,
56
57 pub fn isEmpty(v: View) bool {
58 return v.slice.len == 0;
59 }
60 };
61 pub fn view(block: PosixBlock) View {
62 return .{ .slice = @ptrCast(block.slice) };
63 }
64};
65
66pub const WindowsBlock = struct {
67 slice: [:0]const u16,
68
69 pub const empty: WindowsBlock = .{ .slice = &.{0} };
70
71 pub fn deinit(block: WindowsBlock, gpa: Allocator) void {
72 gpa.free(block.slice);
73 }
74
75 pub const View = struct {
76 ptr: [*:0]const u16,
77
78 pub fn isEmpty(v: View) bool {
79 return v.ptr[0] == 0;
80 }
81 };
82 pub fn view(block: WindowsBlock) View {
83 return .{ .ptr = block.slice.ptr };
84 }
38};85};
3986
40pub const Map = struct {87pub const Map = struct {
...@@ -46,47 +93,60 @@ pub const Map = struct {...@@ -46,47 +93,60 @@ pub const Map = struct {
46 pub const Size = usize;93 pub const Size = usize;
4794
48 pub const EnvNameHashContext = struct {95 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
55 pub fn hash(self: @This(), s: []const u8) u32 {96 pub fn hash(self: @This(), s: []const u8) u32 {
56 _ = self;97 _ = self;
57 if (native_os == .windows) {98 switch (native_os) {
58 var h = std.hash.Wyhash.init(0);99 else => return std.array_hash_map.hashString(s),
59 var it = unicode.Wtf8View.initUnchecked(s).iterator();100 .windows => {
60 while (it.nextCodepoint()) |cp| {101 var h = std.hash.Wyhash.init(0);
61 const cp_upper = upcase(cp);102 var it = unicode.Wtf8View.initUnchecked(s).iterator();
62 h.update(&[_]u8{103 while (it.nextCodepoint()) |cp| {
63 @as(u8, @intCast((cp_upper >> 16) & 0xff)),104 const cp_upper = if (std.math.cast(u16, cp)) |wtf16|
64 @as(u8, @intCast((cp_upper >> 8) & 0xff)),105 std.os.windows.toUpperWtf16(wtf16)
65 @as(u8, @intCast((cp_upper >> 0) & 0xff)),106 else
66 });107 cp;
67 }108 h.update(&[_]u8{
68 return @truncate(h.final());109 @truncate(cp_upper >> 0),
110 @truncate(cp_upper >> 8),
111 @truncate(cp_upper >> 16),
112 });
113 }
114 return @truncate(h.final());
115 },
69 }116 }
70 return std.array_hash_map.hashString(s);
71 }117 }
72118
73 pub fn eql(self: @This(), a: []const u8, b: []const u8, b_index: usize) bool {119 pub fn eql(self: @This(), a: []const u8, b: []const u8, b_index: usize) bool {
74 _ = self;120 _ = self;
75 _ = b_index;121 _ = b_index;
76 if (native_os == .windows) {122 return eqlKeys(a, b);
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);
88 }123 }
89 };124 };
125 fn eqlKeys(a: []const u8, b: []const u8) bool {
126 return switch (native_os) {
127 else => std.array_hash_map.eqlString(a, b),
128 .windows => std.os.windows.eqlIgnoreCaseWtf8(a, b),
129 };
130 }
131
132 pub fn validateKey(key: []const u8) bool {
133 switch (native_os) {
134 else => return key.len > 0 and mem.findAny(u8, key, &.{ 0, '=' }) == null,
135 .windows => {
136 if (!unicode.wtf8ValidateSlice(key)) return false;
137 var it = unicode.Wtf8View.initUnchecked(key).iterator();
138 switch (it.nextCodepoint() orelse return false) {
139 0 => return false,
140 else => {},
141 }
142 while (it.nextCodepoint()) |cp| switch (cp) {
143 0, '=' => return false,
144 else => {},
145 };
146 return true;
147 },
148 }
149 }
90150
91 /// Create a Map backed by a specific allocator.151 /// Create a Map backed by a specific allocator.
92 /// That allocator will be used for both backing allocations152 /// That allocator will be used for both backing allocations
...@@ -99,30 +159,71 @@ pub const Map = struct {...@@ -99,30 +159,71 @@ pub const Map = struct {
99 /// of the stored keys and values.159 /// of the stored keys and values.
100 pub fn deinit(self: *Map) void {160 pub fn deinit(self: *Map) void {
101 const gpa = self.allocator;161 const gpa = self.allocator;
102 var it = self.array_hash_map.iterator();162 for (self.keys()) |key| gpa.free(key);
103 while (it.next()) |entry| {163 for (self.values()) |value| gpa.free(value);
104 gpa.free(entry.key_ptr.*);
105 gpa.free(entry.value_ptr.*);
106 }
107 self.array_hash_map.deinit(gpa);164 self.array_hash_map.deinit(gpa);
108 self.* = undefined;165 self.* = undefined;
109 }166 }
110167
111 pub fn keys(m: *const Map) [][]const u8 {168 pub fn keys(map: *const Map) [][]const u8 {
112 return m.array_hash_map.keys();169 return map.array_hash_map.keys();
170 }
171
172 pub fn values(map: *const Map) [][]const u8 {
173 return map.array_hash_map.values();
174 }
175
176 pub fn putPosixBlock(map: *Map, view: PosixBlock.View) Allocator.Error!void {
177 for (view.slice) |entry| {
178 var entry_i: usize = 0;
179 while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {}
180 const key = entry[0..entry_i];
181
182 var end_i: usize = entry_i;
183 while (entry[end_i] != 0) : (end_i += 1) {}
184 const value = entry[entry_i + 1 .. end_i];
185
186 try map.put(key, value);
187 }
113 }188 }
114189
115 pub fn values(m: *const Map) [][]const u8 {190 pub fn putWindowsBlock(map: *Map, view: WindowsBlock.View) Allocator.Error!void {
116 return m.array_hash_map.values();191 var i: usize = 0;
192 while (view.ptr[i] != 0) {
193 const key_start = i;
194
195 // There are some special environment variables that start with =,
196 // so we need a special case to not treat = as a key/value separator
197 // if it's the first character.
198 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
199 if (view.ptr[key_start] == '=') i += 1;
200
201 while (view.ptr[i] != 0 and view.ptr[i] != '=') : (i += 1) {}
202 const key_w = view.ptr[key_start..i];
203 const key = try unicode.wtf16LeToWtf8Alloc(map.allocator, key_w);
204 errdefer map.allocator.free(key);
205
206 if (view.ptr[i] == '=') i += 1;
207
208 const value_start = i;
209 while (view.ptr[i] != 0) : (i += 1) {}
210 const value_w = view.ptr[value_start..i];
211 const value = try unicode.wtf16LeToWtf8Alloc(map.allocator, value_w);
212 errdefer map.allocator.free(value);
213
214 i += 1; // skip over null byte
215
216 try map.putMove(key, value);
217 }
117 }218 }
118219
119 /// Same as `put` but the key and value become owned by the Map rather220 /// Same as `put` but the key and value become owned by the Map rather
120 /// than being copied.221 /// than being copied.
121 /// If `putMove` fails, the ownership of key and value does not transfer.222 /// If `putMove` fails, the ownership of key and value does not transfer.
122 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.223 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
123 pub fn putMove(self: *Map, key: []u8, value: []u8) !void {224 pub fn putMove(self: *Map, key: []u8, value: []u8) Allocator.Error!void {
225 assert(validateKey(key));
124 const gpa = self.allocator;226 const gpa = self.allocator;
125 assert(unicode.wtf8ValidateSlice(key));
126 const get_or_put = try self.array_hash_map.getOrPut(gpa, key);227 const get_or_put = try self.array_hash_map.getOrPut(gpa, key);
127 if (get_or_put.found_existing) {228 if (get_or_put.found_existing) {
128 gpa.free(get_or_put.key_ptr.*);229 gpa.free(get_or_put.key_ptr.*);
...@@ -134,8 +235,8 @@ pub const Map = struct {...@@ -134,8 +235,8 @@ pub const Map = struct {
134235
135 /// `key` and `value` are copied into the Map.236 /// `key` and `value` are copied into the Map.
136 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.237 /// 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 {238 pub fn put(self: *Map, key: []const u8, value: []const u8) Allocator.Error!void {
138 assert(unicode.wtf8ValidateSlice(key));239 assert(validateKey(key));
139 const gpa = self.allocator;240 const gpa = self.allocator;
140 const value_copy = try gpa.dupe(u8, value);241 const value_copy = try gpa.dupe(u8, value);
141 errdefer gpa.free(value_copy);242 errdefer gpa.free(value_copy);
...@@ -155,7 +256,7 @@ pub const Map = struct {...@@ -155,7 +256,7 @@ pub const Map = struct {
155 /// The returned pointer is invalidated if the map resizes.256 /// The returned pointer is invalidated if the map resizes.
156 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.257 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
157 pub fn getPtr(self: Map, key: []const u8) ?*[]const u8 {258 pub fn getPtr(self: Map, key: []const u8) ?*[]const u8 {
158 assert(unicode.wtf8ValidateSlice(key));259 assert(validateKey(key));
159 return self.array_hash_map.getPtr(key);260 return self.array_hash_map.getPtr(key);
160 }261 }
161262
...@@ -164,11 +265,12 @@ pub const Map = struct {...@@ -164,11 +265,12 @@ pub const Map = struct {
164 /// key is removed from the map.265 /// key is removed from the map.
165 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.266 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
166 pub fn get(self: Map, key: []const u8) ?[]const u8 {267 pub fn get(self: Map, key: []const u8) ?[]const u8 {
167 assert(unicode.wtf8ValidateSlice(key));268 assert(validateKey(key));
168 return self.array_hash_map.get(key);269 return self.array_hash_map.get(key);
169 }270 }
170271
171 pub fn contains(m: *const Map, key: []const u8) bool {272 pub fn contains(m: *const Map, key: []const u8) bool {
273 assert(validateKey(key));
172 return m.array_hash_map.contains(key);274 return m.array_hash_map.contains(key);
173 }275 }
174276
...@@ -181,7 +283,7 @@ pub const Map = struct {...@@ -181,7 +283,7 @@ pub const Map = struct {
181 /// This invalidates the value returned by get() for this key.283 /// This invalidates the value returned by get() for this key.
182 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.284 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
183 pub fn swapRemove(self: *Map, key: []const u8) bool {285 pub fn swapRemove(self: *Map, key: []const u8) bool {
184 assert(unicode.wtf8ValidateSlice(key));286 assert(validateKey(key));
185 const kv = self.array_hash_map.fetchSwapRemove(key) orelse return false;287 const kv = self.array_hash_map.fetchSwapRemove(key) orelse return false;
186 const gpa = self.allocator;288 const gpa = self.allocator;
187 gpa.free(kv.key);289 gpa.free(kv.key);
...@@ -198,7 +300,7 @@ pub const Map = struct {...@@ -198,7 +300,7 @@ pub const Map = struct {
198 /// This invalidates the value returned by get() for this key.300 /// This invalidates the value returned by get() for this key.
199 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.301 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
200 pub fn orderedRemove(self: *Map, key: []const u8) bool {302 pub fn orderedRemove(self: *Map, key: []const u8) bool {
201 assert(unicode.wtf8ValidateSlice(key));303 assert(validateKey(key));
202 const kv = self.array_hash_map.fetchOrderedRemove(key) orelse return false;304 const kv = self.array_hash_map.fetchOrderedRemove(key) orelse return false;
203 const gpa = self.allocator;305 const gpa = self.allocator;
204 gpa.free(kv.key);306 gpa.free(kv.key);
...@@ -233,105 +335,120 @@ pub const Map = struct {...@@ -233,105 +335,120 @@ pub const Map = struct {
233335
234 /// Creates a null-delimited environment variable block in the format336 /// Creates a null-delimited environment variable block in the format
235 /// expected by POSIX, from a hash map plus options.337 /// expected by POSIX, from a hash map plus options.
236 pub fn createBlockPosix(338 pub fn createPosixBlock(
237 map: *const Map,339 map: *const Map,
238 arena: Allocator,340 gpa: Allocator,
239 options: CreateBlockPosixOptions,341 options: CreatePosixBlockOptions,
240 ) Allocator.Error![:null]?[*:0]u8 {342 ) Allocator.Error!PosixBlock {
241 const ZigProgressAction = enum { nothing, edit, delete, add };343 const ZigProgressAction = enum { nothing, edit, delete, add };
242 const zig_progress_action: ZigProgressAction = a: {344 const zig_progress_action: ZigProgressAction = action: {
243 const fd = options.zig_progress_fd orelse break :a .nothing;345 const fd = options.zig_progress_fd orelse break :action .nothing;
244 const exists = map.get("ZIG_PROGRESS") != null;346 const exists = map.contains("ZIG_PROGRESS");
245 if (fd >= 0) {347 if (fd >= 0) {
246 break :a if (exists) .edit else .add;348 break :action if (exists) .edit else .add;
247 } else {349 } else {
248 if (exists) break :a .delete;350 if (exists) break :action .delete;
249 }351 }
250 break :a .nothing;352 break :action .nothing;
251 };353 };
252354
253 const envp_count: usize = c: {355 const envp = try gpa.allocSentinel(?[*:0]u8, len: {
254 var c: usize = map.count();356 var len: usize = map.count();
255 switch (zig_progress_action) {357 switch (zig_progress_action) {
256 .add => c += 1,358 .add => len += 1,
257 .delete => c -= 1,359 .delete => len -= 1,
258 .nothing, .edit => {},360 .nothing, .edit => {},
259 }361 }
260 break :c c;362 break :len len;
261 };363 }, null);
262364 var envp_len: usize = 0;
263 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);365 errdefer {
264 var i: usize = 0;366 envp[envp_len] = null;
367 PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa);
368 }
265369
266 if (zig_progress_action == .add) {370 if (zig_progress_action == .add) {
267 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);371 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
268 i += 1;372 envp_len += 1;
269 }373 }
270374
271 {375 for (map.keys(), map.values()) |key, value| {
272 var it = map.iterator();376 if (mem.eql(u8, key, "ZIG_PROGRESS")) switch (zig_progress_action) {
273 while (it.next()) |pair| {377 .add => unreachable,
274 if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) {378 .delete => continue,
275 .add => unreachable,379 .edit => {
276 .delete => continue,380 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={d}", .{
277 .edit => {381 key, options.zig_progress_fd.?,
278 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{382 }, 0);
279 pair.key_ptr.*, options.zig_progress_fd.?,383 envp_len += 1;
280 }, 0);384 continue;
281 i += 1;385 },
282 continue;386 .nothing => {},
283 },387 };
284 .nothing => {},388
285 };389 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={s}", .{ key, value }, 0);
286390 envp_len += 1;
287 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0);
288 i += 1;
289 }
290 }391 }
291392
292 assert(i == envp_count);393 assert(envp_len == envp.len);
293 return envp_buf;394 return .{ .slice = envp };
294 }395 }
295396
296 /// Caller owns result.397 /// Caller owns result.
297 pub fn createBlockWindows(map: *const Map, gpa: Allocator) error{ OutOfMemory, InvalidWtf8 }![:0]u16 {398 pub fn createWindowsBlock(
399 map: *const Map,
400 gpa: Allocator,
401 options: CreateWindowsBlockOptions,
402 ) error{ OutOfMemory, InvalidWtf8 }!WindowsBlock {
298 // count bytes needed403 // count bytes needed
299 const max_chars_needed = x: {404 const max_chars_needed = max_chars_needed: {
300 // Only need 2 trailing NUL code units for an empty environment405 var max_chars_needed: usize = "\x00".len;
301 var max_chars_needed: usize = if (map.count() == 0) 2 else 1;406 if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
302 var it = map.iterator();407 max_chars_needed += std.fmt.count("ZIG_PROGRESS={d}\x00", .{@intFromPtr(handle)});
303 while (it.next()) |pair| {408 };
304 // +1 for '='409 for (map.keys(), map.values()) |key, value| {
305 // +1 for null byte410 if (options.zig_progress_handle != null and eqlKeys(key, "ZIG_PROGRESS")) continue;
306 max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2;411 max_chars_needed += key.len + "=".len + value.len + "\x00".len;
307 }412 }
308 break :x max_chars_needed;413 break :max_chars_needed @max("\x00\x00".len, max_chars_needed);
309 };414 };
310 const result = try gpa.alloc(u16, max_chars_needed);415 const block = try gpa.alloc(u16, max_chars_needed);
311 errdefer gpa.free(result);416 errdefer gpa.free(block);
312417
313 var it = map.iterator();
314 var i: usize = 0;418 var i: usize = 0;
315 while (it.next()) |pair| {419 if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
316 i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);420 @memcpy(
317 result[i] = '=';421 block[i..][0.."ZIG_PROGRESS=".len],
422 &[_]u16{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S', '=' },
423 );
424 i += "ZIG_PROGRESS=".len;
425 var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined;
426 const value = std.fmt.bufPrint(&value_buf, "{d}", .{@intFromPtr(handle)}) catch unreachable;
427 for (block[i..][0..value.len], value) |*r, v| r.* = v;
428 i += value.len;
429 block[i] = 0;
318 i += 1;430 i += 1;
319 i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);431 };
320 result[i] = 0;432 for (map.keys(), map.values()) |key, value| {
433 if (options.zig_progress_handle != null and eqlKeys(key, "ZIG_PROGRESS")) continue;
434 i += try unicode.wtf8ToWtf16Le(block[i..], key);
435 block[i] = '=';
436 i += 1;
437 i += try unicode.wtf8ToWtf16Le(block[i..], value);
438 block[i] = 0;
321 i += 1;439 i += 1;
322 }440 }
323 result[i] = 0;
324 i += 1;
325 // An empty environment is a special case that requires a redundant441 // An empty environment is a special case that requires a redundant
326 // NUL terminator. CreateProcess will read the second code unit even442 // NUL terminator. CreateProcess will read the second code unit even
327 // though theoretically the first should be enough to recognize that the443 // though theoretically the first should be enough to recognize that the
328 // environment is empty (see https://nullprogram.com/blog/2023/08/23/)444 // environment is empty (see https://nullprogram.com/blog/2023/08/23/)
329 if (map.count() == 0) {445 for (0..2) |_| {
330 result[i] = 0;446 block[i] = 0;
331 i += 1;447 i += 1;
332 }448 if (i >= 2) break;
333 const reallocated = try gpa.realloc(result, i);449 } else unreachable;
334 return reallocated[0 .. i - 1 :0];450 const reallocated = try gpa.realloc(block, i);
451 return .{ .slice = reallocated[0 .. i - 1 :0] };
335 }452 }
336};453};
337454
...@@ -344,13 +461,18 @@ pub const CreateMapError = error{...@@ -344,13 +461,18 @@ pub const CreateMapError = error{
344461
345/// Allocates a `Map` and copies environment block into it.462/// Allocates a `Map` and copies environment block into it.
346pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {463pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
347 if (native_os == .windows)464 var map = Map.init(allocator);
348 return createMapWide(std.os.windows.peb().ProcessParameters.Environment, allocator);465 errdefer map.deinit();
349466 if (native_os == .windows) empty: {
350 var result = Map.init(allocator);467 if (!env.block.use_global) break :empty;
351 errdefer result.deinit();468
469 const peb = std.os.windows.peb();
470 assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
471 defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
472 try map.putWindowsBlock(.{ .ptr = peb.ProcessParameters.Environment });
473 } else if (native_os == .wasi and !builtin.link_libc) empty: {
474 if (!env.block.use_global) break :empty;
352475
353 if (native_os == .wasi and !builtin.link_libc) {
354 var environ_count: usize = undefined;476 var environ_count: usize = undefined;
355 var environ_buf_size: usize = undefined;477 var environ_buf_size: usize = undefined;
356478
...@@ -360,7 +482,7 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {...@@ -360,7 +482,7 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
360 }482 }
361483
362 if (environ_count == 0) {484 if (environ_count == 0) {
363 return result;485 return map;
364 }486 }
365487
366 const environ = try allocator.alloc([*:0]u8, environ_count);488 const environ = try allocator.alloc([*:0]u8, environ_count);
...@@ -373,63 +495,9 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {...@@ -373,63 +495,9 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
373 return posix.unexpectedErrno(environ_get_ret);495 return posix.unexpectedErrno(environ_get_ret);
374 }496 }
375497
376 for (environ) |line| {498 try map.putPosixBlock(.{ .slice = environ });
377 const pair = mem.sliceTo(line, 0);499 } else try map.putPosixBlock(env.block.view());
378 var parts = mem.splitScalar(u8, pair, '=');500 return map;
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;
433}501}
434502
435pub const ContainsError = error{503pub const ContainsError = error{
...@@ -451,6 +519,7 @@ pub const ContainsError = error{...@@ -451,6 +519,7 @@ pub const ContainsError = error{
451/// * `containsConstant`519/// * `containsConstant`
452/// * `containsUnempty`520/// * `containsUnempty`
453pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool {521pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool {
522 if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8;
454 var map = try createMap(environ, gpa);523 var map = try createMap(environ, gpa);
455 defer map.deinit();524 defer map.deinit();
456 return map.contains(key);525 return map.contains(key);
...@@ -464,6 +533,7 @@ pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError...@@ -464,6 +533,7 @@ pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError
464/// * `containsUnemptyConstant`533/// * `containsUnemptyConstant`
465/// * `contains`534/// * `contains`
466pub fn containsUnempty(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool {535pub fn containsUnempty(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool {
536 if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8;
467 var map = try createMap(environ, gpa);537 var map = try createMap(environ, gpa);
468 defer map.deinit();538 defer map.deinit();
469 const value = map.get(key) orelse return false;539 const value = map.get(key) orelse return false;
...@@ -516,16 +586,15 @@ pub inline fn containsUnemptyConstant(environ: Environ, comptime key: []const u8...@@ -516,16 +586,15 @@ pub inline fn containsUnemptyConstant(environ: Environ, comptime key: []const u8
516/// * `createMap`586/// * `createMap`
517pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 {587pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 {
518 if (mem.findScalar(u8, key, '=') != null) return null;588 if (mem.findScalar(u8, key, '=') != null) return null;
519 for (environ.block) |opt_line| {589 for (environ.block.view().slice) |entry| {
520 const line = opt_line.?;590 var entry_i: usize = 0;
521 var line_i: usize = 0;591 while (entry[entry_i] != 0) : (entry_i += 1) {
522 while (line[line_i] != 0) : (line_i += 1) {592 if (entry_i == key.len) break;
523 if (line_i == key.len) break;593 if (entry[entry_i] != key[entry_i]) break;
524 if (line[line_i] != key[line_i]) break;
525 }594 }
526 if ((line_i != key.len) or (line[line_i] != '=')) continue;595 if ((entry_i != key.len) or (entry[entry_i] != '=')) continue;
527596
528 return mem.sliceTo(line + line_i + 1, 0);597 return mem.sliceTo(entry + entry_i + 1, 0);
529 }598 }
530 return null;599 return null;
531}600}
...@@ -541,14 +610,16 @@ pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 {...@@ -541,14 +610,16 @@ pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 {
541/// * `containsConstant`610/// * `containsConstant`
542/// * `contains`611/// * `contains`
543pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 {612pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 {
544 comptime assert(native_os == .windows);
545 comptime assert(@TypeOf(environ.block) == void);
546
547 // '=' anywhere but the start makes this an invalid environment variable name.613 // '=' anywhere but the start makes this an invalid environment variable name.
548 const key_slice = mem.sliceTo(key, 0);614 const key_slice = mem.sliceTo(key, 0);
549 if (key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') != null) return null;615 assert(key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') == null);
550616
551 const ptr = std.os.windows.peb().ProcessParameters.Environment;617 if (!environ.block.use_global) return null;
618
619 const peb = std.os.windows.peb();
620 assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
621 defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
622 const ptr = peb.ProcessParameters.Environment;
552623
553 var i: usize = 0;624 var i: usize = 0;
554 while (ptr[i] != 0) {625 while (ptr[i] != 0) {
...@@ -558,8 +629,7 @@ pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 {...@@ -558,8 +629,7 @@ pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 {
558 // so we need a special case to not treat = as a key/value separator629 // so we need a special case to not treat = as a key/value separator
559 // if it's the first character.630 // if it's the first character.
560 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133631 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
561 const equal_search_start: usize = if (key_value[0] == '=') 1 else 0;632 const equal_index = mem.findScalarPos(u16, key_value, 1, '=') orelse {
562 const equal_index = mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse {
563 // This is enforced by CreateProcess.633 // This is enforced by CreateProcess.
564 // If violated, CreateProcess will fail with INVALID_PARAMETER.634 // If violated, CreateProcess will fail with INVALID_PARAMETER.
565 unreachable; // must contain a =635 unreachable; // must contain a =
...@@ -598,13 +668,14 @@ pub const GetAllocError = error{...@@ -598,13 +668,14 @@ pub const GetAllocError = error{
598/// See also:668/// See also:
599/// * `createMap`669/// * `createMap`
600pub fn getAlloc(environ: Environ, gpa: Allocator, key: []const u8) GetAllocError![]u8 {670pub fn getAlloc(environ: Environ, gpa: Allocator, key: []const u8) GetAllocError![]u8 {
671 if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8;
601 var map = createMap(environ, gpa) catch return error.OutOfMemory;672 var map = createMap(environ, gpa) catch return error.OutOfMemory;
602 defer map.deinit();673 defer map.deinit();
603 const val = map.get(key) orelse return error.EnvironmentVariableMissing;674 const val = map.get(key) orelse return error.EnvironmentVariableMissing;
604 return gpa.dupe(u8, val);675 return gpa.dupe(u8, val);
605}676}
606677
607pub const CreateBlockPosixOptions = struct {678pub const CreatePosixBlockOptions = struct {
608 /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.679 /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
609 /// If non-null, negative means to remove the environment variable, and >= 0680 /// If non-null, negative means to remove the environment variable, and >= 0
610 /// means to provide it with the given integer.681 /// means to provide it with the given integer.
...@@ -613,67 +684,147 @@ pub const CreateBlockPosixOptions = struct {...@@ -613,67 +684,147 @@ pub const CreateBlockPosixOptions = struct {
613684
614/// Creates a null-delimited environment variable block in the format expected685/// Creates a null-delimited environment variable block in the format expected
615/// by POSIX, from a different one.686/// by POSIX, from a different one.
616pub fn createBlockPosix(687pub fn createPosixBlock(
617 existing: Environ,688 existing: Environ,
618 arena: Allocator,689 gpa: Allocator,
619 options: CreateBlockPosixOptions,690 options: CreatePosixBlockOptions,
620) Allocator.Error![:null]?[*:0]u8 {691) Allocator.Error!PosixBlock {
621 const contains_zig_progress = for (existing.block) |opt_line| {692 const contains_zig_progress = for (existing.block.view().slice) |entry| {
622 if (mem.eql(u8, mem.sliceTo(opt_line.?, '='), "ZIG_PROGRESS")) break true;693 if (mem.eql(u8, mem.sliceTo(entry, '='), "ZIG_PROGRESS")) break true;
623 } else false;694 } else false;
624695
625 const ZigProgressAction = enum { nothing, edit, delete, add };696 const ZigProgressAction = enum { nothing, edit, delete, add };
626 const zig_progress_action: ZigProgressAction = a: {697 const zig_progress_action: ZigProgressAction = action: {
627 const fd = options.zig_progress_fd orelse break :a .nothing;698 const fd = options.zig_progress_fd orelse break :action .nothing;
628 if (fd >= 0) {699 if (fd >= 0) {
629 break :a if (contains_zig_progress) .edit else .add;700 break :action if (contains_zig_progress) .edit else .add;
630 } else {701 } else {
631 if (contains_zig_progress) break :a .delete;702 if (contains_zig_progress) break :action .delete;
632 }703 }
633 break :a .nothing;704 break :action .nothing;
634 };705 };
635706
636 const envp_count: usize = c: {707 const envp = try gpa.allocSentinel(?[*:0]u8, len: {
637 var count: usize = existing.block.len;708 var len: usize = existing.block.slice.len;
638 switch (zig_progress_action) {709 switch (zig_progress_action) {
639 .add => count += 1,710 .add => len += 1,
640 .delete => count -= 1,711 .delete => len -= 1,
641 .nothing, .edit => {},712 .nothing, .edit => {},
642 }713 }
643 break :c count;714 break :len len;
644 };715 }, null);
645716 var envp_len: usize = 0;
646 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);717 errdefer {
647 var i: usize = 0;718 envp[envp_len] = null;
648 var existing_index: usize = 0;719 PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa);
649720 }
650 if (zig_progress_action == .add) {721 if (zig_progress_action == .add) {
651 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);722 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
652 i += 1;723 envp_len += 1;
653 }724 }
654725
655 while (existing.block[existing_index]) |line| : (existing_index += 1) {726 var existing_index: usize = 0;
656 if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {727 while (existing.block.slice[existing_index]) |entry| : (existing_index += 1) {
728 if (mem.eql(u8, mem.sliceTo(entry, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
657 .add => unreachable,729 .add => unreachable,
658 .delete => continue,730 .delete => continue,
659 .edit => {731 .edit => {
660 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);732 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
661 i += 1;733 envp_len += 1;
662 continue;734 continue;
663 },735 },
664 .nothing => {},736 .nothing => {},
665 };737 };
666 envp_buf[i] = try arena.dupeZ(u8, mem.span(line));738 envp[envp_len] = try gpa.dupeZ(u8, mem.span(entry));
667 i += 1;739 envp_len += 1;
668 }740 }
669741
670 assert(i == envp_count);742 assert(envp_len == envp.len);
671 return envp_buf;743 return .{ .slice = envp };
672}744}
673745
674test "Map.createBlock" {746pub const CreateWindowsBlockOptions = struct {
675 const allocator = testing.allocator;747 /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
676 var envmap = Map.init(allocator);748 /// If non-null, `std.os.windows.INVALID_HANDLE_VALUE` means to remove the
749 /// environment variable, otherwise provide it with the given handle as an integer.
750 zig_progress_handle: ?std.os.windows.HANDLE = null,
751};
752
753/// Creates a null-delimited environment variable block in the format expected
754/// by POSIX, from a different one.
755pub fn createWindowsBlock(
756 existing: Environ,
757 gpa: Allocator,
758 options: CreateWindowsBlockOptions,
759) Allocator.Error!WindowsBlock {
760 if (!existing.block.use_global) return .{
761 .slice = try gpa.dupeSentinel(u16, WindowsBlock.empty.slice, 0),
762 };
763 const peb = std.os.windows.peb();
764 assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
765 defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
766 const existing_block = peb.ProcessParameters.Environment;
767 var ranges: [2]struct { start: usize, end: usize } = undefined;
768 var ranges_len: usize = 0;
769 ranges[ranges_len].start = 0;
770 const zig_progress_key = [_]u16{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S', '=' };
771 const needed_len = needed_len: {
772 var needed_len: usize = "\x00".len;
773 if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
774 needed_len += std.fmt.count("ZIG_PROGRESS={d}\x00", .{@intFromPtr(handle)});
775 };
776 var i: usize = 0;
777 while (existing_block[i] != 0) {
778 const start = i;
779 const entry = mem.sliceTo(existing_block[start..], 0);
780 i += entry.len + "\x00".len;
781 if (options.zig_progress_handle != null and entry.len >= zig_progress_key.len and
782 std.os.windows.eqlIgnoreCaseWtf16(entry[0..zig_progress_key.len], &zig_progress_key))
783 {
784 ranges[ranges_len].end = start;
785 ranges_len += 1;
786 ranges[ranges_len].start = i;
787 } else needed_len += entry.len + "\x00".len;
788 }
789 ranges[ranges_len].end = i;
790 ranges_len += 1;
791 break :needed_len @max("\x00\x00".len, needed_len);
792 };
793 const block = try gpa.alloc(u16, needed_len);
794 errdefer gpa.free(block);
795 var i: usize = 0;
796 if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
797 @memcpy(block[i..][0..zig_progress_key.len], &zig_progress_key);
798 i += zig_progress_key.len;
799 var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined;
800 const value = std.fmt.bufPrint(&value_buf, "{d}", .{@intFromPtr(handle)}) catch unreachable;
801 for (block[i..][0..value.len], value) |*r, v| r.* = v;
802 i += value.len;
803 block[i] = 0;
804 i += 1;
805 };
806 for (ranges[0..ranges_len]) |range| {
807 const range_len = range.end - range.start;
808 @memcpy(block[i..][0..range_len], existing_block[range.start..range.end]);
809 i += range_len;
810 }
811 // An empty environment is a special case that requires a redundant
812 // NUL terminator. CreateProcess will read the second code unit even
813 // though theoretically the first should be enough to recognize that the
814 // environment is empty (see https://nullprogram.com/blog/2023/08/23/)
815 for (0..2) |_| {
816 block[i] = 0;
817 i += 1;
818 if (i >= 2) break;
819 } else unreachable;
820 assert(i == block.len);
821 return .{ .slice = block[0 .. i - 1 :0] };
822}
823
824test "Map.createPosixBlock" {
825 const gpa = testing.allocator;
826
827 var envmap = Map.init(gpa);
677 defer envmap.deinit();828 defer envmap.deinit();
678829
679 try envmap.put("HOME", "/home/ifreund");830 try envmap.put("HOME", "/home/ifreund");
...@@ -682,29 +833,24 @@ test "Map.createBlock" {...@@ -682,29 +833,24 @@ test "Map.createBlock" {
682 try envmap.put("DEBUGINFOD_URLS", " ");833 try envmap.put("DEBUGINFOD_URLS", " ");
683 try envmap.put("XCURSOR_SIZE", "24");834 try envmap.put("XCURSOR_SIZE", "24");
684835
685 var arena = std.heap.ArenaAllocator.init(allocator);836 const block = try envmap.createPosixBlock(gpa, .{});
686 defer arena.deinit();837 defer block.deinit(gpa);
687 const environ = try envmap.createBlockPosix(arena.allocator(), .{});
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{
692 "HOME=/home/ifreund",842 "HOME=/home/ifreund",
693 "WAYLAND_DISPLAY=wayland-1",843 "WAYLAND_DISPLAY=wayland-1",
694 "DISPLAY=:1",844 "DISPLAY=:1",
695 "DEBUGINFOD_URLS= ",845 "DEBUGINFOD_URLS= ",
696 "XCURSOR_SIZE=24",846 "XCURSOR_SIZE=24",
697 }) |target| {847 }, block.slice) |expected, actual| try testing.expectEqualStrings(expected, mem.span(actual.?));
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 }
704}848}
705849
706test Map {850test Map {
707 var env = Map.init(testing.allocator);851 const gpa = testing.allocator;
852
853 var env: Map = .init(gpa);
708 defer env.deinit();854 defer env.deinit();
709855
710 try env.put("SOMETHING_NEW", "hello");856 try env.put("SOMETHING_NEW", "hello");
...@@ -740,6 +886,7 @@ test Map {...@@ -740,6 +886,7 @@ test Map {
740 try testing.expect(env.swapRemove("SOMETHING_NEW"));886 try testing.expect(env.swapRemove("SOMETHING_NEW"));
741 try testing.expect(!env.swapRemove("SOMETHING_NEW"));887 try testing.expect(!env.swapRemove("SOMETHING_NEW"));
742 try testing.expect(env.get("SOMETHING_NEW") == null);888 try testing.expect(env.get("SOMETHING_NEW") == null);
889 try testing.expect(!env.contains("SOMETHING_NEW"));
743890
744 try testing.expectEqual(@as(Map.Size, 1), env.count());891 try testing.expectEqual(@as(Map.Size, 1), env.count());
745892
...@@ -749,10 +896,10 @@ test Map {...@@ -749,10 +896,10 @@ test Map {
749 try testing.expectEqualStrings("something else", env.get("кириллица").?);896 try testing.expectEqualStrings("something else", env.get("кириллица").?);
750897
751 // and WTF-8 that's not valid UTF-8898 // 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{
753 mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate900 mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate
754 });901 });
755 defer testing.allocator.free(wtf8_with_surrogate_pair);902 defer gpa.free(wtf8_with_surrogate_pair);
756903
757 try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair);904 try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair);
758 try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?);905 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" {...@@ -769,13 +916,9 @@ test "convert from Environ to Map and back again" {
769 defer map.deinit();916 defer map.deinit();
770 try map.put("FOO", "BAR");917 try map.put("FOO", "BAR");
771 try map.put("A", "");918 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
780 try testing.expectEqual(true, environ.contains(gpa, "FOO"));923 try testing.expectEqual(true, environ.contains(gpa, "FOO"));
781 try testing.expectEqual(false, environ.contains(gpa, "BAR"));924 try testing.expectEqual(false, environ.contains(gpa, "BAR"));
...@@ -783,7 +926,6 @@ test "convert from Environ to Map and back again" {...@@ -783,7 +926,6 @@ test "convert from Environ to Map and back again" {
783 try testing.expectEqual(true, environ.containsConstant("A"));926 try testing.expectEqual(true, environ.containsConstant("A"));
784 try testing.expectEqual(false, environ.containsUnempty(gpa, "A"));927 try testing.expectEqual(false, environ.containsUnempty(gpa, "A"));
785 try testing.expectEqual(false, environ.containsUnemptyConstant("A"));928 try testing.expectEqual(false, environ.containsUnemptyConstant("A"));
786 try testing.expectEqual(true, environ.contains(gpa, ""));
787 try testing.expectEqual(false, environ.contains(gpa, "B"));929 try testing.expectEqual(false, environ.contains(gpa, "B"));
788930
789 try testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(gpa, "BOGUS"));931 try testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(gpa, "BOGUS"));
...@@ -800,23 +942,47 @@ test "convert from Environ to Map and back again" {...@@ -800,23 +942,47 @@ test "convert from Environ to Map and back again" {
800 try testing.expectEqualDeep(map.values(), map2.values());942 try testing.expectEqualDeep(map.values(), map2.values());
801}943}
802944
803test createMapWide {945test "Map.putPosixBlock" {
804 if (builtin.cpu.arch.endian() == .big) return error.SkipZigTest; // TODO946 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
806 const gpa = testing.allocator;969 const gpa = testing.allocator;
807970
808 var map: Map = .init(gpa);971 var map: Map = .init(gpa);
809 defer map.deinit();972 defer map.deinit();
973
810 try map.put("FOO", "BAR");974 try map.put("FOO", "BAR");
811 try map.put("A", "");975 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);979 const block = try map.createWindowsBlock(gpa, .{});
815 defer gpa.free(environ);980 defer block.deinit(gpa);
816981
817 var map2 = try createMapWide(environ, gpa);982 var map2: Map = .init(gpa);
818 defer map2.deinit();983 defer map2.deinit();
984 try map2.putWindowsBlock(block.view());
819985
820 try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B" }, map2.keys());986 try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B", "ZIG_PROGRESS" }, map2.keys());
821 try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "" }, map2.values());987 try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "", "unchanged" }, map2.values());
822}988}
lib/std/start.zig+9-8
...@@ -90,15 +90,15 @@ fn _DllMainCRTStartup(...@@ -90,15 +90,15 @@ fn _DllMainCRTStartup(
90fn wasm_freestanding_start() callconv(.c) void {90fn wasm_freestanding_start() callconv(.c) void {
91 // This is marked inline because for some reason LLVM in91 // This is marked inline because for some reason LLVM in
92 // release mode fails to inline it, and we want fewer call frames in stack traces.92 // 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 });
94}94}
9595
96fn startWasi() callconv(.c) void {96fn startWasi() callconv(.c) void {
97 // The function call is marked inline because for some reason LLVM in97 // The function call is marked inline because for some reason LLVM in
98 // release mode fails to inline it, and we want fewer call frames in stack traces.98 // release mode fails to inline it, and we want fewer call frames in stack traces.
99 switch (builtin.wasi_exec_model) {99 switch (builtin.wasi_exec_model) {
100 .reactor => _ = @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, .{ {}, {} })),101 .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global })),
102 }102 }
103}103}
104104
...@@ -476,7 +476,7 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn {...@@ -476,7 +476,7 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn {
476 const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;476 const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
477 const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)];477 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));
480}480}
481481
482fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn {482fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn {
...@@ -620,13 +620,14 @@ fn expandStackSize(phdrs: []elf.Phdr) void {...@@ -620,13 +620,14 @@ fn expandStackSize(phdrs: []elf.Phdr) void {
620}620}
621621
622inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) u8 {622inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) u8 {
623 const env_block: std.process.Environ.Block = .{ .slice = envp };
623 if (std.Options.debug_threaded_io) |t| {624 if (std.Options.debug_threaded_io) |t| {
624 if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0];625 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 } };
626 }627 }
627 std.Thread.maybeAttachSignalStack();628 std.Thread.maybeAttachSignalStack();
628 std.debug.maybeEnableSegfaultHandler();629 std.debug.maybeEnableSegfaultHandler();
629 return callMain(argv[0..argc], envp);630 return callMain(argv[0..argc], env_block);
630}631}
631632
632fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int {633fn 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...@@ -648,7 +649,7 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal
648 std.debug.maybeEnableSegfaultHandler();649 std.debug.maybeEnableSegfaultHandler();
649 const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;650 const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
650 const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)];651 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);
652 },653 },
653 else => {},654 else => {},
654 }655 }
...@@ -661,7 +662,7 @@ fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int {...@@ -661,7 +662,7 @@ fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int {
661 if (@sizeOf(std.Io.Threaded.Argv0) != 0) {662 if (@sizeOf(std.Io.Threaded.Argv0) != 0) {
662 if (std.Options.debug_threaded_io) |t| t.argv0.value = argv[0];663 if (std.Options.debug_threaded_io) |t| t.argv0.value = argv[0];
663 }664 }
664 return callMain(argv, &.{});665 return callMain(argv, .empty);
665}666}
666667
667/// General error message for a malformed return type668/// General error message for a malformed return type
test/standalone/env_vars/main.zig-23
...@@ -12,14 +12,10 @@ pub fn main(init: std.process.Init) !void {...@@ -12,14 +12,10 @@ pub fn main(init: std.process.Init) !void {
12 // containsUnempty12 // containsUnempty
13 {13 {
14 try std.testing.expect(try environ.containsUnempty(allocator, "FOO"));14 try std.testing.expect(try environ.containsUnempty(allocator, "FOO"));
15 try std.testing.expect(!(try environ.containsUnempty(allocator, "FOO=")));
16 try std.testing.expect(!(try environ.containsUnempty(allocator, "FO")));
17 try std.testing.expect(!(try environ.containsUnempty(allocator, "FOOO")));
18 if (builtin.os.tag == .windows) {15 if (builtin.os.tag == .windows) {
19 try std.testing.expect(try environ.containsUnempty(allocator, "foo"));16 try std.testing.expect(try environ.containsUnempty(allocator, "foo"));
20 }17 }
21 try std.testing.expect(try environ.containsUnempty(allocator, "EQUALS"));18 try std.testing.expect(try environ.containsUnempty(allocator, "EQUALS"));
22 try std.testing.expect(!(try environ.containsUnempty(allocator, "EQUALS=ABC")));
23 try std.testing.expect(try environ.containsUnempty(allocator, "КИРиллИЦА"));19 try std.testing.expect(try environ.containsUnempty(allocator, "КИРиллИЦА"));
24 if (builtin.os.tag == .windows) {20 if (builtin.os.tag == .windows) {
25 try std.testing.expect(try environ.containsUnempty(allocator, "кирИЛЛица"));21 try std.testing.expect(try environ.containsUnempty(allocator, "кирИЛЛица"));
...@@ -35,14 +31,10 @@ pub fn main(init: std.process.Init) !void {...@@ -35,14 +31,10 @@ pub fn main(init: std.process.Init) !void {
35 // containsUnemptyConstant31 // containsUnemptyConstant
36 {32 {
37 try std.testing.expect(environ.containsUnemptyConstant("FOO"));33 try std.testing.expect(environ.containsUnemptyConstant("FOO"));
38 try std.testing.expect(!environ.containsUnemptyConstant("FOO="));
39 try std.testing.expect(!environ.containsUnemptyConstant("FO"));
40 try std.testing.expect(!environ.containsUnemptyConstant("FOOO"));
41 if (builtin.os.tag == .windows) {34 if (builtin.os.tag == .windows) {
42 try std.testing.expect(environ.containsUnemptyConstant("foo"));35 try std.testing.expect(environ.containsUnemptyConstant("foo"));
43 }36 }
44 try std.testing.expect(environ.containsUnemptyConstant("EQUALS"));37 try std.testing.expect(environ.containsUnemptyConstant("EQUALS"));
45 try std.testing.expect(!environ.containsUnemptyConstant("EQUALS=ABC"));
46 try std.testing.expect(environ.containsUnemptyConstant("КИРиллИЦА"));38 try std.testing.expect(environ.containsUnemptyConstant("КИРиллИЦА"));
47 if (builtin.os.tag == .windows) {39 if (builtin.os.tag == .windows) {
48 try std.testing.expect(environ.containsUnemptyConstant("кирИЛЛица"));40 try std.testing.expect(environ.containsUnemptyConstant("кирИЛЛица"));
...@@ -58,14 +50,10 @@ pub fn main(init: std.process.Init) !void {...@@ -58,14 +50,10 @@ pub fn main(init: std.process.Init) !void {
58 // contains50 // contains
59 {51 {
60 try std.testing.expect(try environ.contains(allocator, "FOO"));52 try std.testing.expect(try environ.contains(allocator, "FOO"));
61 try std.testing.expect(!(try environ.contains(allocator, "FOO=")));
62 try std.testing.expect(!(try environ.contains(allocator, "FO")));
63 try std.testing.expect(!(try environ.contains(allocator, "FOOO")));
64 if (builtin.os.tag == .windows) {53 if (builtin.os.tag == .windows) {
65 try std.testing.expect(try environ.contains(allocator, "foo"));54 try std.testing.expect(try environ.contains(allocator, "foo"));
66 }55 }
67 try std.testing.expect(try environ.contains(allocator, "EQUALS"));56 try std.testing.expect(try environ.contains(allocator, "EQUALS"));
68 try std.testing.expect(!(try environ.contains(allocator, "EQUALS=ABC")));
69 try std.testing.expect(try environ.contains(allocator, "КИРиллИЦА"));57 try std.testing.expect(try environ.contains(allocator, "КИРиллИЦА"));
70 if (builtin.os.tag == .windows) {58 if (builtin.os.tag == .windows) {
71 try std.testing.expect(try environ.contains(allocator, "кирИЛЛица"));59 try std.testing.expect(try environ.contains(allocator, "кирИЛЛица"));
...@@ -81,14 +69,10 @@ pub fn main(init: std.process.Init) !void {...@@ -81,14 +69,10 @@ pub fn main(init: std.process.Init) !void {
81 // containsConstant69 // containsConstant
82 {70 {
83 try std.testing.expect(environ.containsConstant("FOO"));71 try std.testing.expect(environ.containsConstant("FOO"));
84 try std.testing.expect(!environ.containsConstant("FOO="));
85 try std.testing.expect(!environ.containsConstant("FO"));
86 try std.testing.expect(!environ.containsConstant("FOOO"));
87 if (builtin.os.tag == .windows) {72 if (builtin.os.tag == .windows) {
88 try std.testing.expect(environ.containsConstant("foo"));73 try std.testing.expect(environ.containsConstant("foo"));
89 }74 }
90 try std.testing.expect(environ.containsConstant("EQUALS"));75 try std.testing.expect(environ.containsConstant("EQUALS"));
91 try std.testing.expect(!environ.containsConstant("EQUALS=ABC"));
92 try std.testing.expect(environ.containsConstant("КИРиллИЦА"));76 try std.testing.expect(environ.containsConstant("КИРиллИЦА"));
93 if (builtin.os.tag == .windows) {77 if (builtin.os.tag == .windows) {
94 try std.testing.expect(environ.containsConstant("кирИЛЛица"));78 try std.testing.expect(environ.containsConstant("кирИЛЛица"));
...@@ -104,14 +88,10 @@ pub fn main(init: std.process.Init) !void {...@@ -104,14 +88,10 @@ pub fn main(init: std.process.Init) !void {
104 // getAlloc88 // getAlloc
105 {89 {
106 try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "FOO"));90 try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "FOO"));
107 try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOO="));
108 try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FO"));
109 try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOOO"));
110 if (builtin.os.tag == .windows) {91 if (builtin.os.tag == .windows) {
111 try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "foo"));92 try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "foo"));
112 }93 }
113 try std.testing.expectEqualSlices(u8, "ABC=123", try environ.getAlloc(arena, "EQUALS"));94 try std.testing.expectEqualSlices(u8, "ABC=123", try environ.getAlloc(arena, "EQUALS"));
114 try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "EQUALS=ABC"));
115 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "КИРиллИЦА"));95 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "КИРиллИЦА"));
116 if (builtin.os.tag == .windows) {96 if (builtin.os.tag == .windows) {
117 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "кирИЛЛица"));97 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "кирИЛЛица"));
...@@ -130,13 +110,10 @@ pub fn main(init: std.process.Init) !void {...@@ -130,13 +110,10 @@ pub fn main(init: std.process.Init) !void {
130 defer environ_map.deinit();110 defer environ_map.deinit();
131111
132 try std.testing.expectEqualSlices(u8, "123", environ_map.get("FOO").?);112 try std.testing.expectEqualSlices(u8, "123", environ_map.get("FOO").?);
133 try std.testing.expectEqual(null, environ_map.get("FO"));
134 try std.testing.expectEqual(null, environ_map.get("FOOO"));
135 if (builtin.os.tag == .windows) {113 if (builtin.os.tag == .windows) {
136 try std.testing.expectEqualSlices(u8, "123", environ_map.get("foo").?);114 try std.testing.expectEqualSlices(u8, "123", environ_map.get("foo").?);
137 }115 }
138 try std.testing.expectEqualSlices(u8, "ABC=123", environ_map.get("EQUALS").?);116 try std.testing.expectEqualSlices(u8, "ABC=123", environ_map.get("EQUALS").?);
139 try std.testing.expectEqual(null, environ_map.get("EQUALS=ABC"));
140 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("КИРиллИЦА").?);117 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("КИРиллИЦА").?);
141 if (builtin.os.tag == .windows) {118 if (builtin.os.tag == .windows) {
142 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("кирИЛЛица").?);119 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("кирИЛЛица").?);
test/standalone/windows_argv/fuzz.zig+7-2
...@@ -125,7 +125,7 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO...@@ -125,7 +125,7 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO
125 .lpReserved2 = null,125 .lpReserved2 = null,
126 .hStdInput = null,126 .hStdInput = null,
127 .hStdOutput = null,127 .hStdOutput = null,
128 .hStdError = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null,128 .hStdError = windows.peb().ProcessParameters.hStdError,
129 };129 };
130 var proc_info: windows.PROCESS_INFORMATION = undefined;130 var proc_info: windows.PROCESS_INFORMATION = undefined;
131131
...@@ -149,7 +149,12 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO...@@ -149,7 +149,12 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO
149 break :spawn proc_info.hProcess;149 break :spawn proc_info.hProcess;
150 };150 };
151 defer windows.CloseHandle(child_proc);151 defer windows.CloseHandle(child_proc);
152 try windows.WaitForSingleObjectEx(child_proc, windows.INFINITE, false);152 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
153 switch (windows.ntdll.NtWaitForSingleObject(child_proc, windows.FALSE, &infinite_timeout)) {
154 windows.NTSTATUS.WAIT_0 => {},
155 .TIMEOUT => return error.WaitTimeOut,
156 else => |status| return windows.unexpectedStatus(status),
157 }
153158
154 var exit_code: windows.DWORD = undefined;159 var exit_code: windows.DWORD = undefined;
155 if (windows.kernel32.GetExitCodeProcess(child_proc, &exit_code) == 0) {160 if (windows.kernel32.GetExitCodeProcess(child_proc, &exit_code) == 0) {
test/standalone/windows_spawn/main.zig+4-3
...@@ -233,12 +233,13 @@ fn testExecWithCwdInner(gpa: Allocator, io: Io, command: []const u8, cwd: std.pr...@@ -233,12 +233,13 @@ fn testExecWithCwdInner(gpa: Allocator, io: Io, command: []const u8, cwd: std.pr
233}233}
234234
235fn renameExe(dir: Io.Dir, io: Io, old_sub_path: []const u8, new_sub_path: []const u8) !void {235fn renameExe(dir: Io.Dir, io: Io, old_sub_path: []const u8, new_sub_path: []const u8) !void {
236 var attempt: u5 = 0;236 var attempt: u5 = 10;
237 while (true) break dir.rename(old_sub_path, dir, new_sub_path, io) catch |err| switch (err) {237 while (true) break dir.rename(old_sub_path, dir, new_sub_path, io) catch |err| switch (err) {
238 error.AccessDenied => {238 error.AccessDenied => {
239 if (attempt == 13) return error.AccessDenied;239 if (attempt == 26) return error.AccessDenied;
240 // give the kernel a chance to finish closing the executable handle240 // give the kernel a chance to finish closing the executable handle
241 _ = std.os.windows.kernel32.SleepEx(@as(u32, 1) << attempt >> 1, std.os.windows.FALSE);241 const interval = @as(std.os.windows.LARGE_INTEGER, -1) << attempt;
242 _ = std.os.windows.ntdll.NtDelayExecution(std.os.windows.FALSE, &interval);
242 attempt += 1;243 attempt += 1;
243 continue;244 continue;
244 },245 },