authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-05 01:24:18+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-05 01:24:18+01:00
logfcef9905ae859601d085576012b81dc05f67c46f
tree1c45d66e0a8e011c178a7d5d6db87a31a6681713
parentc3edf0ba641fcaf9ccc93e27ca3bf140d4b8e84c
parent71156aff806856d5d48e72cd8aeb9315b9ae0b62

Merge pull request 'std.Progress: implement inter-process progress reporting for windows' (#31113) from threaded-win-cleanup into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31113 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

17 files changed, 1449 insertions(+), 1561 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/Step.zig+20-32
...@@ -386,10 +386,14 @@ pub const ZigProcess = struct {...@@ -386,10 +386,14 @@ pub const ZigProcess = struct {
386 child: std.process.Child,386 child: std.process.Child,
387 multi_reader_buffer: Io.File.MultiReader.Buffer(2),387 multi_reader_buffer: Io.File.MultiReader.Buffer(2),
388 multi_reader: Io.File.MultiReader,388 multi_reader: Io.File.MultiReader,
389 progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void,389 progress_ipc_index: ?if (std.Progress.have_ipc) std.Progress.Ipc.Index else noreturn,
390390
391 pub const StreamEnum = enum { stdout, stderr };391 pub const StreamEnum = enum { stdout, stderr };
392392
393 pub fn saveState(zp: *ZigProcess, prog_node: std.Progress.Node) void {
394 zp.progress_ipc_index = if (std.Progress.have_ipc) prog_node.takeIpcIndex() else null;
395 }
396
393 pub fn deinit(zp: *ZigProcess, io: Io) void {397 pub fn deinit(zp: *ZigProcess, io: Io) void {
394 zp.child.kill(io);398 zp.child.kill(io);
395 zp.multi_reader.deinit();399 zp.multi_reader.deinit();
...@@ -417,7 +421,14 @@ pub fn evalZigProcess(...@@ -417,7 +421,14 @@ pub fn evalZigProcess(
417421
418 if (s.getZigProcess()) |zp| update: {422 if (s.getZigProcess()) |zp| update: {
419 assert(watch);423 assert(watch);
420 if (std.Progress.have_ipc) if (zp.progress_ipc_fd) |fd| prog_node.setIpcFd(fd);424 if (zp.progress_ipc_index) |ipc_index| prog_node.setIpcIndex(ipc_index);
425 zp.progress_ipc_index = null;
426 var exited = false;
427 defer if (exited) {
428 s.cast(Compile).?.zig_process = null;
429 zp.deinit(io);
430 gpa.destroy(zp);
431 } else zp.saveState(prog_node);
421 const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) {432 const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) {
422 error.BrokenPipe, error.EndOfStream => |reason| {433 error.BrokenPipe, error.EndOfStream => |reason| {
423 std.log.info("{s} restart required: {t}", .{ argv[0], reason });434 std.log.info("{s} restart required: {t}", .{ argv[0], reason });
...@@ -426,7 +437,7 @@ pub fn evalZigProcess(...@@ -426,7 +437,7 @@ pub fn evalZigProcess(
426 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });437 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
427 };438 };
428 _ = term;439 _ = term;
429 s.clearZigProcess(gpa);440 exited = true;
430 break :update;441 break :update;
431 },442 },
432 else => |e| return e,443 else => |e| return e,
...@@ -442,7 +453,7 @@ pub fn evalZigProcess(...@@ -442,7 +453,7 @@ pub fn evalZigProcess(
442 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });453 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
443 };454 };
444 s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;455 s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
445 s.clearZigProcess(gpa);456 exited = true;
446 try handleChildProcessTerm(s, term);457 try handleChildProcessTerm(s, term);
447 return error.MakeFailed;458 return error.MakeFailed;
448 }459 }
...@@ -467,19 +478,16 @@ pub fn evalZigProcess(...@@ -467,19 +478,16 @@ pub fn evalZigProcess(
467 .progress_node = prog_node,478 .progress_node = prog_node,
468 }) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err });479 }) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err });
469480
470 zp.* = .{
471 .child = zp.child,
472 .multi_reader_buffer = undefined,
473 .multi_reader = undefined,
474 .progress_ipc_fd = if (std.Progress.have_ipc) prog_node.getIpcFd() else {},
475 };
476 zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{481 zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{
477 zp.child.stdout.?, zp.child.stderr.?,482 zp.child.stdout.?, zp.child.stderr.?,
478 });483 });
479 if (watch) s.setZigProcess(zp);484 if (watch) s.cast(Compile).?.zig_process = zp;
480 defer if (!watch) zp.deinit(io);485 defer if (!watch) zp.deinit(io);
481486
482 const result = try zigProcessUpdate(s, zp, watch, web_server, gpa);487 const result = result: {
488 defer if (watch) zp.saveState(prog_node);
489 break :result try zigProcessUpdate(s, zp, watch, web_server, gpa);
490 };
483491
484 if (!watch) {492 if (!watch) {
485 // Send EOF to stdin.493 // Send EOF to stdin.
...@@ -670,26 +678,6 @@ pub fn getZigProcess(s: *Step) ?*ZigProcess {...@@ -670,26 +678,6 @@ pub fn getZigProcess(s: *Step) ?*ZigProcess {
670 };678 };
671}679}
672680
673fn setZigProcess(s: *Step, zp: *ZigProcess) void {
674 switch (s.id) {
675 .compile => s.cast(Compile).?.zig_process = zp,
676 else => unreachable,
677 }
678}
679
680fn clearZigProcess(s: *Step, gpa: Allocator) void {
681 switch (s.id) {
682 .compile => {
683 const compile = s.cast(Compile).?;
684 if (compile.zig_process) |zp| {
685 gpa.destroy(zp);
686 compile.zig_process = null;
687 }
688 },
689 else => unreachable,
690 }
691}
692
693fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {681fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {
694 const header: std.zig.Client.Message.Header = .{682 const header: std.zig.Client.Message.Header = .{
695 .tag = tag,683 .tag = tag,
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+416-367
...@@ -19,7 +19,7 @@ const Alignment = std.mem.Alignment;...@@ -19,7 +19,7 @@ const Alignment = std.mem.Alignment;
19const assert = std.debug.assert;19const assert = std.debug.assert;
20const posix = std.posix;20const posix = std.posix;
21const windows = std.os.windows;21const windows = std.os.windows;
22const ws2_32 = std.os.windows.ws2_32;22const ws2_32 = windows.ws2_32;
2323
24/// Thread-safe.24/// Thread-safe.
25///25///
...@@ -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 _,
...@@ -1498,7 +1519,9 @@ pub const init_single_threaded: Threaded = .{...@@ -1498,7 +1519,9 @@ pub const init_single_threaded: Threaded = .{
1498 .old_sig_pipe = undefined,1519 .old_sig_pipe = undefined,
1499 .have_signal_handler = false,1520 .have_signal_handler = false,
1500 .argv0 = .empty,1521 .argv0 = .empty,
1501 .environ = .{},1522 .environ = .{ .process_environ = .{
1523 .block = if (process.Environ.Block == process.Environ.GlobalBlock) .global else .empty,
1524 } },
1502 .worker_threads = .init(null),1525 .worker_threads = .init(null),
1503 .disable_memory_mapping = false,1526 .disable_memory_mapping = false,
1504};1527};
...@@ -1533,6 +1556,7 @@ pub fn deinit(t: *Threaded) void {...@@ -1533,6 +1556,7 @@ pub fn deinit(t: *Threaded) void {
1533 }1556 }
1534 t.null_file.deinit();1557 t.null_file.deinit();
1535 t.random_file.deinit();1558 t.random_file.deinit();
1559 t.pipe_file.deinit();
1536 t.* = undefined;1560 t.* = undefined;
1537}1561}
15381562
...@@ -1576,14 +1600,7 @@ fn worker(t: *Threaded) void {...@@ -1576,14 +1600,7 @@ fn worker(t: *Threaded) void {
1576 },1600 },
1577 },1601 },
1578 },1602 },
1579 &.{1603 &.{ .ObjectName = null },
1580 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
1581 .RootDirectory = null,
1582 .ObjectName = null,
1583 .Attributes = .{},
1584 .SecurityDescriptor = null,
1585 .SecurityQualityOfService = null,
1586 },
1587 &windows.teb().ClientId,1604 &windows.teb().ClientId,
1588 ) == .SUCCESS);1605 ) == .SUCCESS);
1589 }1606 }
...@@ -2595,8 +2612,7 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void {...@@ -2595,8 +2612,7 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void {
2595 // opportunity to find additional ready operations.2612 // opportunity to find additional ready operations.
2596 break :t 0;2613 break :t 0;
2597 }2614 }
2598 const max_poll_ms = std.math.maxInt(i32);2615 break :t std.math.maxInt(i32);
2599 break :t max_poll_ms;
2600 };2616 };
2601 const syscall = try Syscall.start();2617 const syscall = try Syscall.start();
2602 const rc = posix.system.poll(&poll_buffer, poll_len, timeout_ms);2618 const rc = posix.system.poll(&poll_buffer, poll_len, timeout_ms);
...@@ -2716,6 +2732,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout...@@ -2716,6 +2732,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout
2716 break :allocation allocation;2732 break :allocation allocation;
2717 };2733 };
2718 @memcpy(slice[0..poll_buffer_len], storage.slice);2734 @memcpy(slice[0..poll_buffer_len], storage.slice);
2735 storage.slice = slice;
2719 }2736 }
2720 storage.slice[len] = .{2737 storage.slice[len] = .{
2721 .fd = file.handle,2738 .fd = file.handle,
...@@ -2769,9 +2786,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout...@@ -2769,9 +2786,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout
2769 }2786 }
2770 const d = deadline orelse break :t -1;2787 const d = deadline orelse break :t -1;
2771 const duration = d.durationFromNow(t_io);2788 const duration = d.durationFromNow(t_io);
2772 if (duration.raw.nanoseconds <= 0) return error.Timeout;2789 break :t @min(@max(0, duration.raw.toMilliseconds()), std.math.maxInt(i32));
2773 const max_poll_ms = std.math.maxInt(i32);
2774 break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds()));
2775 };2790 };
2776 const syscall = try Syscall.start();2791 const syscall = try Syscall.start();
2777 const rc = posix.system.poll(&poll_buffer, poll_storage.len, timeout_ms);2792 const rc = posix.system.poll(&poll_buffer, poll_storage.len, timeout_ms);
...@@ -3379,12 +3394,8 @@ fn dirCreateDirPathOpenWindows(...@@ -3379,12 +3394,8 @@ fn dirCreateDirPathOpenWindows(
3379 },3394 },
3380 },3395 },
3381 &.{3396 &.{
3382 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
3383 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,3397 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
3384 .Attributes = .{},
3385 .ObjectName = &nt_name,3398 .ObjectName = &nt_name,
3386 .SecurityDescriptor = null,
3387 .SecurityQualityOfService = null,
3388 },3399 },
3389 &io_status_block,3400 &io_status_block,
3390 null,3401 null,
...@@ -4066,13 +4077,9 @@ fn dirAccessWindows(...@@ -4066,13 +4077,9 @@ fn dirAccessWindows(
4066 .MaximumLength = path_len_bytes,4077 .MaximumLength = path_len_bytes,
4067 .Buffer = @constCast(sub_path_w.ptr),4078 .Buffer = @constCast(sub_path_w.ptr),
4068 };4079 };
4069 var attr: windows.OBJECT_ATTRIBUTES = .{4080 const attr: windows.OBJECT_ATTRIBUTES = .{
4070 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
4071 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,4081 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
4072 .Attributes = .{},
4073 .ObjectName = &nt_name,4082 .ObjectName = &nt_name,
4074 .SecurityDescriptor = null,
4075 .SecurityQualityOfService = null,
4076 };4083 };
4077 var basic_info: windows.FILE.BASIC_INFORMATION = undefined;4084 var basic_info: windows.FILE.BASIC_INFORMATION = undefined;
4078 const syscall: Syscall = try .start();4085 const syscall: Syscall = try .start();
...@@ -4288,14 +4295,8 @@ fn dirCreateFileWindows(...@@ -4288,14 +4295,8 @@ fn dirCreateFileWindows(
4288 .Buffer = @constCast(sub_path_w.ptr),4295 .Buffer = @constCast(sub_path_w.ptr),
4289 };4296 };
4290 const attr: windows.OBJECT_ATTRIBUTES = .{4297 const attr: windows.OBJECT_ATTRIBUTES = .{
4291 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
4292 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,4298 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
4293 .Attributes = .{
4294 .INHERIT = false,
4295 },
4296 .ObjectName = &nt_name,4299 .ObjectName = &nt_name,
4297 .SecurityDescriptor = null,
4298 .SecurityQualityOfService = null,
4299 };4300 };
4300 const create_disposition: windows.FILE.CREATE_DISPOSITION = if (flags.exclusive)4301 const create_disposition: windows.FILE.CREATE_DISPOSITION = if (flags.exclusive)
4301 .CREATE4302 .CREATE
...@@ -4908,17 +4909,6 @@ pub fn dirOpenFileWtf16(...@@ -4908,17 +4909,6 @@ pub fn dirOpenFileWtf16(
4908 .MaximumLength = path_len_bytes,4909 .MaximumLength = path_len_bytes,
4909 .Buffer = @constCast(sub_path_w.ptr),4910 .Buffer = @constCast(sub_path_w.ptr),
4910 };4911 };
4911 var attr: w.OBJECT_ATTRIBUTES = .{
4912 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
4913 .RootDirectory = dir_handle,
4914 .Attributes = .{
4915 // TODO should we set INHERIT=false?
4916 //.INHERIT = false,
4917 },
4918 .ObjectName = &nt_name,
4919 .SecurityDescriptor = null,
4920 .SecurityQualityOfService = null,
4921 };
4922 var io_status_block: w.IO_STATUS_BLOCK = undefined;4912 var io_status_block: w.IO_STATUS_BLOCK = undefined;
49234913
4924 // There are multiple kernel bugs being worked around with retries.4914 // There are multiple kernel bugs being worked around with retries.
...@@ -4937,7 +4927,10 @@ pub fn dirOpenFileWtf16(...@@ -4937,7 +4927,10 @@ pub fn dirOpenFileWtf16(
4937 .WRITE = flags.isWrite(),4927 .WRITE = flags.isWrite(),
4938 },4928 },
4939 },4929 },
4940 &attr,4930 &.{
4931 .RootDirectory = dir_handle,
4932 .ObjectName = &nt_name,
4933 },
4941 &io_status_block,4934 &io_status_block,
4942 null,4935 null,
4943 .{ .NORMAL = true },4936 .{ .NORMAL = true },
...@@ -5305,12 +5298,8 @@ pub fn dirOpenDirWindows(...@@ -5305,12 +5298,8 @@ pub fn dirOpenDirWindows(
5305 },5298 },
5306 },5299 },
5307 &.{5300 &.{
5308 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
5309 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,5301 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
5310 .Attributes = .{},
5311 .ObjectName = &nt_name,5302 .ObjectName = &nt_name,
5312 .SecurityDescriptor = null,
5313 .SecurityQualityOfService = null,
5314 },5303 },
5315 &io_status_block,5304 &io_status_block,
5316 null,5305 null,
...@@ -6520,12 +6509,8 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov...@@ -6520,12 +6509,8 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
6520 .SYNCHRONIZE = true,6509 .SYNCHRONIZE = true,
6521 } },6510 } },
6522 &.{6511 &.{
6523 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
6524 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,6512 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
6525 .Attributes = .{},
6526 .ObjectName = &nt_name,6513 .ObjectName = &nt_name,
6527 .SecurityDescriptor = null,
6528 .SecurityQualityOfService = null,
6529 },6514 },
6530 &io_status_block,6515 &io_status_block,
6531 null,6516 null,
...@@ -6534,6 +6519,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov...@@ -6534,6 +6519,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
6534 .OPEN,6519 .OPEN,
6535 .{6520 .{
6536 .DIRECTORY_FILE = remove_dir,6521 .DIRECTORY_FILE = remove_dir,
6522 .IO = .SYNCHRONOUS_NONALERT,
6537 .NON_DIRECTORY_FILE = !remove_dir,6523 .NON_DIRECTORY_FILE = !remove_dir,
6538 .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead?6524 .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead?
6539 },6525 },
...@@ -7345,14 +7331,8 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink...@@ -7345,14 +7331,8 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink
7345 .Buffer = @constCast(sub_path_w.ptr),7331 .Buffer = @constCast(sub_path_w.ptr),
7346 };7332 };
7347 const attr: windows.OBJECT_ATTRIBUTES = .{7333 const attr: windows.OBJECT_ATTRIBUTES = .{
7348 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
7349 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,7334 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
7350 .Attributes = .{
7351 .INHERIT = false,
7352 },
7353 .ObjectName = &nt_name,7335 .ObjectName = &nt_name,
7354 .SecurityDescriptor = null,
7355 .SecurityQualityOfService = null,
7356 };7336 };
7357 var io_status_block: windows.IO_STATUS_BLOCK = undefined;7337 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
7358 var result_handle: windows.HANDLE = undefined;7338 var result_handle: windows.HANDLE = undefined;
...@@ -7909,24 +7889,19 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void {...@@ -7909,24 +7889,19 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void {
7909 const t: *Threaded = @ptrCast(@alignCast(userdata));7889 const t: *Threaded = @ptrCast(@alignCast(userdata));
7910 _ = t;7890 _ = t;
79117891
7892 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
7912 const syscall: Syscall = try .start();7893 const syscall: Syscall = try .start();
7913 while (true) {7894 while (true) {
7914 if (windows.kernel32.FlushFileBuffers(file.handle) != 0) {7895 switch (windows.ntdll.NtFlushBuffersFile(file.handle, &io_status_block)) {
7915 return syscall.finish();7896 .SUCCESS => break syscall.finish(),
7916 }7897 .CANCELLED => {
7917 switch (windows.GetLastError()) {
7918 .SUCCESS => unreachable, // `FlushFileBuffers` returned nonzero
7919 .INVALID_HANDLE => unreachable,
7920 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time
7921 .UNEXP_NET_ERR => return syscall.fail(error.InputOutput),
7922 .OPERATION_ABORTED => {
7923 try syscall.checkCancel();7898 try syscall.checkCancel();
7924 continue;7899 continue;
7925 },7900 },
7926 else => |err| {7901 .INVALID_HANDLE => unreachable,
7927 syscall.finish();7902 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time
7928 return windows.unexpectedError(err);7903 .UNEXPECTED_NETWORK_ERROR => return syscall.fail(error.InputOutput),
7929 },7904 else => |status| return syscall.unexpectedNtstatus(status),
7930 }7905 }
7931 }7906 }
7932}7907}
...@@ -14446,7 +14421,10 @@ const WindowsEnvironStrings = struct {...@@ -14446,7 +14421,10 @@ const WindowsEnvironStrings = struct {
14446 PATHEXT: ?[:0]const u16 = null,14421 PATHEXT: ?[:0]const u16 = null,
1444714422
14448 fn scan() WindowsEnvironStrings {14423 fn scan() WindowsEnvironStrings {
14449 const ptr = windows.peb().ProcessParameters.Environment;14424 const peb = windows.peb();
14425 assert(windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
14426 defer assert(windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
14427 const ptr = peb.ProcessParameters.Environment;
1445014428
14451 var result: WindowsEnvironStrings = .{};14429 var result: WindowsEnvironStrings = .{};
14452 var i: usize = 0;14430 var i: usize = 0;
...@@ -14472,7 +14450,7 @@ const WindowsEnvironStrings = struct {...@@ -14472,7 +14450,7 @@ const WindowsEnvironStrings = struct {
1447214450
14473 inline for (@typeInfo(WindowsEnvironStrings).@"struct".fields) |field| {14451 inline for (@typeInfo(WindowsEnvironStrings).@"struct".fields) |field| {
14474 const field_name_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(field.name);14452 const field_name_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(field.name);
14475 if (std.os.windows.eqlIgnoreCaseWtf16(key_w, field_name_w)) @field(result, field.name) = value_w;14453 if (windows.eqlIgnoreCaseWtf16(key_w, field_name_w)) @field(result, field.name) = value_w;
14476 }14454 }
14477 }14455 }
1447814456
...@@ -14491,29 +14469,46 @@ fn scanEnviron(t: *Threaded) void {...@@ -14491,29 +14469,46 @@ fn scanEnviron(t: *Threaded) void {
14491 // This value expires with any call that modifies the environment,14469 // This value expires with any call that modifies the environment,
14492 // which is outside of this Io implementation's control, so references14470 // which is outside of this Io implementation's control, so references
14493 // must be short-lived.14471 // must be short-lived.
14494 const ptr = windows.peb().ProcessParameters.Environment;14472 const peb = windows.peb();
14473 assert(windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
14474 defer assert(windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
14475 const ptr = peb.ProcessParameters.Environment;
1449514476
14496 var i: usize = 0;14477 var i: usize = 0;
14497 while (ptr[i] != 0) {14478 while (ptr[i] != 0) {
14498 const key_start = i;
1449914479
14500 // There are some special environment variables that start with =,14480 // There are some special environment variables that start with =,
14501 // so we need a special case to not treat = as a key/value separator14481 // so we need a special case to not treat = as a key/value separator
14502 // if it's the first character.14482 // if it's the first character.
14503 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=1413314483 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
14504 if (ptr[key_start] == '=') i += 1;14484 const key_start = i;
1450514485 if (ptr[i] == '=') i += 1;
14506 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}14486 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
14507 const key_w = ptr[key_start..i];14487 const key_w = ptr[key_start..i];
14508 if (std.mem.eql(u16, key_w, &.{ 'N', 'O', '_', 'C', 'O', 'L', 'O', 'R' })) {14488
14489 const value_start = i + 1;
14490 while (ptr[i] != 0) : (i += 1) {} // skip over '=' and value
14491 const value_w = ptr[value_start..i];
14492 i += 1; // skip over null byte
14493
14494 if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'N', 'O', '_', 'C', 'O', 'L', 'O', 'R' })) {
14509 t.environ.exist.NO_COLOR = true;14495 t.environ.exist.NO_COLOR = true;
14510 } else if (std.mem.eql(u16, key_w, &.{ 'C', 'L', 'I', 'C', 'O', 'L', 'O', 'R', '_', 'F', 'O', 'R', 'C', 'E' })) {14496 } else if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'C', 'L', 'I', 'C', 'O', 'L', 'O', 'R', '_', 'F', 'O', 'R', 'C', 'E' })) {
14511 t.environ.exist.CLICOLOR_FORCE = true;14497 t.environ.exist.CLICOLOR_FORCE = true;
14498 } else if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S' })) {
14499 t.environ.zig_progress_file = file: {
14500 var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined;
14501 const len = std.unicode.calcWtf8Len(value_w);
14502 if (len > value_buf.len) break :file error.UnrecognizedFormat;
14503 assert(std.unicode.wtf16LeToWtf8(&value_buf, value_w) == len);
14504 break :file .{
14505 .handle = @ptrFromInt(std.fmt.parseInt(usize, value_buf[0..len], 10) catch
14506 break :file error.UnrecognizedFormat),
14507 .flags = .{ .nonblocking = true },
14508 };
14509 };
14512 }14510 }
14513 comptime assert(@sizeOf(Environ.String) == 0);14511 comptime assert(@sizeOf(Environ.String) == 0);
14514
14515 while (ptr[i] != 0) : (i += 1) {} // skip over '=' and value
14516 i += 1; // skip over null byte
14517 }14512 }
14518 } else if (native_os == .wasi and !builtin.link_libc) {14513 } else if (native_os == .wasi and !builtin.link_libc) {
14519 var environ_count: usize = undefined;14514 var environ_count: usize = undefined;
...@@ -14559,22 +14554,28 @@ fn scanEnviron(t: *Threaded) void {...@@ -14559,22 +14554,28 @@ fn scanEnviron(t: *Threaded) void {
14559 comptime assert(@sizeOf(Environ.String) == 0);14554 comptime assert(@sizeOf(Environ.String) == 0);
14560 }14555 }
14561 } else {14556 } else {
14562 for (t.environ.process_environ.block) |opt_line| {14557 for (t.environ.process_environ.block.slice) |opt_entry| {
14563 const line = opt_line.?;14558 const entry = opt_entry.?;
14564 var line_i: usize = 0;14559 var entry_i: usize = 0;
14565 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}14560 while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {}
14566 const key = line[0..line_i];14561 const key = entry[0..entry_i];
1456714562
14568 var end_i: usize = line_i;14563 var end_i: usize = entry_i;
14569 while (line[end_i] != 0) : (end_i += 1) {}14564 while (entry[end_i] != 0) : (end_i += 1) {}
14570 const value = line[line_i + 1 .. end_i :0];14565 const value = entry[entry_i + 1 .. end_i :0];
1457114566
14572 if (std.mem.eql(u8, key, "NO_COLOR")) {14567 if (std.mem.eql(u8, key, "NO_COLOR")) {
14573 t.environ.exist.NO_COLOR = true;14568 t.environ.exist.NO_COLOR = true;
14574 } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) {14569 } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) {
14575 t.environ.exist.CLICOLOR_FORCE = true;14570 t.environ.exist.CLICOLOR_FORCE = true;
14576 } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) {14571 } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) {
14577 t.environ.zig_progress_handle = std.fmt.parseInt(u31, value, 10) catch error.UnrecognizedFormat;14572 t.environ.zig_progress_file = file: {
14573 break :file .{
14574 .handle = std.fmt.parseInt(u31, value, 10) catch
14575 break :file error.UnrecognizedFormat,
14576 .flags = .{ .nonblocking = true },
14577 };
14578 };
14578 } else inline for (@typeInfo(Environ.String).@"struct".fields) |field| {14579 } else inline for (@typeInfo(Environ.String).@"struct".fields) |field| {
14579 if (std.mem.eql(u8, key, field.name)) @field(t.environ.string, field.name) = value;14580 if (std.mem.eql(u8, key, field.name)) @field(t.environ.string, field.name) = value;
14580 }14581 }
...@@ -14597,19 +14598,17 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces...@@ -14597,19 +14598,17 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
14597 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);14598 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);
14598 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;14599 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
1459914600
14600 const envp: [*:null]const ?[*:0]const u8 = m: {14601 const env_block = env_block: {
14601 const prog_fd: i32 = -1;14602 const prog_fd: i32 = -1;
14602 if (options.environ_map) |environ_map| {14603 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
14603 break :m (try environ_map.createBlockPosix(arena, .{
14604 .zig_progress_fd = prog_fd,
14605 })).ptr;
14606 }
14607 break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{
14608 .zig_progress_fd = prog_fd,14604 .zig_progress_fd = prog_fd,
14609 })).ptr;14605 });
14606 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{
14607 .zig_progress_fd = prog_fd,
14608 });
14610 };14609 };
1461114610
14612 return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH);14611 return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
14613}14612}
1461414613
14615fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError {14614fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError {
...@@ -14679,16 +14678,17 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp...@@ -14679,16 +14678,17 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
14679 const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore);14678 const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore);
14680 const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined;14679 const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined;
1468114680
14682 const prog_pipe: [2]posix.fd_t = p: {14681 const prog_pipe: [2]posix.fd_t = if (options.progress_node.index != .none)
14683 if (options.progress_node.index == .none) {14682 // We use CLOEXEC for the same reason as in `pipe_flags`.
14684 break :p .{ -1, -1 };14683 try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true })
14685 } else {14684 else
14686 // We use CLOEXEC for the same reason as in `pipe_flags`.14685 .{ -1, -1 };
14687 break :p try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true });
14688 }
14689 };
14690 errdefer destroyPipe(prog_pipe);14686 errdefer destroyPipe(prog_pipe);
1469114687
14688 if (native_os == .linux and prog_pipe[0] != -1) {
14689 _ = posix.system.fcntl(prog_pipe[0], posix.F.SETPIPE_SZ, @as(u32, std.Progress.max_packet_len * 2));
14690 }
14691
14692 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);14692 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
14693 defer arena_allocator.deinit();14693 defer arena_allocator.deinit();
14694 const arena = arena_allocator.allocator();14694 const arena = arena_allocator.allocator();
...@@ -14708,16 +14708,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp...@@ -14708,16 +14708,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
14708 const prog_fileno = 3;14708 const prog_fileno = 3;
14709 comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno);14709 comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno);
1471014710
14711 const envp: [*:null]const ?[*:0]const u8 = m: {14711 const env_block = env_block: {
14712 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;14712 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
14713 if (options.environ_map) |environ_map| {14713 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
14714 break :m (try environ_map.createBlockPosix(arena, .{
14715 .zig_progress_fd = prog_fd,
14716 })).ptr;
14717 }
14718 break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{
14719 .zig_progress_fd = prog_fd,14714 .zig_progress_fd = prog_fd,
14720 })).ptr;14715 });
14716 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{
14717 .zig_progress_fd = prog_fd,
14718 });
14721 };14719 };
1472214720
14723 // This pipe communicates to the parent errors in the child between `fork` and `execvpe`.14721 // This pipe communicates to the parent errors in the child between `fork` and `execvpe`.
...@@ -14800,7 +14798,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp...@@ -14800,7 +14798,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
14800 }14798 }
14801 }14799 }
1480214800
14803 const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH);14801 const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
14804 forkBail(ep1, err);14802 forkBail(ep1, err);
14805 }14803 }
1480614804
...@@ -14814,8 +14812,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp...@@ -14814,8 +14812,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
14814 if (options.stderr == .pipe) posix.close(stderr_pipe[1]);14812 if (options.stderr == .pipe) posix.close(stderr_pipe[1]);
1481514813
14816 if (prog_pipe[1] != -1) posix.close(prog_pipe[1]);14814 if (prog_pipe[1] != -1) posix.close(prog_pipe[1]);
1481714815 options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });
14818 options.progress_node.setIpcFd(prog_pipe[0]);
1481914816
14820 return .{14817 return .{
14821 .pid = pid,14818 .pid = pid,
...@@ -14938,42 +14935,44 @@ fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT...@@ -14938,42 +14935,44 @@ fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT
14938 // some rare edge cases where our process handle no longer has the14935 // some rare edge cases where our process handle no longer has the
14939 // PROCESS_TERMINATE access right, so let's do another check to make14936 // PROCESS_TERMINATE access right, so let's do another check to make
14940 // sure the process is really no longer running:14937 // sure the process is really no longer running:
14941 windows.WaitForSingleObjectEx(handle, 0, false) catch return error.AccessDenied;14938 const minimal_timeout: windows.LARGE_INTEGER = -1;
14942 return error.AlreadyTerminated;14939 switch (windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &minimal_timeout)) {
14940 .SUCCESS => return error.AlreadyTerminated,
14941 else => return error.AccessDenied,
14942 }
14943 },14943 },
14944 else => |err| return windows.unexpectedError(err),14944 else => |err| return windows.unexpectedError(err),
14945 }14945 }
14946 }14946 }
14947 _ = windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE);14947 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
14948 _ = windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &infinite_timeout);
14948 childCleanupWindows(child);14949 childCleanupWindows(child);
14949}14950}
1495014951
14951fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term {14952fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term {
14952 const handle = child.id.?;14953 const handle = child.id.?;
1495314954
14954 const syscall: Syscall = try .start();14955 const alertable_syscall: AlertableSyscall = try .start();
14955 while (true) switch (windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE)) {14956 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
14956 windows.WAIT_OBJECT_0 => break syscall.finish(),14957 while (true) switch (windows.ntdll.NtWaitForSingleObject(handle, windows.TRUE, &infinite_timeout)) {
14957 windows.WAIT_ABANDONED, windows.WAIT_TIMEOUT => {14958 windows.NTSTATUS.WAIT_0 => break alertable_syscall.finish(),
14958 try syscall.checkCancel();14959 .USER_APC, .ALERTED, .TIMEOUT => {
14960 try alertable_syscall.checkCancel();
14959 continue;14961 continue;
14960 },14962 },
14961 windows.WAIT_FAILED => {14963 else => |status| return alertable_syscall.unexpectedNtstatus(status),
14962 syscall.finish();
14963 switch (windows.GetLastError()) {
14964 else => |err| return windows.unexpectedError(err),
14965 }
14966 },
14967 else => return syscall.fail(error.Unexpected),
14968 };14964 };
1496914965
14970 const term: process.Child.Term = x: {14966 var info: windows.PROCESS_BASIC_INFORMATION = undefined;
14971 var exit_code: windows.DWORD = undefined;14967 const term: process.Child.Term = switch (windows.ntdll.NtQueryInformationProcess(
14972 if (windows.kernel32.GetExitCodeProcess(handle, &exit_code) == 0) {14968 handle,
14973 break :x .{ .unknown = 0 };14969 .BasicInformation,
14974 } else {14970 &info,
14975 break :x .{ .exited = @as(u8, @truncate(exit_code)) };14971 @sizeOf(windows.PROCESS_BASIC_INFORMATION),
14976 }14972 null,
14973 )) {
14974 .SUCCESS => .{ .exited = @as(u8, @truncate(@intFromEnum(info.ExitStatus))) },
14975 else => .{ .unknown = 0 },
14977 };14976 };
1497814977
14979 childCleanupWindows(child);14978 childCleanupWindows(child);
...@@ -15236,88 +15235,71 @@ fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32...@@ -15236,88 +15235,71 @@ fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32
15236fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {15235fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
15237 const t: *Threaded = @ptrCast(@alignCast(userdata));15236 const t: *Threaded = @ptrCast(@alignCast(userdata));
1523815237
15239 var saAttr: windows.SECURITY_ATTRIBUTES = .{
15240 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
15241 .bInheritHandle = windows.TRUE,
15242 .lpSecurityDescriptor = null,
15243 };
15244
15245 const any_ignore =15238 const any_ignore =
15246 options.stdin == .ignore or15239 options.stdin == .ignore or
15247 options.stdout == .ignore or15240 options.stdout == .ignore or
15248 options.stderr == .ignore;15241 options.stderr == .ignore;
1524915242 const nul_handle = if (any_ignore) try getNulDevice(t) else undefined;
15250 const nul_handle = if (any_ignore) try getNulHandle(t) else undefined;15243
1525115244 const any_inherit =
15252 var g_hChildStd_IN_Rd: ?windows.HANDLE = null;15245 options.stdin == .inherit or
15253 var g_hChildStd_IN_Wr: ?windows.HANDLE = null;15246 options.stdout == .inherit or
15254 switch (options.stdin) {15247 options.stderr == .inherit;
15255 .pipe => {15248 const peb = if (any_inherit) windows.peb() else undefined;
15256 try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr);15249
15257 },15250 const stdin_pipe = if (options.stdin == .pipe) try t.windowsCreatePipe(.{
15258 .ignore => {15251 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15259 g_hChildStd_IN_Rd = nul_handle;15252 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15260 },15253 .outbound = true,
15261 .inherit => {15254 }) else undefined;
15262 g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE) catch null;15255 errdefer if (options.stdin == .pipe) for (stdin_pipe) |handle| windows.CloseHandle(handle);
15263 },15256
15264 .close => {15257 const stdout_pipe = if (options.stdout == .pipe) try t.windowsCreatePipe(.{
15265 g_hChildStd_IN_Rd = null;15258 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15266 },15259 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15267 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),15260 .inbound = true,
15268 }15261 }) else undefined;
15269 errdefer if (options.stdin == .pipe) {15262 errdefer if (options.stdout == .pipe) for (stdout_pipe) |handle| windows.CloseHandle(handle);
15270 windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr);15263
15271 };15264 const stderr_pipe = if (options.stderr == .pipe) try t.windowsCreatePipe(.{
1527215265 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15273 var g_hChildStd_OUT_Rd: ?windows.HANDLE = null;15266 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15274 var g_hChildStd_OUT_Wr: ?windows.HANDLE = null;15267 .inbound = true,
15275 switch (options.stdout) {15268 }) else undefined;
15276 .pipe => {15269 errdefer if (options.stderr == .pipe) for (stderr_pipe) |handle| windows.CloseHandle(handle);
15277 try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr);15270
15278 },15271 const prog_pipe = if (options.progress_node.index != .none) try t.windowsCreatePipe(.{
15279 .ignore => {15272 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15280 g_hChildStd_OUT_Wr = nul_handle;15273 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .ASYNCHRONOUS } },
15281 },15274 .inbound = true,
15282 .inherit => {15275 .quota = std.Progress.max_packet_len * 2,
15283 g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) catch null;15276 }) else undefined;
15284 },15277 errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle);
15285 .close => {
15286 g_hChildStd_OUT_Wr = null;
15287 },
15288 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
15289 }
15290 errdefer if (options.stdout == .pipe) {
15291 windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr);
15292 };
15293
15294 var g_hChildStd_ERR_Rd: ?windows.HANDLE = null;
15295 var g_hChildStd_ERR_Wr: ?windows.HANDLE = null;
15296 switch (options.stderr) {
15297 .pipe => {
15298 try windowsMakeAsyncPipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr);
15299 },
15300 .ignore => {
15301 g_hChildStd_ERR_Wr = nul_handle;
15302 },
15303 .inherit => {
15304 g_hChildStd_ERR_Wr = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null;
15305 },
15306 .close => {
15307 g_hChildStd_ERR_Wr = null;
15308 },
15309 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
15310 }
15311 errdefer if (options.stderr == .pipe) {
15312 windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr);
15313 };
1531415278
15315 var siStartInfo: windows.STARTUPINFOW = .{15279 var siStartInfo: windows.STARTUPINFOW = .{
15316 .cb = @sizeOf(windows.STARTUPINFOW),15280 .cb = @sizeOf(windows.STARTUPINFOW),
15317 .hStdError = g_hChildStd_ERR_Wr,
15318 .hStdOutput = g_hChildStd_OUT_Wr,
15319 .hStdInput = g_hChildStd_IN_Rd,
15320 .dwFlags = windows.STARTF_USESTDHANDLES,15281 .dwFlags = windows.STARTF_USESTDHANDLES,
15282 .hStdInput = switch (options.stdin) {
15283 .inherit => peb.ProcessParameters.hStdInput,
15284 .file => |file| file.handle,
15285 .ignore => nul_handle,
15286 .pipe => stdin_pipe[1],
15287 .close => null,
15288 },
15289 .hStdOutput = switch (options.stdout) {
15290 .inherit => peb.ProcessParameters.hStdOutput,
15291 .file => |file| file.handle,
15292 .ignore => nul_handle,
15293 .pipe => stdout_pipe[1],
15294 .close => null,
15295 },
15296 .hStdError = switch (options.stderr) {
15297 .inherit => peb.ProcessParameters.hStdError,
15298 .file => |file| file.handle,
15299 .ignore => nul_handle,
15300 .pipe => stderr_pipe[1],
15301 .close => null,
15302 },
1532115303
15322 .lpReserved = null,15304 .lpReserved = null,
15323 .lpDesktop = null,15305 .lpDesktop = null,
...@@ -15363,8 +15345,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -15363,8 +15345,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
15363 };15345 };
15364 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;15346 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
1536515347
15366 const maybe_envp_buf = if (options.environ_map) |environ_map| try environ_map.createBlockWindows(arena) else null;15348 const env_block = env_block: {
15367 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;15349 const prog_handle = if (options.progress_node.index != .none)
15350 prog_pipe[1]
15351 else
15352 windows.INVALID_HANDLE_VALUE;
15353 if (options.environ_map) |environ_map| break :env_block try environ_map.createWindowsBlock(arena, .{
15354 .zig_progress_handle = prog_handle,
15355 });
15356 break :env_block try t.environ.process_environ.createWindowsBlock(arena, .{
15357 .zig_progress_handle = if (options.progress_node.index != .none) prog_pipe[1] else windows.INVALID_HANDLE_VALUE,
15358 });
15359 };
1536815360
15369 const app_name_wtf8 = options.argv[0];15361 const app_name_wtf8 = options.argv[0];
15370 const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8);15362 const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8);
...@@ -15439,7 +15431,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -15439,7 +15431,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
15439 &app_buf,15431 &app_buf,
15440 PATHEXT,15432 PATHEXT,
15441 &cmd_line_cache,15433 &cmd_line_cache,
15442 envp_ptr,15434 env_block,
15443 cwd_w_ptr,15435 cwd_w_ptr,
15444 flags,15436 flags,
15445 &siStartInfo,15437 &siStartInfo,
...@@ -15474,7 +15466,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -15474,7 +15466,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
15474 &app_buf,15466 &app_buf,
15475 PATHEXT,15467 PATHEXT,
15476 &cmd_line_cache,15468 &cmd_line_cache,
15477 envp_ptr,15469 env_block,
15478 cwd_w_ptr,15470 cwd_w_ptr,
15479 flags,15471 flags,
15480 &siStartInfo,15472 &siStartInfo,
...@@ -15494,21 +15486,40 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -15494,21 +15486,40 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
15494 };15486 };
15495 }15487 }
1549615488
15497 if (options.stdin == .pipe) windows.CloseHandle(g_hChildStd_IN_Rd.?);15489 if (options.progress_node.index != .none) {
15498 if (options.stderr == .pipe) windows.CloseHandle(g_hChildStd_ERR_Wr.?);15490 windows.CloseHandle(prog_pipe[1]);
15499 if (options.stdout == .pipe) windows.CloseHandle(g_hChildStd_OUT_Wr.?);15491 options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });
15492 }
1550015493
15501 return .{15494 return .{
15502 .id = piProcInfo.hProcess,15495 .id = piProcInfo.hProcess,
15503 .thread_handle = piProcInfo.hThread,15496 .thread_handle = piProcInfo.hThread,
15504 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = false } } else null,15497 .stdin = stdin: switch (options.stdin) {
15505 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,15498 .pipe => {
15506 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,15499 windows.CloseHandle(stdin_pipe[1]);
15500 break :stdin .{ .handle = stdin_pipe[0], .flags = .{ .nonblocking = false } };
15501 },
15502 else => null,
15503 },
15504 .stdout = stdout: switch (options.stdout) {
15505 .pipe => {
15506 windows.CloseHandle(stdout_pipe[1]);
15507 break :stdout .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = true } };
15508 },
15509 else => null,
15510 },
15511 .stderr = stderr: switch (options.stderr) {
15512 .pipe => {
15513 windows.CloseHandle(stderr_pipe[1]);
15514 break :stderr .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = true } };
15515 },
15516 else => null,
15517 },
15507 .request_resource_usage_statistics = options.request_resource_usage_statistics,15518 .request_resource_usage_statistics = options.request_resource_usage_statistics,
15508 };15519 };
15509}15520}
1551015521
15511fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {15522fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
15512 {15523 {
15513 mutexLock(&t.mutex);15524 mutexLock(&t.mutex);
15514 defer mutexUnlock(&t.mutex);15525 defer mutexUnlock(&t.mutex);
...@@ -15516,12 +15527,6 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {...@@ -15516,12 +15527,6 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
15516 }15527 }
1551715528
15518 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' };15529 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' };
15519
15520 var nt_name: windows.UNICODE_STRING = .{
15521 .Length = device_path.len * 2,
15522 .MaximumLength = 0,
15523 .Buffer = @constCast(&device_path),
15524 };
15525 var fresh_handle: windows.HANDLE = undefined;15530 var fresh_handle: windows.HANDLE = undefined;
15526 var io_status_block: windows.IO_STATUS_BLOCK = undefined;15531 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
15527 var syscall: Syscall = try .start();15532 var syscall: Syscall = try .start();
...@@ -15532,12 +15537,11 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {...@@ -15532,12 +15537,11 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
15532 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } },15537 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } },
15533 },15538 },
15534 &.{15539 &.{
15535 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),15540 .ObjectName = @constCast(&windows.UNICODE_STRING{
15536 .RootDirectory = null,15541 .Length = @sizeOf(@TypeOf(device_path)),
15537 .ObjectName = &nt_name,15542 .MaximumLength = 0,
15538 .Attributes = .{},15543 .Buffer = @constCast(&device_path),
15539 .SecurityDescriptor = null,15544 }),
15540 .SecurityQualityOfService = null,
15541 },15545 },
15542 &io_status_block,15546 &io_status_block,
15543 .VALID_FLAGS,15547 .VALID_FLAGS,
...@@ -15564,7 +15568,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {...@@ -15564,7 +15568,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
15564 };15568 };
15565}15569}
1556615570
15567fn getNulHandle(t: *Threaded) !windows.HANDLE {15571fn getNulDevice(t: *Threaded) !windows.HANDLE {
15568 {15572 {
15569 mutexLock(&t.mutex);15573 mutexLock(&t.mutex);
15570 defer mutexUnlock(&t.mutex);15574 defer mutexUnlock(&t.mutex);
...@@ -15572,44 +15576,26 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE {...@@ -15572,44 +15576,26 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE {
15572 }15576 }
1557315577
15574 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' };15578 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' };
15575 var nt_name: windows.UNICODE_STRING = .{
15576 .Length = device_path.len * 2,
15577 .MaximumLength = 0,
15578 .Buffer = @constCast(&device_path),
15579 };
15580 const attr: windows.OBJECT_ATTRIBUTES = .{
15581 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
15582 .RootDirectory = null,
15583 .Attributes = .{
15584 .INHERIT = true,
15585 },
15586 .ObjectName = &nt_name,
15587 .SecurityDescriptor = null,
15588 .SecurityQualityOfService = null,
15589 };
15590 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
15591 var fresh_handle: windows.HANDLE = undefined;15579 var fresh_handle: windows.HANDLE = undefined;
15580 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
15592 var syscall: Syscall = try .start();15581 var syscall: Syscall = try .start();
15593 while (true) switch (windows.ntdll.NtCreateFile(15582 while (true) switch (windows.ntdll.NtOpenFile(
15594 &fresh_handle,15583 &fresh_handle,
15595 .{15584 .{
15596 .STANDARD = .{ .SYNCHRONIZE = true },15585 .STANDARD = .{ .SYNCHRONIZE = true },
15597 .GENERIC = .{ .WRITE = true, .READ = true },15586 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true, .WRITE_DATA = true } },
15587 },
15588 &.{
15589 .Attributes = .{ .INHERIT = true },
15590 .ObjectName = @constCast(&windows.UNICODE_STRING{
15591 .Length = @sizeOf(@TypeOf(device_path)),
15592 .MaximumLength = 0,
15593 .Buffer = @constCast(&device_path),
15594 }),
15598 },15595 },
15599 &attr,
15600 &io_status_block,15596 &io_status_block,
15601 null,
15602 .{ .NORMAL = true },
15603 .VALID_FLAGS,15597 .VALID_FLAGS,
15604 .OPEN,15598 .{ .IO = .SYNCHRONOUS_NONALERT },
15605 .{
15606 .DIRECTORY_FILE = false,
15607 .NON_DIRECTORY_FILE = true,
15608 .IO = .SYNCHRONOUS_NONALERT,
15609 .OPEN_REPARSE_POINT = false,
15610 },
15611 null,
15612 0,
15613 )) {15599 )) {
15614 .SUCCESS => {15600 .SUCCESS => {
15615 syscall.finish();15601 syscall.finish();
...@@ -15623,6 +15609,64 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE {...@@ -15623,6 +15609,64 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE {
15623 return fresh_handle;15609 return fresh_handle;
15624 }15610 }
15625 },15611 },
15612 .CANCELLED => {
15613 try syscall.checkCancel();
15614 continue;
15615 },
15616 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
15617 .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status),
15618 .INVALID_HANDLE => |status| return syscall.ntstatusBug(status),
15619 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
15620 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
15621 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
15622 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
15623 .SHARING_VIOLATION => return syscall.fail(error.AccessDenied),
15624 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
15625 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice),
15626 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
15627 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
15628 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
15629 else => |status| return syscall.unexpectedNtstatus(status),
15630 };
15631}
15632
15633fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE {
15634 {
15635 mutexLock(&t.mutex);
15636 defer mutexUnlock(&t.mutex);
15637 if (t.pipe_file.handle) |handle| return handle;
15638 }
15639
15640 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'a', 'm', 'e', 'd', 'P', 'i', 'p', 'e', '\\' };
15641 var fresh_handle: windows.HANDLE = undefined;
15642 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
15643 var syscall: Syscall = try .start();
15644 while (true) switch (windows.ntdll.NtOpenFile(
15645 &fresh_handle,
15646 .{ .STANDARD = .{ .SYNCHRONIZE = true } },
15647 &.{
15648 .ObjectName = @constCast(&windows.UNICODE_STRING{
15649 .Length = @sizeOf(@TypeOf(device_path)),
15650 .MaximumLength = 0,
15651 .Buffer = @constCast(&device_path),
15652 }),
15653 },
15654 &io_status_block,
15655 .VALID_FLAGS,
15656 .{ .IO = .SYNCHRONOUS_NONALERT },
15657 )) {
15658 .SUCCESS => {
15659 syscall.finish();
15660 mutexLock(&t.mutex); // Another thread might have won the race.
15661 defer mutexUnlock(&t.mutex);
15662 if (t.pipe_file.handle) |prev_handle| {
15663 windows.CloseHandle(fresh_handle);
15664 return prev_handle;
15665 } else {
15666 t.pipe_file.handle = fresh_handle;
15667 return fresh_handle;
15668 }
15669 },
15626 .DELETE_PENDING => {15670 .DELETE_PENDING => {
15627 // This error means that there *was* a file in this location on15671 // This error means that there *was* a file in this location on
15628 // the file system, but it was deleted. However, the OS is not15672 // the file system, but it was deleted. However, the OS is not
...@@ -15669,7 +15713,7 @@ fn windowsCreateProcessPathExt(...@@ -15669,7 +15713,7 @@ fn windowsCreateProcessPathExt(
15669 app_buf: *std.ArrayList(u16),15713 app_buf: *std.ArrayList(u16),
15670 pathext: [:0]const u16,15714 pathext: [:0]const u16,
15671 cmd_line_cache: *WindowsCommandLineCache,15715 cmd_line_cache: *WindowsCommandLineCache,
15672 envp_ptr: ?[*:0]const u16,15716 env_block: ?process.Environ.WindowsBlock,
15673 cwd_ptr: ?[*:0]u16,15717 cwd_ptr: ?[*:0]u16,
15674 flags: windows.CreateProcessFlags,15718 flags: windows.CreateProcessFlags,
15675 lpStartupInfo: *windows.STARTUPINFOW,15719 lpStartupInfo: *windows.STARTUPINFOW,
...@@ -15846,7 +15890,7 @@ fn windowsCreateProcessPathExt(...@@ -15846,7 +15890,7 @@ fn windowsCreateProcessPathExt(
15846 if (windowsCreateProcess(15890 if (windowsCreateProcess(
15847 app_name_w.ptr,15891 app_name_w.ptr,
15848 cmd_line_w.ptr,15892 cmd_line_w.ptr,
15849 envp_ptr,15893 env_block,
15850 cwd_ptr,15894 cwd_ptr,
15851 flags,15895 flags,
15852 lpStartupInfo,15896 lpStartupInfo,
...@@ -15906,7 +15950,7 @@ fn windowsCreateProcessPathExt(...@@ -15906,7 +15950,7 @@ fn windowsCreateProcessPathExt(
15906 else15950 else
15907 full_app_name;15951 full_app_name;
1590815952
15909 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {15953 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, env_block, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {
15910 return;15954 return;
15911 } else |err| switch (err) {15955 } else |err| switch (err) {
15912 error.FileNotFound => continue,15956 error.FileNotFound => continue,
...@@ -15930,7 +15974,7 @@ fn windowsCreateProcessPathExt(...@@ -15930,7 +15974,7 @@ fn windowsCreateProcessPathExt(
15930fn windowsCreateProcess(15974fn windowsCreateProcess(
15931 app_name: [*:0]u16,15975 app_name: [*:0]u16,
15932 cmd_line: [*:0]u16,15976 cmd_line: [*:0]u16,
15933 env_ptr: ?[*:0]const u16,15977 env_block: ?process.Environ.WindowsBlock,
15934 cwd_ptr: ?[*:0]u16,15978 cwd_ptr: ?[*:0]u16,
15935 flags: windows.CreateProcessFlags,15979 flags: windows.CreateProcessFlags,
15936 lpStartupInfo: *windows.STARTUPINFOW,15980 lpStartupInfo: *windows.STARTUPINFOW,
...@@ -15945,7 +15989,7 @@ fn windowsCreateProcess(...@@ -15945,7 +15989,7 @@ fn windowsCreateProcess(
15945 null,15989 null,
15946 windows.TRUE,15990 windows.TRUE,
15947 flags,15991 flags,
15948 env_ptr,15992 if (env_block) |block| block.slice.ptr else null,
15949 cwd_ptr,15993 cwd_ptr,
15950 lpStartupInfo,15994 lpStartupInfo,
15951 lpProcessInformation,15995 lpProcessInformation,
...@@ -16466,11 +16510,11 @@ fn posixExecv(...@@ -16466,11 +16510,11 @@ fn posixExecv(
16466 arg0_expand: process.ArgExpansion,16510 arg0_expand: process.ArgExpansion,
16467 file: [*:0]const u8,16511 file: [*:0]const u8,
16468 child_argv: [*:null]?[*:0]const u8,16512 child_argv: [*:null]?[*:0]const u8,
16469 envp: [*:null]const ?[*:0]const u8,16513 env_block: process.Environ.PosixBlock,
16470 PATH: []const u8,16514 PATH: []const u8,
16471) process.ReplaceError {16515) process.ReplaceError {
16472 const file_slice = std.mem.sliceTo(file, 0);16516 const file_slice = std.mem.sliceTo(file, 0);
16473 if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, envp);16517 if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, env_block);
1647416518
16475 // Use of PATH_MAX here is valid as the path_buf will be passed16519 // Use of PATH_MAX here is valid as the path_buf will be passed
16476 // directly to the operating system in posixExecvPath.16520 // directly to the operating system in posixExecvPath.
...@@ -16498,7 +16542,7 @@ fn posixExecv(...@@ -16498,7 +16542,7 @@ fn posixExecv(
16498 .expand => child_argv[0] = full_path,16542 .expand => child_argv[0] = full_path,
16499 .no_expand => {},16543 .no_expand => {},
16500 }16544 }
16501 err = posixExecvPath(full_path, child_argv, envp);16545 err = posixExecvPath(full_path, child_argv, env_block);
16502 switch (err) {16546 switch (err) {
16503 error.AccessDenied => seen_eacces = true,16547 error.AccessDenied => seen_eacces = true,
16504 error.FileNotFound, error.NotDir => {},16548 error.FileNotFound, error.NotDir => {},
...@@ -16513,10 +16557,10 @@ fn posixExecv(...@@ -16513,10 +16557,10 @@ fn posixExecv(
16513pub fn posixExecvPath(16557pub fn posixExecvPath(
16514 path: [*:0]const u8,16558 path: [*:0]const u8,
16515 child_argv: [*:null]const ?[*:0]const u8,16559 child_argv: [*:null]const ?[*:0]const u8,
16516 envp: [*:null]const ?[*:0]const u8,16560 env_block: process.Environ.PosixBlock,
16517) process.ReplaceError {16561) process.ReplaceError {
16518 try Thread.checkCancel();16562 try Thread.checkCancel();
16519 switch (posix.errno(posix.system.execve(path, child_argv, envp))) {16563 switch (posix.errno(posix.system.execve(path, child_argv, env_block.slice.ptr))) {
16520 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.16564 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
16521 .@"2BIG" => return error.SystemResources,16565 .@"2BIG" => return error.SystemResources,
16522 .MFILE => return error.ProcessFdQuotaExceeded,16566 .MFILE => return error.ProcessFdQuotaExceeded,
...@@ -16548,100 +16592,105 @@ pub fn posixExecvPath(...@@ -16548,100 +16592,105 @@ pub fn posixExecvPath(
16548 }16592 }
16549}16593}
1655016594
16551fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {16595pub const CreatePipeOptions = struct {
16552 var rd_h: windows.HANDLE = undefined;16596 server: End,
16553 var wr_h: windows.HANDLE = undefined;16597 client: End,
16554 try windows.CreatePipe(&rd_h, &wr_h, sattr);16598 inbound: bool = false,
16555 errdefer windowsDestroyPipe(rd_h, wr_h);16599 outbound: bool = false,
16556 try windows.SetHandleInformation(wr_h, windows.HANDLE_FLAG_INHERIT, 0);16600 maximum_instances: u32 = 1,
16557 rd.* = rd_h;16601 quota: u32 = 4096,
16558 wr.* = wr_h;16602 default_timeout: windows.LARGE_INTEGER = -120 * std.time.ns_per_s / 100,
16559}
16560
16561fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void {
16562 if (rd) |h| posix.close(h);
16563 if (wr) |h| posix.close(h);
16564}
16565
16566fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
16567 var tmp_bufw: [128]u16 = undefined;
1656816603
16569 // Anonymous pipes are built upon Named pipes.16604 pub const End = struct {
16570 // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe16605 attributes: windows.OBJECT_ATTRIBUTES.ATTRIBUTES = .{},
16571 // Asynchronous (overlapped) read and write operations are not supported by anonymous pipes.16606 mode: windows.FILE.MODE,
16572 // https://docs.microsoft.com/en-us/windows/win32/ipc/anonymous-pipe-operations
16573 const pipe_path = blk: {
16574 var tmp_buf: [128]u8 = undefined;
16575 // Forge a random path for the pipe.
16576 const pipe_path = std.fmt.bufPrintSentinel(
16577 &tmp_buf,
16578 "\\\\.\\pipe\\zig-childprocess-{d}-{d}",
16579 .{ windows.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) },
16580 0,
16581 ) catch unreachable;
16582 const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable;
16583 tmp_bufw[len] = 0;
16584 break :blk tmp_bufw[0..len :0];
16585 };16607 };
1658616608};
16587 // Create the read handle that can be used with overlapped IO ops.16609pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.HANDLE {
16588 const read_handle = windows.kernel32.CreateNamedPipeW(16610 const named_pipe_device = try t.getNamedPipeDevice();
16589 pipe_path.ptr,16611 const server_handle = server_handle: {
16590 windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED,16612 var handle: windows.HANDLE = undefined;
16591 windows.PIPE_TYPE_BYTE,16613 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
16592 1,16614 const syscall: Syscall = try .start();
16593 4096,16615 while (true) switch (windows.ntdll.NtCreateNamedPipeFile(
16594 4096,16616 &handle,
16595 0,16617 .{
16596 sattr,16618 .SPECIFIC = .{ .FILE_PIPE = .{
16597 );16619 .READ_DATA = options.inbound,
16598 if (read_handle == windows.INVALID_HANDLE_VALUE) {16620 .WRITE_DATA = options.outbound,
16599 switch (windows.GetLastError()) {16621 .WRITE_ATTRIBUTES = true,
16600 else => |err| return windows.unexpectedError(err),16622 } },
16601 }16623 .STANDARD = .{ .SYNCHRONIZE = true },
16602 }16624 },
16603 errdefer posix.close(read_handle);16625 &.{
1660416626 .RootDirectory = named_pipe_device,
16605 var sattr_copy = sattr.*;16627 .Attributes = options.server.attributes,
16606 const write_handle = windows.kernel32.CreateFileW(16628 },
16607 pipe_path.ptr,16629 &io_status_block,
16608 .{ .GENERIC = .{ .WRITE = true } },16630 .{ .READ = true, .WRITE = true },
16609 0,16631 .CREATE,
16610 &sattr_copy,16632 options.server.mode,
16611 windows.OPEN_EXISTING,16633 .{ .TYPE = .BYTE_STREAM },
16612 @bitCast(windows.FILE.ATTRIBUTE{ .NORMAL = true }),16634 .{ .MODE = .BYTE_STREAM },
16613 null,16635 .{ .OPERATION = .QUEUE },
16614 );16636 options.maximum_instances,
16615 if (write_handle == windows.INVALID_HANDLE_VALUE) {16637 if (options.inbound) options.quota else 0,
16616 switch (windows.GetLastError()) {16638 if (options.outbound) options.quota else 0,
16617 else => |err| return windows.unexpectedError(err),16639 &options.default_timeout,
16618 }16640 )) {
16619 }16641 .SUCCESS => break syscall.finish(),
16620 errdefer posix.close(write_handle);16642 .CANCELLED => {
1662116643 try syscall.checkCancel();
16622 try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0);16644 continue;
1662316645 },
16624 rd.* = read_handle;16646 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
16625 wr.* = write_handle;16647 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
16648 else => |status| return syscall.unexpectedNtstatus(status),
16649 };
16650 break :server_handle handle;
16651 };
16652 errdefer windows.CloseHandle(server_handle);
16653 const client_handle = client_handle: {
16654 var handle: windows.HANDLE = undefined;
16655 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
16656 const syscall: Syscall = try .start();
16657 while (true) switch (windows.ntdll.NtOpenFile(
16658 &handle,
16659 .{
16660 .SPECIFIC = .{ .FILE_PIPE = .{
16661 .READ_DATA = options.outbound,
16662 .WRITE_DATA = options.inbound,
16663 .WRITE_ATTRIBUTES = true,
16664 } },
16665 .STANDARD = .{ .SYNCHRONIZE = true },
16666 },
16667 &.{
16668 .RootDirectory = server_handle,
16669 .Attributes = options.client.attributes,
16670 },
16671 &io_status_block,
16672 .{ .READ = true, .WRITE = true },
16673 options.client.mode,
16674 )) {
16675 .SUCCESS => break syscall.finish(),
16676 .CANCELLED => {
16677 try syscall.checkCancel();
16678 continue;
16679 },
16680 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
16681 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
16682 else => |status| return syscall.unexpectedNtstatus(status),
16683 };
16684 break :client_handle handle;
16685 };
16686 errdefer windows.CloseHandle(client_handle);
16687 return .{ server_handle, client_handle };
16626}16688}
1662716689
16628var pipe_name_counter = std.atomic.Value(u32).init(1);
16629
16630fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {16690fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
16631 const t: *Threaded = @ptrCast(@alignCast(userdata));16691 const t: *Threaded = @ptrCast(@alignCast(userdata));
16632
16633 t.scanEnviron();16692 t.scanEnviron();
1663416693 return t.environ.zig_progress_file;
16635 const int = try t.environ.zig_progress_handle;
16636
16637 return .{
16638 .handle = switch (@typeInfo(Io.File.Handle)) {
16639 .int => int,
16640 .pointer => @ptrFromInt(int),
16641 else => return error.UnsupportedOperation,
16642 },
16643 .flags = .{ .nonblocking = false },
16644 };
16645}16694}
1664616695
16647pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {16696pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {
...@@ -16737,7 +16786,7 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void {...@@ -16737,7 +16786,7 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void {
16737 // despite the function being documented to always return TRUE16786 // despite the function being documented to always return TRUE
16738 // * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG16787 // * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG
16739 // Therefore, that function is avoided in favor of using the device directly.16788 // Therefore, that function is avoided in favor of using the device directly.
16740 const cng_device = try getCngHandle(t);16789 const cng_device = try getCngDevice(t);
16741 var io_status_block: windows.IO_STATUS_BLOCK = undefined;16790 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
16742 var i: usize = 0;16791 var i: usize = 0;
16743 const syscall: Syscall = try .start();16792 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+473-472
...@@ -11,7 +11,7 @@ const windows = std.os.windows;...@@ -11,7 +11,7 @@ const windows = std.os.windows;
11const testing = std.testing;11const testing = std.testing;
12const assert = std.debug.assert;12const assert = std.debug.assert;
13const posix = std.posix;13const posix = std.posix;
14const Writer = std.Io.Writer;14const Writer = Io.Writer;
1515
16/// Currently this API only supports this value being set to stderr, which16/// Currently this API only supports this value being set to stderr, which
17/// happens automatically inside `start`.17/// happens automatically inside `start`.
...@@ -21,13 +21,10 @@ io: Io,...@@ -21,13 +21,10 @@ io: Io,
2121
22terminal_mode: TerminalMode,22terminal_mode: TerminalMode,
2323
24update_worker: ?Io.Future(void),24update_worker: ?Io.Future(WorkerError!void),
2525
26/// Atomically set by SIGWINCH as well as the root done() function.26/// Atomically set by SIGWINCH as well as the root done() function.
27redraw_event: Io.Event,27redraw_event: Io.Event,
28/// Indicates a request to shut down and reset global state.
29/// Accessed atomically.
30done: bool,
31need_clear: bool,28need_clear: bool,
32status: Status,29status: Status,
3330
...@@ -43,15 +40,19 @@ draw_buffer: []u8,...@@ -43,15 +40,19 @@ draw_buffer: []u8,
43/// This is in a separate array from `node_storage` but with the same length so40/// This is in a separate array from `node_storage` but with the same length so
44/// that it can be iterated over efficiently without trashing too much of the41/// that it can be iterated over efficiently without trashing too much of the
45/// CPU cache.42/// CPU cache.
46node_parents: []Node.Parent,43node_parents: [node_storage_buffer_len]Node.Parent,
47node_storage: []Node.Storage,44node_storage: [node_storage_buffer_len]Node.Storage,
48node_freelist_next: []Node.OptionalIndex,45node_freelist_next: [node_storage_buffer_len]Node.OptionalIndex,
49node_freelist: Freelist,46node_freelist: Freelist,
50/// This is the number of elements in node arrays which have been used so far. Nodes before this47/// This is the number of elements in node arrays which have been used so far. Nodes before this
51/// index are either active, or on the freelist. The remaining nodes are implicitly free. This48/// index are either active, or on the freelist. The remaining nodes are implicitly free. This
52/// value may at times temporarily exceed the node count.49/// value may at times temporarily exceed the node count.
53node_end_index: u32,50node_end_index: u32,
5451
52ipc_next: Ipc.SlotAtomic,
53ipc: [ipc_storage_buffer_len]Ipc,
54ipc_files: [ipc_storage_buffer_len]Io.File,
55
55start_failure: StartFailure,56start_failure: StartFailure,
5657
57pub const Status = enum {58pub const Status = enum {
...@@ -77,6 +78,80 @@ const Freelist = packed struct(u32) {...@@ -77,6 +78,80 @@ const Freelist = packed struct(u32) {
77 generation: u24,78 generation: u24,
78};79};
7980
81pub const Ipc = packed struct(u32) {
82 /// mutex protecting `file` use, only locked by `serializeIpc`
83 locked: bool,
84 /// when unlocked: whether `file` is defined
85 /// when locked: whether `file` does not need to be closed
86 valid: bool,
87 unused: @Int(.unsigned, 32 - 2 - @bitSizeOf(Generation)) = 0,
88 generation: Generation,
89
90 pub const Slot = std.math.IntFittingRange(0, ipc_storage_buffer_len - 1);
91 pub const Generation = @Int(.unsigned, 32 - @bitSizeOf(Slot));
92
93 const SlotAtomic = @Int(.unsigned, std.math.ceilPowerOfTwoAssert(usize, @min(@bitSizeOf(Slot), 8)));
94
95 pub const Index = packed struct(u32) {
96 slot: Slot,
97 generation: Generation,
98 };
99
100 const Data = struct {
101 state: State,
102 bytes_read: u16,
103 main_index: u8,
104 start_index: u8,
105 nodes_len: u8,
106
107 const State = enum { unused, pending, ready };
108
109 /// No operations have been started on this file.
110 const unused: Data = .{
111 .state = .unused,
112 .bytes_read = 0,
113 .main_index = 0,
114 .start_index = 0,
115 .nodes_len = 0,
116 };
117
118 fn findLastPacket(data: *const Data, buffer: *const [max_packet_len]u8) struct { u16, u16 } {
119 assert(data.state == .ready);
120 var packet_start: u16 = 0;
121 var packet_end: u16 = 0;
122 const bytes_read = data.bytes_read;
123 while (bytes_read - packet_end >= 1) {
124 const nodes_len: u16 = buffer[packet_end];
125 const packet_len = 1 + nodes_len * (@sizeOf(Node.Storage) + @sizeOf(Node.Parent));
126 if (packet_end + packet_len > bytes_read) break;
127 packet_start = packet_end;
128 packet_end += packet_len;
129 }
130 return .{ packet_start, packet_end };
131 }
132
133 fn rebase(
134 data: *Data,
135 buffer: *[max_packet_len]u8,
136 vec: *[1][]u8,
137 batch: *std.Io.Batch,
138 slot: Slot,
139 packet_end: u16,
140 ) void {
141 assert(data.state == .ready);
142 const remaining = buffer[packet_end..data.bytes_read];
143 @memmove(buffer[0..remaining.len], remaining);
144 vec.* = .{buffer[remaining.len..]};
145 batch.addAt(slot, .{ .file_read_streaming = .{
146 .file = global_progress.ipc_files[slot],
147 .data = vec,
148 } });
149 data.state = .pending;
150 data.bytes_read = @intCast(remaining.len);
151 }
152 };
153};
154
80pub const TerminalMode = union(enum) {155pub const TerminalMode = union(enum) {
81 off,156 off,
82 ansi_escape_codes,157 ansi_escape_codes,
...@@ -116,7 +191,7 @@ pub const Node = struct {...@@ -116,7 +191,7 @@ pub const Node = struct {
116191
117 pub const none: Node = .{ .index = .none };192 pub const none: Node = .{ .index = .none };
118193
119 pub const max_name_len = 40;194 pub const max_name_len = 120;
120195
121 const Storage = extern struct {196 const Storage = extern struct {
122 /// Little endian.197 /// Little endian.
...@@ -127,25 +202,16 @@ pub const Node = struct {...@@ -127,25 +202,16 @@ pub const Node = struct {
127 name: [max_name_len]u8 align(@alignOf(usize)),202 name: [max_name_len]u8 align(@alignOf(usize)),
128203
129 /// Not thread-safe.204 /// Not thread-safe.
130 fn getIpcFd(s: Storage) ?Io.File.Handle {205 fn getIpcIndex(s: Storage) ?Ipc.Index {
131 return if (s.estimated_total_count == std.math.maxInt(u32)) switch (@typeInfo(Io.File.Handle)) {206 return if (s.estimated_total_count == std.math.maxInt(u32)) @bitCast(s.completed_count) else null;
132 .int => @bitCast(s.completed_count),
133 .pointer => @ptrFromInt(s.completed_count),
134 else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
135 } else null;
136 }207 }
137208
138 /// Thread-safe.209 /// Thread-safe.
139 fn setIpcFd(s: *Storage, fd: Io.File.Handle) void {210 fn setIpcIndex(s: *Storage, ipc_index: Ipc.Index) void {
140 const integer: u32 = switch (@typeInfo(Io.File.Handle)) {
141 .int => @bitCast(fd),
142 .pointer => @intFromPtr(fd),
143 else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
144 };
145 // `estimated_total_count` max int indicates the special state that211 // `estimated_total_count` max int indicates the special state that
146 // causes `completed_count` to be treated as a file descriptor, so212 // causes `completed_count` to be treated as a file descriptor, so
147 // the order here matters.213 // the order here matters.
148 @atomicStore(u32, &s.completed_count, integer, .monotonic);214 @atomicStore(u32, &s.completed_count, @bitCast(ipc_index), .monotonic);
149 @atomicStore(u32, &s.estimated_total_count, std.math.maxInt(u32), .release); // synchronizes with acquire in `serialize`215 @atomicStore(u32, &s.estimated_total_count, std.math.maxInt(u32), .release); // synchronizes with acquire in `serialize`
150 }216 }
151217
...@@ -155,6 +221,14 @@ pub const Node = struct {...@@ -155,6 +221,14 @@ pub const Node = struct {
155 s.estimated_total_count = @byteSwap(s.estimated_total_count);221 s.estimated_total_count = @byteSwap(s.estimated_total_count);
156 }222 }
157223
224 fn copyRoot(dest: *Node.Storage, src: *align(1) const Node.Storage) void {
225 dest.* = .{
226 .completed_count = src.completed_count,
227 .estimated_total_count = src.estimated_total_count,
228 .name = if (src.name[0] == 0) dest.name else src.name,
229 };
230 }
231
158 comptime {232 comptime {
159 assert((@sizeOf(Storage) % 4) == 0);233 assert((@sizeOf(Storage) % 4) == 0);
160 }234 }
...@@ -242,7 +316,7 @@ pub const Node = struct {...@@ -242,7 +316,7 @@ pub const Node = struct {
242 }316 }
243317
244 const free_index = @atomicRmw(u32, &global_progress.node_end_index, .Add, 1, .monotonic);318 const free_index = @atomicRmw(u32, &global_progress.node_end_index, .Add, 1, .monotonic);
245 if (free_index >= global_progress.node_storage.len) {319 if (free_index >= node_storage_buffer_len) {
246 // Ran out of node storage memory. Progress for this node will not be tracked.320 // Ran out of node storage memory. Progress for this node will not be tracked.
247 _ = @atomicRmw(u32, &global_progress.node_end_index, .Sub, 1, .monotonic);321 _ = @atomicRmw(u32, &global_progress.node_end_index, .Sub, 1, .monotonic);
248 return Node.none;322 return Node.none;
...@@ -292,15 +366,17 @@ pub const Node = struct {...@@ -292,15 +366,17 @@ pub const Node = struct {
292 const index = n.index.unwrap() orelse return;366 const index = n.index.unwrap() orelse return;
293 const storage = storageByIndex(index);367 const storage = storageByIndex(index);
294 // Avoid u32 max int which is used to indicate a special state.368 // Avoid u32 max int which is used to indicate a special state.
295 const saturated = @min(std.math.maxInt(u32) - 1, count);369 const saturated_total_count = @min(std.math.maxInt(u32) - 1, count);
296 @atomicStore(u32, &storage.estimated_total_count, saturated, .monotonic);370 @atomicStore(u32, &storage.estimated_total_count, saturated_total_count, .monotonic);
297 }371 }
298372
299 /// Thread-safe.373 /// Thread-safe.
300 pub fn increaseEstimatedTotalItems(n: Node, count: usize) void {374 pub fn increaseEstimatedTotalItems(n: Node, count: usize) void {
301 const index = n.index.unwrap() orelse return;375 const index = n.index.unwrap() orelse return;
302 const storage = storageByIndex(index);376 const storage = storageByIndex(index);
303 _ = @atomicRmw(u32, &storage.estimated_total_count, .Add, std.math.lossyCast(u32, count), .monotonic);377 // Avoid u32 max int which is used to indicate a special state.
378 const saturated_total_count = @min(std.math.maxInt(u32) - 1, count);
379 _ = @atomicRmw(u32, &storage.estimated_total_count, .Add, saturated_total_count, .monotonic);
304 }380 }
305381
306 /// Finish a started `Node`. Thread-safe.382 /// Finish a started `Node`. Thread-safe.
...@@ -310,11 +386,25 @@ pub const Node = struct {...@@ -310,11 +386,25 @@ pub const Node = struct {
310 return;386 return;
311 }387 }
312 const index = n.index.unwrap() orelse return;388 const index = n.index.unwrap() orelse return;
389 const io = global_progress.io;
313 const parent_ptr = parentByIndex(index);390 const parent_ptr = parentByIndex(index);
314 if (@atomicLoad(Node.Parent, parent_ptr, .monotonic).unwrap()) |parent_index| {391 if (@atomicLoad(Node.Parent, parent_ptr, .monotonic).unwrap()) |parent_index| {
315 _ = @atomicRmw(u32, &storageByIndex(parent_index).completed_count, .Add, 1, .monotonic);392 _ = @atomicRmw(u32, &storageByIndex(parent_index).completed_count, .Add, 1, .monotonic);
316 @atomicStore(Node.Parent, parent_ptr, .unused, .monotonic);393 @atomicStore(Node.Parent, parent_ptr, .unused, .monotonic);
317394
395 if (storageByIndex(index).getIpcIndex()) |ipc_index| {
396 const file = global_progress.ipc_files[ipc_index.slot];
397 const ipc = @atomicRmw(
398 Ipc,
399 &global_progress.ipc[ipc_index.slot],
400 .And,
401 .{ .locked = true, .valid = false, .generation = std.math.maxInt(Ipc.Generation) },
402 .release,
403 );
404 assert(ipc.valid and ipc.generation == ipc_index.generation);
405 if (!ipc.locked) file.close(io);
406 }
407
318 const freelist = &global_progress.node_freelist;408 const freelist = &global_progress.node_freelist;
319 var old_freelist = @atomicLoad(Freelist, freelist, .monotonic);409 var old_freelist = @atomicLoad(Freelist, freelist, .monotonic);
320 while (true) {410 while (true) {
...@@ -332,34 +422,52 @@ pub const Node = struct {...@@ -332,34 +422,52 @@ pub const Node = struct {
332 };422 };
333 }423 }
334 } else {424 } else {
335 @atomicStore(bool, &global_progress.done, true, .monotonic);425 if (global_progress.update_worker) |*worker| worker.cancel(io) catch {};
336 const io = global_progress.io;426 for (&global_progress.ipc, &global_progress.ipc_files) |ipc, ipc_file| {
337 global_progress.redraw_event.set(io);427 assert(!ipc.locked or !ipc.valid); // missing call to end()
338 if (global_progress.update_worker) |*worker| worker.await(io);428 if (ipc.locked or ipc.valid) ipc_file.close(io);
429 }
339 }430 }
340 }431 }
341432
342 /// Posix-only. Used by `std.process.Child`. Thread-safe.433 /// Used by `std.process.Child`. Thread-safe.
343 pub fn setIpcFd(node: Node, fd: Io.File.Handle) void {434 pub fn setIpcFile(node: Node, expected_io_userdata: ?*anyopaque, file: Io.File) void {
344 const index = node.index.unwrap() orelse return;435 const index = node.index.unwrap() orelse return;
345 assert(fd >= 0);436 const io = global_progress.io;
346 assert(fd != posix.STDOUT_FILENO);437 assert(io.userdata == expected_io_userdata);
347 assert(fd != posix.STDIN_FILENO);438 for (0..ipc_storage_buffer_len) |_| {
348 assert(fd != posix.STDERR_FILENO);439 const slot: Ipc.Slot = @truncate(
349 storageByIndex(index).setIpcFd(fd);440 @atomicRmw(Ipc.SlotAtomic, &global_progress.ipc_next, .Add, 1, .monotonic),
441 );
442 if (slot >= ipc_storage_buffer_len) continue;
443 const ipc_ptr = &global_progress.ipc[slot];
444 const ipc = @atomicLoad(Ipc, ipc_ptr, .monotonic);
445 if (ipc.locked or ipc.valid) continue;
446 const generation = ipc.generation +% 1;
447 if (@cmpxchgWeak(
448 Ipc,
449 ipc_ptr,
450 ipc,
451 .{ .locked = false, .valid = true, .generation = generation },
452 .acquire,
453 .monotonic,
454 )) |_| continue;
455 global_progress.ipc_files[slot] = file;
456 storageByIndex(index).setIpcIndex(.{ .slot = slot, .generation = generation });
457 break;
458 } else file.close(io);
350 }459 }
351460
352 /// Posix-only. Thread-safe. Assumes the node is storing an IPC file461 pub fn setIpcIndex(node: Node, ipc_index: Ipc.Index) void {
353 /// descriptor.462 storageByIndex(node.index.unwrap() orelse return).setIpcIndex(ipc_index);
354 pub fn getIpcFd(node: Node) ?Io.File.Handle {463 }
355 const index = node.index.unwrap() orelse return null;464
356 const storage = storageByIndex(index);465 /// Not thread-safe.
357 const int = @atomicLoad(u32, &storage.completed_count, .monotonic);466 pub fn takeIpcIndex(node: Node) ?Ipc.Index {
358 return switch (@typeInfo(Io.File.Handle)) {467 const storage = storageByIndex(node.index.unwrap() orelse return null);
359 .int => @bitCast(int),468 assert(storage.estimated_total_count == std.math.maxInt(u32));
360 .pointer => @ptrFromInt(int),469 @atomicStore(u32, &storage.estimated_total_count, 0, .monotonic);
361 else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),470 return @bitCast(storage.completed_count);
362 };
363 }471 }
364472
365 fn storageByIndex(index: Node.Index) *Node.Storage {473 fn storageByIndex(index: Node.Index) *Node.Storage {
...@@ -379,7 +487,9 @@ pub const Node = struct {...@@ -379,7 +487,9 @@ pub const Node = struct {
379487
380 const storage = storageByIndex(free_index);488 const storage = storageByIndex(free_index);
381 @atomicStore(u32, &storage.completed_count, 0, .monotonic);489 @atomicStore(u32, &storage.completed_count, 0, .monotonic);
382 @atomicStore(u32, &storage.estimated_total_count, std.math.lossyCast(u32, estimated_total_items), .monotonic);490 // Avoid u32 max int which is used to indicate a special state.
491 const saturated_total_count = @min(std.math.maxInt(u32) - 1, estimated_total_items);
492 @atomicStore(u32, &storage.estimated_total_count, saturated_total_count, .monotonic);
383 const name_len = @min(max_name_len, name.len);493 const name_len = @min(max_name_len, name.len);
384 copyAtomicStore(storage.name[0..name_len], name[0..name_len]);494 copyAtomicStore(storage.name[0..name_len], name[0..name_len]);
385 if (name_len < storage.name.len)495 if (name_len < storage.name.len)
...@@ -406,16 +516,20 @@ var global_progress: Progress = .{...@@ -406,16 +516,20 @@ var global_progress: Progress = .{
406 .rows = 0,516 .rows = 0,
407 .cols = 0,517 .cols = 0,
408 .draw_buffer = undefined,518 .draw_buffer = undefined,
409 .done = false,
410 .need_clear = false,519 .need_clear = false,
411 .status = .working,520 .status = .working,
412 .start_failure = .unstarted,
413521
414 .node_parents = &node_parents_buffer,522 .node_parents = undefined,
415 .node_storage = &node_storage_buffer,523 .node_storage = undefined,
416 .node_freelist_next = &node_freelist_next_buffer,524 .node_freelist_next = undefined,
417 .node_freelist = .{ .head = .none, .generation = 0 },525 .node_freelist = .{ .head = .none, .generation = 0 },
418 .node_end_index = 0,526 .node_end_index = 0,
527
528 .ipc_next = 0,
529 .ipc = undefined,
530 .ipc_files = undefined,
531
532 .start_failure = .unstarted,
419};533};
420534
421pub const StartFailure = union(enum) {535pub const StartFailure = union(enum) {
...@@ -425,17 +539,23 @@ pub const StartFailure = union(enum) {...@@ -425,17 +539,23 @@ pub const StartFailure = union(enum) {
425 parent_ipc: error{ UnsupportedOperation, UnrecognizedFormat },539 parent_ipc: error{ UnsupportedOperation, UnrecognizedFormat },
426};540};
427541
428const node_storage_buffer_len = 83;542/// One less than a power of two ensures `max_packet_len` is already a power of two.
429var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined;543const node_storage_buffer_len = ipc_storage_buffer_len - 1;
430var node_storage_buffer: [node_storage_buffer_len]Node.Storage = undefined;544
431var node_freelist_next_buffer: [node_storage_buffer_len]Node.OptionalIndex = undefined;545/// Power of two to avoid wasted `ipc_next` increments.
546const ipc_storage_buffer_len = 128;
547
548pub const max_packet_len = std.math.ceilPowerOfTwoAssert(
549 usize,
550 1 + node_storage_buffer_len * (@sizeOf(Node.Storage) + @sizeOf(Node.OptionalIndex)),
551);
432552
433var default_draw_buffer: [4096]u8 = undefined;553var default_draw_buffer: [4096]u8 = undefined;
434554
435var debug_start_trace = std.debug.Trace.init;555var debug_start_trace = std.debug.Trace.init;
436556
437pub const have_ipc = switch (builtin.os.tag) {557pub const have_ipc = switch (builtin.os.tag) {
438 .wasi, .freestanding, .windows => false,558 .wasi, .freestanding => false,
439 else => true,559 else => true,
440};560};
441561
...@@ -467,9 +587,9 @@ pub fn start(io: Io, options: Options) Node {...@@ -467,9 +587,9 @@ pub fn start(io: Io, options: Options) Node {
467 }587 }
468 debug_start_trace.add("first initialized here");588 debug_start_trace.add("first initialized here");
469589
470 @memset(global_progress.node_parents, .unused);590 @memset(&global_progress.node_parents, .unused);
591 @memset(&global_progress.ipc, .{ .locked = false, .valid = false, .generation = 0 });
471 const root_node = Node.init(@enumFromInt(0), .none, options.root_name, options.estimated_total_items);592 const root_node = Node.init(@enumFromInt(0), .none, options.root_name, options.estimated_total_items);
472 global_progress.done = false;
473 global_progress.node_end_index = 1;593 global_progress.node_end_index = 1;
474594
475 assert(options.draw_buffer.len >= 200);595 assert(options.draw_buffer.len >= 200);
...@@ -477,21 +597,18 @@ pub fn start(io: Io, options: Options) Node {...@@ -477,21 +597,18 @@ pub fn start(io: Io, options: Options) Node {
477 global_progress.refresh_rate_ns = @intCast(options.refresh_rate_ns.toNanoseconds());597 global_progress.refresh_rate_ns = @intCast(options.refresh_rate_ns.toNanoseconds());
478 global_progress.initial_delay_ns = @intCast(options.initial_delay_ns.toNanoseconds());598 global_progress.initial_delay_ns = @intCast(options.initial_delay_ns.toNanoseconds());
479599
480 if (noop_impl)600 if (noop_impl) return .none;
481 return Node.none;
482601
483 global_progress.io = io;602 global_progress.io = io;
484603
485 if (io.vtable.progressParentFile(io.userdata)) |ipc_file| {604 if (io.vtable.progressParentFile(io.userdata)) |ipc_file| {
486 global_progress.update_worker = io.concurrent(ipcThreadRun, .{ io, ipc_file }) catch |err| {605 global_progress.update_worker = io.concurrent(ipcThreadRun, .{ io, ipc_file }) catch |err| {
487 global_progress.start_failure = .{ .spawn_ipc_worker = err };606 global_progress.start_failure = .{ .spawn_ipc_worker = err };
488 return Node.none;607 return .none;
489 };608 };
490 } else |env_err| switch (env_err) {609 } else |env_err| switch (env_err) {
491 error.EnvironmentVariableMissing => {610 error.EnvironmentVariableMissing => {
492 if (options.disable_printing) {611 if (options.disable_printing) return .none;
493 return Node.none;
494 }
495 const stderr: Io.File = .stderr();612 const stderr: Io.File = .stderr();
496 global_progress.terminal = stderr;613 global_progress.terminal = stderr;
497 if (stderr.enableAnsiEscapeCodes(io)) |_| {614 if (stderr.enableAnsiEscapeCodes(io)) |_| {
...@@ -504,14 +621,12 @@ pub fn start(io: Io, options: Options) Node {...@@ -504,14 +621,12 @@ pub fn start(io: Io, options: Options) Node {
504 } else |err| switch (err) {621 } else |err| switch (err) {
505 error.Canceled => {622 error.Canceled => {
506 io.recancel();623 io.recancel();
507 return Node.none;624 return .none;
508 },625 },
509 }626 }
510 }627 }
511628
512 if (global_progress.terminal_mode == .off) {629 if (global_progress.terminal_mode == .off) return .none;
513 return Node.none;
514 }
515630
516 if (have_sigwinch) {631 if (have_sigwinch) {
517 const act: posix.Sigaction = .{632 const act: posix.Sigaction = .{
...@@ -530,12 +645,12 @@ pub fn start(io: Io, options: Options) Node {...@@ -530,12 +645,12 @@ pub fn start(io: Io, options: Options) Node {
530 global_progress.update_worker = future;645 global_progress.update_worker = future;
531 } else |err| {646 } else |err| {
532 global_progress.start_failure = .{ .spawn_update_worker = err };647 global_progress.start_failure = .{ .spawn_update_worker = err };
533 return Node.none;648 return .none;
534 }649 }
535 },650 },
536 else => |e| {651 else => |e| {
537 global_progress.start_failure = .{ .parent_ipc = e };652 global_progress.start_failure = .{ .parent_ipc = e };
538 return Node.none;653 return .none;
539 },654 },
540 }655 }
541656
...@@ -548,58 +663,55 @@ pub fn setStatus(new_status: Status) void {...@@ -548,58 +663,55 @@ pub fn setStatus(new_status: Status) void {
548}663}
549664
550/// Returns whether a resize is needed to learn the terminal size.665/// Returns whether a resize is needed to learn the terminal size.
551fn wait(io: Io, timeout_ns: u64) bool {666fn wait(io: Io, timeout_ns: u64) Io.Cancelable!bool {
552 const timeout: Io.Timeout = .{ .duration = .{667 const timeout: Io.Timeout = .{ .duration = .{
553 .clock = .awake,668 .clock = .awake,
554 .raw = .fromNanoseconds(timeout_ns),669 .raw = .fromNanoseconds(timeout_ns),
555 } };670 } };
556 const resize_flag = if (global_progress.redraw_event.waitTimeout(io, timeout)) |_| true else |err| switch (err) {671 const resize_flag = if (global_progress.redraw_event.waitTimeout(io, timeout)) |_| true else |err| switch (err) {
557 error.Timeout, error.Canceled => false,672 error.Timeout => false,
673 error.Canceled => |e| return e,
558 };674 };
559 global_progress.redraw_event.reset();675 global_progress.redraw_event.reset();
560 return resize_flag or (global_progress.cols == 0);676 return resize_flag or (global_progress.cols == 0);
561}677}
562678
563fn updateTask(io: Io) void {679const WorkerError = error{WindowTooSmall} || Io.ConcurrentError || Io.Cancelable ||
680 Io.File.Writer.Error || Io.Operation.FileReadStreaming.Error;
681
682fn updateTask(io: Io) WorkerError!void {
564 // Store this data in the thread so that it does not need to be part of the683 // Store this data in the thread so that it does not need to be part of the
565 // linker data of the main executable.684 // linker data of the main executable.
566 var serialized_buffer: Serialized.Buffer = undefined;685 var serialized_buffer: Serialized.Buffer = undefined;
686 serialized_buffer.init();
687 defer serialized_buffer.batch.cancel(io);
567688
568 // In this function we bypass the wrapper code inside `Io.lockStderr` /689 // In this function we bypass the wrapper code inside `Io.lockStderr` /
569 // `Io.tryLockStderr` in order to avoid clearing the terminal twice.690 // `Io.tryLockStderr` in order to avoid clearing the terminal twice.
570 // We still want to go through the `Io` instance however in case it uses a691 // We still want to go through the `Io` instance however in case it uses a
571 // task-switching mutex.692 // task-switching mutex.
572693
573 {694 try maybeUpdateSize(io, try wait(io, global_progress.initial_delay_ns));
574 const resize_flag = wait(io, global_progress.initial_delay_ns);695 errdefer {
575 if (@atomicLoad(bool, &global_progress.done, .monotonic)) return;696 const cancel_protection = io.swapCancelProtection(.blocked);
576 maybeUpdateSize(io, resize_flag) catch return;697 defer _ = io.swapCancelProtection(cancel_protection);
577698 const stderr = io.vtable.lockStderr(io.userdata, null) catch |err| switch (err) {
578 const buffer, _ = computeRedraw(&serialized_buffer);699 error.Canceled => unreachable, // blocked
579 if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {700 };
580 defer io.unlockStderr();701 defer io.unlockStderr();
581 global_progress.need_clear = true;702 clearWrittenWithEscapeCodes(stderr.file_writer) catch {};
582 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
583 }
584 }703 }
585
586 while (true) {704 while (true) {
587 const resize_flag = wait(io, global_progress.refresh_rate_ns);705 const buffer, _ = try computeRedraw(io, &serialized_buffer);
588706 if (try io.vtable.tryLockStderr(io.userdata, null)) |locked_stderr| {
589 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
590 const stderr = io.vtable.lockStderr(io.userdata, null) catch return;
591 defer io.unlockStderr();
592 return clearWrittenWithEscapeCodes(stderr.file_writer) catch {};
593 }
594
595 maybeUpdateSize(io, resize_flag) catch return;
596
597 const buffer, _ = computeRedraw(&serialized_buffer);
598 if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {
599 defer io.unlockStderr();707 defer io.unlockStderr();
600 global_progress.need_clear = true;708 global_progress.need_clear = true;
601 locked_stderr.file_writer.interface.writeAll(buffer) catch return;709 locked_stderr.file_writer.interface.writeAll(buffer) catch |err| switch (err) {
710 error.WriteFailed => return locked_stderr.file_writer.err.?,
711 };
602 }712 }
713
714 try maybeUpdateSize(io, try wait(io, global_progress.refresh_rate_ns));
603 }715 }
604}716}
605717
...@@ -611,79 +723,60 @@ fn windowsApiWriteMarker() void {...@@ -611,79 +723,60 @@ fn windowsApiWriteMarker() void {
611 _ = windows.kernel32.WriteConsoleW(handle, &[_]u16{windows_api_start_marker}, 1, &num_chars_written, null);723 _ = windows.kernel32.WriteConsoleW(handle, &[_]u16{windows_api_start_marker}, 1, &num_chars_written, null);
612}724}
613725
614fn windowsApiUpdateTask(io: Io) void {726fn windowsApiUpdateTask(io: Io) WorkerError!void {
727 // Store this data in the thread so that it does not need to be part of the
728 // linker data of the main executable.
615 var serialized_buffer: Serialized.Buffer = undefined;729 var serialized_buffer: Serialized.Buffer = undefined;
730 serialized_buffer.init();
731 defer serialized_buffer.batch.cancel(io);
616732
617 // In this function we bypass the wrapper code inside `Io.lockStderr` /733 // In this function we bypass the wrapper code inside `Io.lockStderr` /
618 // `Io.tryLockStderr` in order to avoid clearing the terminal twice.734 // `Io.tryLockStderr` in order to avoid clearing the terminal twice.
619 // We still want to go through the `Io` instance however in case it uses a735 // We still want to go through the `Io` instance however in case it uses a
620 // task-switching mutex.736 // task-switching mutex.
621737
622 {738 try maybeUpdateSize(io, try wait(io, global_progress.initial_delay_ns));
623 const resize_flag = wait(io, global_progress.initial_delay_ns);739 errdefer {
624 if (@atomicLoad(bool, &global_progress.done, .monotonic)) return;740 const cancel_protection = io.swapCancelProtection(.blocked);
625 maybeUpdateSize(io, resize_flag) catch return;741 defer _ = io.swapCancelProtection(cancel_protection);
626742 _ = io.vtable.lockStderr(io.userdata, null) catch |err| switch (err) {
627 const buffer, const nl_n = computeRedraw(&serialized_buffer);743 error.Canceled => unreachable, // blocked
628 if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {744 };
629 defer io.unlockStderr();745 defer io.unlockStderr();
630 windowsApiWriteMarker();746 clearWrittenWindowsApi() catch {};
631 global_progress.need_clear = true;
632 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
633 windowsApiMoveToMarker(nl_n) catch return;
634 }
635 }747 }
636
637 while (true) {748 while (true) {
638 const resize_flag = wait(io, global_progress.refresh_rate_ns);749 const buffer, const nl_n = try computeRedraw(io, &serialized_buffer);
639
640 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
641 _ = io.vtable.lockStderr(io.userdata, null) catch return;
642 defer io.unlockStderr();
643 return clearWrittenWindowsApi() catch {};
644 }
645
646 maybeUpdateSize(io, resize_flag) catch return;
647
648 const buffer, const nl_n = computeRedraw(&serialized_buffer);
649 if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {750 if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {
650 defer io.unlockStderr();751 defer io.unlockStderr();
651 clearWrittenWindowsApi() catch return;752 try clearWrittenWindowsApi();
652 windowsApiWriteMarker();753 windowsApiWriteMarker();
653 global_progress.need_clear = true;754 global_progress.need_clear = true;
654 locked_stderr.file_writer.interface.writeAll(buffer) catch return;755 locked_stderr.file_writer.interface.writeAll(buffer) catch |err| switch (err) {
756 error.WriteFailed => return locked_stderr.file_writer.err.?,
757 };
655 windowsApiMoveToMarker(nl_n) catch return;758 windowsApiMoveToMarker(nl_n) catch return;
656 }759 }
760
761 try maybeUpdateSize(io, try wait(io, global_progress.refresh_rate_ns));
657 }762 }
658}763}
659764
660fn ipcThreadRun(io: Io, file: Io.File) void {765fn ipcThreadRun(io: Io, file: Io.File) WorkerError!void {
661 // Store this data in the thread so that it does not need to be part of the766 // Store this data in the thread so that it does not need to be part of the
662 // linker data of the main executable.767 // linker data of the main executable.
663 var serialized_buffer: Serialized.Buffer = undefined;768 var serialized_buffer: Serialized.Buffer = undefined;
769 serialized_buffer.init();
770 defer serialized_buffer.batch.cancel(io);
771 var fw = file.writerStreaming(io, &.{});
664772
665 {773 _ = try io.sleep(.fromNanoseconds(global_progress.initial_delay_ns), .awake);
666 _ = wait(io, global_progress.initial_delay_ns);
667
668 if (@atomicLoad(bool, &global_progress.done, .monotonic))
669 return;
670
671 const serialized = serialize(&serialized_buffer);
672 writeIpc(io, file, serialized) catch |err| switch (err) {
673 error.BrokenPipe => return,
674 };
675 }
676
677 while (true) {774 while (true) {
678 _ = wait(io, global_progress.refresh_rate_ns);775 writeIpc(&fw.interface, try serialize(io, &serialized_buffer)) catch |err| switch (err) {
679776 error.WriteFailed => return fw.err.?,
680 if (@atomicLoad(bool, &global_progress.done, .monotonic))
681 return;
682
683 const serialized = serialize(&serialized_buffer);
684 writeIpc(io, file, serialized) catch |err| switch (err) {
685 error.BrokenPipe => return,
686 };777 };
778
779 _ = try io.sleep(.fromNanoseconds(global_progress.refresh_rate_ns), .awake);
687 }780 }
688}781}
689782
...@@ -862,31 +955,49 @@ const Serialized = struct {...@@ -862,31 +955,49 @@ const Serialized = struct {
862 const Buffer = struct {955 const Buffer = struct {
863 parents: [node_storage_buffer_len]Node.Parent,956 parents: [node_storage_buffer_len]Node.Parent,
864 storage: [node_storage_buffer_len]Node.Storage,957 storage: [node_storage_buffer_len]Node.Storage,
865 map: [node_storage_buffer_len]Node.OptionalIndex,
866958
867 parents_copy: [node_storage_buffer_len]Node.Parent,959 ipc_start: u8,
868 storage_copy: [node_storage_buffer_len]Node.Storage,960 ipc_end: u8,
869 ipc_metadata_fds_copy: [node_storage_buffer_len]Fd,961 ipc_data: [ipc_storage_buffer_len]Ipc.Data,
870 ipc_metadata_copy: [node_storage_buffer_len]SavedMetadata,962 ipc_buffers: [ipc_storage_buffer_len][max_packet_len]u8,
871963 ipc_vecs: [ipc_storage_buffer_len][1][]u8,
872 ipc_metadata_fds: [node_storage_buffer_len]Fd,964 batch_storage: [ipc_storage_buffer_len]Io.Operation.Storage,
873 ipc_metadata: [node_storage_buffer_len]SavedMetadata,965 batch: Io.Batch,
966
967 fn init(buffer: *Buffer) void {
968 buffer.ipc_start = 0;
969 buffer.ipc_end = 0;
970 @memset(&buffer.ipc_data, .unused);
971 buffer.batch = .init(&buffer.batch_storage);
972 }
874 };973 };
875};974};
876975
877fn serialize(serialized_buffer: *Serialized.Buffer) Serialized {976fn serialize(io: Io, serialized_buffer: *Serialized.Buffer) !Serialized {
878 var serialized_len: usize = 0;977 var prev_parents: [node_storage_buffer_len]Node.Parent = undefined;
879 var any_ipc = false;978 var prev_storage: [node_storage_buffer_len]Node.Storage = undefined;
979 {
980 const ipc_start = serialized_buffer.ipc_start;
981 const ipc_end = serialized_buffer.ipc_end;
982 @memcpy(prev_parents[ipc_start..ipc_end], serialized_buffer.parents[ipc_start..ipc_end]);
983 @memcpy(prev_storage[ipc_start..ipc_end], serialized_buffer.storage[ipc_start..ipc_end]);
984 }
880985
881 // Iterate all of the nodes and construct a serializable copy of the state that can be examined986 // Iterate all of the nodes and construct a serializable copy of the state that can be examined
882 // without atomics. The `@min` call is here because `node_end_index` might briefly exceed the987 // without atomics. The `@min` call is here because `node_end_index` might briefly exceed the
883 // node count sometimes.988 // node count sometimes.
884 const end_index = @min(@atomicLoad(u32, &global_progress.node_end_index, .monotonic), global_progress.node_storage.len);989 const end_index = @min(
990 @atomicLoad(u32, &global_progress.node_end_index, .monotonic),
991 node_storage_buffer_len,
992 );
993 var map: [node_storage_buffer_len]Node.OptionalIndex = undefined;
994 var serialized_len: u8 = 0;
995 var maybe_ipc_start: ?u8 = null;
885 for (996 for (
886 global_progress.node_parents[0..end_index],997 global_progress.node_parents[0..end_index],
887 global_progress.node_storage[0..end_index],998 global_progress.node_storage[0..end_index],
888 serialized_buffer.map[0..end_index],999 map[0..end_index],
889 ) |*parent_ptr, *storage_ptr, *map| {1000 ) |*parent_ptr, *storage_ptr, *map_entry| {
890 const parent = @atomicLoad(Node.Parent, parent_ptr, .monotonic);1001 const parent = @atomicLoad(Node.Parent, parent_ptr, .monotonic);
891 if (parent == .unused) {1002 if (parent == .unused) {
892 // We might read "mixed" node data in this loop, due to weird atomic things1003 // We might read "mixed" node data in this loop, due to weird atomic things
...@@ -900,17 +1011,17 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized {...@@ -900,17 +1011,17 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized {
900 // parent, it will just not be printed at all. The general idea here is that performance1011 // parent, it will just not be printed at all. The general idea here is that performance
901 // is more important than 100% correct output every frame, given that this API is likely1012 // is more important than 100% correct output every frame, given that this API is likely
902 // to be used in hot paths!1013 // to be used in hot paths!
903 map.* = .none;1014 map_entry.* = .none;
904 continue;1015 continue;
905 }1016 }
906 const dest_storage = &serialized_buffer.storage[serialized_len];1017 const dest_storage = &serialized_buffer.storage[serialized_len];
907 copyAtomicLoad(&dest_storage.name, &storage_ptr.name);1018 copyAtomicLoad(&dest_storage.name, &storage_ptr.name);
908 dest_storage.estimated_total_count = @atomicLoad(u32, &storage_ptr.estimated_total_count, .acquire); // sychronizes with release in `setIpcFd`1019 dest_storage.estimated_total_count = @atomicLoad(u32, &storage_ptr.estimated_total_count, .acquire); // sychronizes with release in `setIpcIndex`
909 dest_storage.completed_count = @atomicLoad(u32, &storage_ptr.completed_count, .monotonic);1020 dest_storage.completed_count = @atomicLoad(u32, &storage_ptr.completed_count, .monotonic);
9101021
911 any_ipc = any_ipc or (dest_storage.getIpcFd() != null);
912 serialized_buffer.parents[serialized_len] = parent;1022 serialized_buffer.parents[serialized_len] = parent;
913 map.* = @enumFromInt(serialized_len);1023 map_entry.* = @enumFromInt(serialized_len);
1024 if (maybe_ipc_start == null and dest_storage.getIpcIndex() != null) maybe_ipc_start = serialized_len;
914 serialized_len += 1;1025 serialized_len += 1;
915 }1026 }
9161027
...@@ -919,266 +1030,212 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized {...@@ -919,266 +1030,212 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized {
919 parent.* = switch (parent.*) {1030 parent.* = switch (parent.*) {
920 .unused => unreachable,1031 .unused => unreachable,
921 .none => .none,1032 .none => .none,
922 _ => |p| serialized_buffer.map[@intFromEnum(p)].toParent(),1033 _ => |p| map[@intFromEnum(p)].toParent(),
923 };1034 };
924 }1035 }
9251036
926 // Find nodes which correspond to child processes.1037 // Fill pipe buffers.
927 if (any_ipc)1038 const batch = &serialized_buffer.batch;
928 serialized_len = serializeIpc(serialized_len, serialized_buffer);1039 batch.awaitConcurrent(io, .{
9291040 .duration = .{ .raw = .zero, .clock = .awake },
930 return .{1041 }) catch |err| switch (err) {
931 .parents = serialized_buffer.parents[0..serialized_len],1042 error.Timeout => {},
932 .storage = serialized_buffer.storage[0..serialized_len],1043 else => |e| return e,
1044 };
1045 var ready_len: u8 = 0;
1046 while (batch.next()) |operation| switch (operation.index) {
1047 0...ipc_storage_buffer_len - 1 => {
1048 const ipc_data = &serialized_buffer.ipc_data[operation.index];
1049 ipc_data.bytes_read += @intCast(
1050 operation.result.file_read_streaming catch |err| switch (err) {
1051 error.EndOfStream => {
1052 const file = global_progress.ipc_files[operation.index];
1053 const ipc = @atomicRmw(
1054 Ipc,
1055 &global_progress.ipc[operation.index],
1056 .And,
1057 .{
1058 .locked = false,
1059 .valid = true,
1060 .generation = std.math.maxInt(Ipc.Generation),
1061 },
1062 .release,
1063 );
1064 assert(ipc.locked);
1065 if (!ipc.valid) file.close(io);
1066 ipc_data.* = .unused;
1067 continue;
1068 },
1069 else => |e| return e,
1070 },
1071 );
1072 assert(ipc_data.state == .pending);
1073 ipc_data.state = .ready;
1074 ready_len += 1;
1075 },
1076 else => unreachable,
933 };1077 };
934}
935
936const SavedMetadata = struct {
937 remaining_read_trash_bytes: u16,
938 main_index: u8,
939 start_index: u8,
940 nodes_len: u8,
941};
942
943const Fd = enum(i32) {
944 _,
945
946 fn init(fd: Io.File.Handle) Fd {
947 return @enumFromInt(if (is_windows) @as(isize, @bitCast(@intFromPtr(fd))) else fd);
948 }
949
950 fn get(fd: Fd) Io.File.Handle {
951 return if (is_windows)
952 @ptrFromInt(@as(usize, @bitCast(@as(isize, @intFromEnum(fd)))))
953 else
954 @intFromEnum(fd);
955 }
956};
957
958var ipc_metadata_len: u8 = 0;
959
960fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buffer) usize {
961 const io = global_progress.io;
962 const ipc_metadata_fds_copy = &serialized_buffer.ipc_metadata_fds_copy;
963 const ipc_metadata_copy = &serialized_buffer.ipc_metadata_copy;
964 const ipc_metadata_fds = &serialized_buffer.ipc_metadata_fds;
965 const ipc_metadata = &serialized_buffer.ipc_metadata;
966
967 var serialized_len = start_serialized_len;
968 var pipe_buf: [2 * 4096]u8 = undefined;
969
970 const old_ipc_metadata_fds = ipc_metadata_fds_copy[0..ipc_metadata_len];
971 const old_ipc_metadata = ipc_metadata_copy[0..ipc_metadata_len];
972 ipc_metadata_len = 0;
9731078
974 main_loop: for (1079 // Find nodes which correspond to child processes.
975 serialized_buffer.parents[0..serialized_len],1080 const ipc_start = maybe_ipc_start orelse serialized_len;
976 serialized_buffer.storage[0..serialized_len],1081 serialized_buffer.ipc_start = ipc_start;
977 0..,1082 for (
1083 serialized_buffer.parents[ipc_start..serialized_len],
1084 serialized_buffer.storage[ipc_start..serialized_len],
1085 ipc_start..,
978 ) |main_parent, *main_storage, main_index| {1086 ) |main_parent, *main_storage, main_index| {
979 if (main_parent == .unused) continue;1087 if (main_parent == .unused) continue;
980 const file: Io.File = .{1088 const ipc_index = main_storage.getIpcIndex() orelse continue;
981 .handle = main_storage.getIpcFd() orelse continue,1089 const ipc = &global_progress.ipc[ipc_index.slot];
982 .flags = .{ .nonblocking = true },1090 const ipc_data = &serialized_buffer.ipc_data[ipc_index.slot];
983 };1091 state: switch (ipc_data.state) {
984 const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata);1092 .unused => {
985 var bytes_read: usize = 0;1093 if (@cmpxchgWeak(
986 while (true) {1094 Ipc,
987 const n = file.readStreaming(io, &.{pipe_buf[bytes_read..]}) catch |err| switch (err) {1095 ipc,
988 error.WouldBlock, error.EndOfStream => break,1096 .{ .locked = false, .valid = true, .generation = ipc_index.generation },
989 else => |e| {1097 .{ .locked = true, .valid = true, .generation = ipc_index.generation },
990 std.log.debug("failed to read child progress data: {t}", .{e});1098 .acquire,
991 main_storage.completed_count = 0;1099 .monotonic,
992 main_storage.estimated_total_count = 0;1100 )) |_| continue;
993 continue :main_loop;1101
994 },1102 const ipc_vec = &serialized_buffer.ipc_vecs[ipc_index.slot];
995 };1103 ipc_vec.* = .{&serialized_buffer.ipc_buffers[ipc_index.slot]};
996 if (opt_saved_metadata) |m| {1104 batch.addAt(ipc_index.slot, .{ .file_read_streaming = .{
997 if (m.remaining_read_trash_bytes > 0) {1105 .file = global_progress.ipc_files[ipc_index.slot],
998 assert(bytes_read == 0);1106 .data = ipc_vec,
999 if (m.remaining_read_trash_bytes >= n) {1107 } });
1000 m.remaining_read_trash_bytes = @intCast(m.remaining_read_trash_bytes - n);1108
1001 continue;1109 ipc_data.* = .{
1002 }1110 .state = .pending,
1003 const src = pipe_buf[m.remaining_read_trash_bytes..n];1111 .bytes_read = 0,
1004 @memmove(pipe_buf[0..src.len], src);1112 .main_index = @intCast(main_index),
1005 m.remaining_read_trash_bytes = 0;1113 .start_index = serialized_len,
1006 bytes_read = src.len;1114 .nodes_len = 0,
1007 continue;1115 };
1008 }1116 main_storage.completed_count = 0;
1009 }1117 main_storage.estimated_total_count = 0;
1010 bytes_read += n;1118 },
1011 }1119 .pending => {
1012 // Ignore all but the last message on the pipe.1120 const start_index = ipc_data.start_index;
1013 var input: []u8 = pipe_buf[0..bytes_read];1121 const nodes_len = @min(ipc_data.nodes_len, node_storage_buffer_len - serialized_len);
1014 if (input.len == 0) {1122
1015 serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, 0, file.handle);1123 main_storage.copyRoot(&prev_storage[ipc_data.main_index]);
1016 continue;1124 @memcpy(
1017 }1125 serialized_buffer.storage[serialized_len..][0..nodes_len],
10181126 prev_storage[start_index..][0..nodes_len],
1019 const storage, const parents = while (true) {1127 );
1020 const subtree_len: usize = input[0];1128 for (
1021 const expected_bytes = 1 + subtree_len * (@sizeOf(Node.Storage) + @sizeOf(Node.Parent));1129 serialized_buffer.parents[serialized_len..][0..nodes_len],
1022 if (input.len < expected_bytes) {1130 prev_parents[serialized_len..][0..nodes_len],
1023 // Ignore short reads. We'll handle the next full message when it comes instead.1131 ) |*parent, prev_parent| parent.* = switch (prev_parent) {
1024 const remaining_read_trash_bytes: u16 = @intCast(expected_bytes - input.len);1132 .none, .unused => .none,
1025 serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, remaining_read_trash_bytes, file.handle);1133 _ => if (@intFromEnum(prev_parent) == ipc_data.main_index)
1026 continue :main_loop;1134 @enumFromInt(main_index)
1027 }1135 else if (@intFromEnum(prev_parent) >= start_index and
1028 if (input.len > expected_bytes) {1136 @intFromEnum(prev_parent) < start_index + nodes_len)
1029 input = input[expected_bytes..];1137 @enumFromInt(@intFromEnum(prev_parent) - start_index + serialized_len)
1030 continue;1138 else
1031 }1139 .none,
1032 const storage_bytes = input[1..][0 .. subtree_len * @sizeOf(Node.Storage)];1140 };
1033 const parents_bytes = input[1 + storage_bytes.len ..][0 .. subtree_len * @sizeOf(Node.Parent)];
1034 break .{
1035 std.mem.bytesAsSlice(Node.Storage, storage_bytes),
1036 std.mem.bytesAsSlice(Node.Parent, parents_bytes),
1037 };
1038 };
1039
1040 const nodes_len: u8 = @intCast(@min(parents.len - 1, serialized_buffer.storage.len - serialized_len));
10411141
1042 // Remember in case the pipe is empty on next update.1142 ipc_data.main_index = @intCast(main_index);
1043 ipc_metadata_fds[ipc_metadata_len] = Fd.init(file.handle);1143 ipc_data.start_index = serialized_len;
1044 ipc_metadata[ipc_metadata_len] = .{1144 ipc_data.nodes_len = nodes_len;
1045 .remaining_read_trash_bytes = 0,1145 serialized_len += nodes_len;
1046 .start_index = @intCast(serialized_len),1146 },
1047 .nodes_len = nodes_len,1147 .ready => {
1048 .main_index = @intCast(main_index),1148 const ipc_buffer = &serialized_buffer.ipc_buffers[ipc_index.slot];
1049 };1149 const packet_start, const packet_end = ipc_data.findLastPacket(ipc_buffer);
1050 ipc_metadata_len += 1;1150 const packet_is_empty = packet_end - packet_start <= 1;
10511151 if (!packet_is_empty) {
1052 // Mount the root here.1152 const storage, const parents, const nodes_len = packet_contents: {
1053 copyRoot(main_storage, &storage[0]);1153 var packet_index: usize = packet_start;
1054 if (is_big_endian) main_storage.byteSwap();1154 const nodes_len: u16 = ipc_buffer[packet_index];
10551155 packet_index += 1;
1056 // Copy the rest of the tree to the end.1156 const storage_bytes =
1057 const storage_dest = serialized_buffer.storage[serialized_len..][0..nodes_len];1157 ipc_buffer[packet_index..][0 .. nodes_len * @sizeOf(Node.Storage)];
1058 @memcpy(storage_dest, storage[1..][0..nodes_len]);1158 packet_index += storage_bytes.len;
10591159 const parents_bytes =
1060 // Always little-endian over the pipe.1160 ipc_buffer[packet_index..][0 .. nodes_len * @sizeOf(Node.Parent)];
1061 if (is_big_endian) for (storage_dest) |*s| s.byteSwap();1161 packet_index += parents_bytes.len;
10621162 assert(packet_index == packet_end);
1063 // Patch up parent pointers taking into account how the subtree is mounted.1163 const storage: []align(1) const Node.Storage = @ptrCast(storage_bytes);
1064 for (serialized_buffer.parents[serialized_len..][0..nodes_len], parents[1..][0..nodes_len]) |*dest, p| {1164 const parents: []align(1) const Node.Parent = @ptrCast(parents_bytes);
1065 dest.* = switch (p) {1165 const children_nodes_len =
1066 // Fix bad data so the rest of the code does not see `unused`.1166 @min(nodes_len - 1, node_storage_buffer_len - serialized_len);
1067 .none, .unused => .none,1167 break :packet_contents .{ storage, parents, children_nodes_len };
1068 // Root node is being mounted here.1168 };
1069 @as(Node.Parent, @enumFromInt(0)) => @enumFromInt(main_index),1169
1070 // Other nodes mounted at the end.1170 // Mount the root here.
1071 // Don't trust child data; if the data is outside the expected range, ignore the data.1171 main_storage.copyRoot(&storage[0]);
1072 // This also handles the case when data was truncated.1172 if (is_big_endian) main_storage.byteSwap();
1073 _ => |off| if (@intFromEnum(off) > nodes_len)1173
1074 .none1174 // Copy the rest of the tree to the end.
1075 else1175 const serialized_storage =
1076 @enumFromInt(serialized_len + @intFromEnum(off) - 1),1176 serialized_buffer.storage[serialized_len..][0..nodes_len];
1077 };1177 @memcpy(serialized_storage, storage[1..][0..nodes_len]);
1178 if (is_big_endian) for (serialized_storage) |*s| s.byteSwap();
1179
1180 // Patch up parent pointers taking into account how the subtree is mounted.
1181 for (
1182 serialized_buffer.parents[serialized_len..][0..nodes_len],
1183 parents[1..][0..nodes_len],
1184 ) |*parent, prev_parent| parent.* = switch (prev_parent) {
1185 // Fix bad data so the rest of the code does not see `unused`.
1186 .none, .unused => .none,
1187 // Root node is being mounted here.
1188 @as(Node.Parent, @enumFromInt(0)) => @enumFromInt(main_index),
1189 // Other nodes mounted at the end.
1190 // Don't trust child data; if the data is outside the expected range,
1191 // ignore the data. This also handles the case when data was truncated.
1192 _ => if (@intFromEnum(prev_parent) <= nodes_len)
1193 @enumFromInt(@intFromEnum(prev_parent) - 1 + serialized_len)
1194 else
1195 .none,
1196 };
1197
1198 ipc_data.main_index = @intCast(main_index);
1199 ipc_data.start_index = serialized_len;
1200 ipc_data.nodes_len = nodes_len;
1201 serialized_len += nodes_len;
1202 }
1203 const ipc_vec = &serialized_buffer.ipc_vecs[ipc_index.slot];
1204 ipc_data.rebase(ipc_buffer, ipc_vec, batch, ipc_index.slot, packet_end);
1205 ready_len -= 1;
1206 if (packet_is_empty) continue :state .pending;
1207 },
1078 }1208 }
1079
1080 serialized_len += nodes_len;
1081 }
1082
1083 // Save a copy in case any pipes are empty on the next update.
1084 @memcpy(serialized_buffer.parents_copy[0..serialized_len], serialized_buffer.parents[0..serialized_len]);
1085 @memcpy(serialized_buffer.storage_copy[0..serialized_len], serialized_buffer.storage[0..serialized_len]);
1086 @memcpy(ipc_metadata_fds_copy[0..ipc_metadata_len], ipc_metadata_fds[0..ipc_metadata_len]);
1087 @memcpy(ipc_metadata_copy[0..ipc_metadata_len], ipc_metadata[0..ipc_metadata_len]);
1088
1089 return serialized_len;
1090}
1091
1092fn copyRoot(dest: *Node.Storage, src: *align(1) Node.Storage) void {
1093 dest.* = .{
1094 .completed_count = src.completed_count,
1095 .estimated_total_count = src.estimated_total_count,
1096 .name = if (src.name[0] == 0) dest.name else src.name,
1097 };
1098}
1099
1100fn findOld(
1101 ipc_fd: Io.File.Handle,
1102 old_metadata_fds: []Fd,
1103 old_metadata: []SavedMetadata,
1104) ?*SavedMetadata {
1105 for (old_metadata_fds, old_metadata) |fd, *m| {
1106 if (fd.get() == ipc_fd)
1107 return m;
1108 }1209 }
1109 return null;1210 serialized_buffer.ipc_end = serialized_len;
1110}1211
11111212 // Ignore data from unused pipes. This ensures that if a child process exists we will
1112fn useSavedIpcData(1213 // eventually see `EndOfStream` and close the pipe.
1113 start_serialized_len: usize,1214 if (ready_len > 0) for (
1114 serialized_buffer: *Serialized.Buffer,1215 &serialized_buffer.ipc_data,
1115 main_storage: *Node.Storage,1216 &serialized_buffer.ipc_buffers,
1116 main_index: usize,1217 &serialized_buffer.ipc_vecs,
1117 opt_saved_metadata: ?*SavedMetadata,1218 0..,
1118 remaining_read_trash_bytes: u16,1219 ) |*ipc_data, *ipc_buffer, *ipc_vec, ipc_slot| switch (ipc_data.state) {
1119 fd: Io.File.Handle,1220 .unused, .pending => {},
1120) usize {1221 .ready => {
1121 const parents_copy = &serialized_buffer.parents_copy;1222 _, const packet_end = ipc_data.findLastPacket(ipc_buffer);
1122 const storage_copy = &serialized_buffer.storage_copy;1223 ipc_data.rebase(ipc_buffer, ipc_vec, batch, @intCast(ipc_slot), packet_end);
1123 const ipc_metadata_fds = &serialized_buffer.ipc_metadata_fds;1224 ready_len -= 1;
1124 const ipc_metadata = &serialized_buffer.ipc_metadata;1225 },
1125
1126 const saved_metadata = opt_saved_metadata orelse {
1127 main_storage.completed_count = 0;
1128 main_storage.estimated_total_count = 0;
1129 if (remaining_read_trash_bytes > 0) {
1130 ipc_metadata_fds[ipc_metadata_len] = Fd.init(fd);
1131 ipc_metadata[ipc_metadata_len] = .{
1132 .remaining_read_trash_bytes = remaining_read_trash_bytes,
1133 .start_index = @intCast(start_serialized_len),
1134 .nodes_len = 0,
1135 .main_index = @intCast(main_index),
1136 };
1137 ipc_metadata_len += 1;
1138 }
1139 return start_serialized_len;
1140 };1226 };
1227 assert(ready_len == 0);
11411228
1142 const start_index = saved_metadata.start_index;1229 return .{
1143 const nodes_len = @min(saved_metadata.nodes_len, serialized_buffer.storage.len - start_serialized_len);1230 .parents = serialized_buffer.parents[0..serialized_len],
1144 const old_main_index = saved_metadata.main_index;1231 .storage = serialized_buffer.storage[0..serialized_len],
1145
1146 ipc_metadata_fds[ipc_metadata_len] = Fd.init(fd);
1147 ipc_metadata[ipc_metadata_len] = .{
1148 .remaining_read_trash_bytes = remaining_read_trash_bytes,
1149 .start_index = @intCast(start_serialized_len),
1150 .nodes_len = nodes_len,
1151 .main_index = @intCast(main_index),
1152 };1232 };
1153 ipc_metadata_len += 1;
1154
1155 const parents = parents_copy[start_index..][0..nodes_len];
1156 const storage = storage_copy[start_index..][0..nodes_len];
1157
1158 copyRoot(main_storage, &storage_copy[old_main_index]);
1159
1160 @memcpy(serialized_buffer.storage[start_serialized_len..][0..storage.len], storage);
1161
1162 for (serialized_buffer.parents[start_serialized_len..][0..parents.len], parents) |*dest, p| {
1163 dest.* = switch (p) {
1164 .none, .unused => .none,
1165 _ => |prev| d: {
1166 if (@intFromEnum(prev) == old_main_index) {
1167 break :d @enumFromInt(main_index);
1168 } else if (@intFromEnum(prev) > nodes_len) {
1169 break :d .none;
1170 } else {
1171 break :d @enumFromInt(@intFromEnum(prev) - start_index + start_serialized_len);
1172 }
1173 },
1174 };
1175 }
1176
1177 return start_serialized_len + storage.len;
1178}1233}
11791234
1180fn computeRedraw(serialized_buffer: *Serialized.Buffer) struct { []u8, usize } {1235fn computeRedraw(io: Io, serialized_buffer: *Serialized.Buffer) !struct { []u8, usize } {
1181 const serialized = serialize(serialized_buffer);1236 if (global_progress.rows == 0 or global_progress.cols == 0) return error.WindowTooSmall;
1237
1238 const serialized = try serialize(io, serialized_buffer);
11821239
1183 // Now we can analyze our copy of the graph without atomics, reconstructing1240 // Now we can analyze our copy of the graph without atomics, reconstructing
1184 // children lists which do not exist in the canonical data. These are1241 // children lists which do not exist in the canonical data. These are
...@@ -1413,9 +1470,7 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool {...@@ -1413,9 +1470,7 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool {
1413 return nl_n + 2 < p.rows;1470 return nl_n + 2 < p.rows;
1414}1471}
14151472
1416var remaining_write_trash_bytes: usize = 0;1473fn writeIpc(writer: *Io.Writer, serialized: Serialized) Io.Writer.Error!void {
1417
1418fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!void {
1419 // Byteswap if necessary to ensure little endian over the pipe. This is1474 // Byteswap if necessary to ensure little endian over the pipe. This is
1420 // needed because the parent or child process might be running in qemu.1475 // needed because the parent or child process might be running in qemu.
1421 if (is_big_endian) for (serialized.storage) |*s| s.byteSwap();1476 if (is_big_endian) for (serialized.storage) |*s| s.byteSwap();
...@@ -1426,62 +1481,8 @@ fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!voi...@@ -1426,62 +1481,8 @@ fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!voi
1426 const storage = std.mem.sliceAsBytes(serialized.storage);1481 const storage = std.mem.sliceAsBytes(serialized.storage);
1427 const parents = std.mem.sliceAsBytes(serialized.parents);1482 const parents = std.mem.sliceAsBytes(serialized.parents);
14281483
1429 var vecs: [3][]const u8 = .{ header, storage, parents };1484 var vec = [3][]const u8{ header, storage, parents };
14301485 try writer.writeVecAll(&vec);
1431 // Ensures the packet can fit in the pipe buffer.
1432 const upper_bound_msg_len = 1 + node_storage_buffer_len * @sizeOf(Node.Storage) +
1433 node_storage_buffer_len * @sizeOf(Node.OptionalIndex);
1434 comptime assert(upper_bound_msg_len <= 4096);
1435
1436 while (remaining_write_trash_bytes > 0) {
1437 // We do this in a separate write call to give a better chance for the
1438 // writev below to be in a single packet.
1439 const n = @min(parents.len, remaining_write_trash_bytes);
1440 if (file.writeStreaming(io, &.{}, &.{parents[0..n]}, 1)) |written| {
1441 remaining_write_trash_bytes -= written;
1442 continue;
1443 } else |err| switch (err) {
1444 error.WouldBlock => return,
1445 error.BrokenPipe => return error.BrokenPipe,
1446 else => |e| {
1447 std.log.debug("failed to send progress to parent process: {t}", .{e});
1448 return error.BrokenPipe;
1449 },
1450 }
1451 }
1452
1453 // If this write would block we do not want to keep trying, but we need to
1454 // know if a partial message was written.
1455 if (writevNonblock(io, file, &vecs)) |written| {
1456 const total = header.len + storage.len + parents.len;
1457 if (written < total) {
1458 remaining_write_trash_bytes = total - written;
1459 }
1460 } else |err| switch (err) {
1461 error.WouldBlock => {},
1462 error.BrokenPipe => return error.BrokenPipe,
1463 else => |e| {
1464 std.log.debug("failed to send progress to parent process: {t}", .{e});
1465 return error.BrokenPipe;
1466 },
1467 }
1468}
1469
1470fn writevNonblock(io: Io, file: Io.File, iov: [][]const u8) Io.File.Writer.Error!usize {
1471 var iov_index: usize = 0;
1472 var written: usize = 0;
1473 var total_written: usize = 0;
1474 while (true) {
1475 while (if (iov_index < iov.len)
1476 written >= iov[iov_index].len
1477 else
1478 return total_written) : (iov_index += 1) written -= iov[iov_index].len;
1479 iov[iov_index].ptr += written;
1480 iov[iov_index].len -= written;
1481 written = try file.writeStreaming(io, &.{}, iov, 1);
1482 if (written == 0) return total_written;
1483 total_written += written;
1484 }
1485}1486}
14861487
1487fn maybeUpdateSize(io: Io, resize_flag: bool) !void {1488fn maybeUpdateSize(io: Io, resize_flag: bool) !void {
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/linux.zig+18
...@@ -1848,6 +1848,24 @@ pub const F = struct {...@@ -1848,6 +1848,24 @@ pub const F = struct {
1848 pub const RDLCK = if (is_sparc) 1 else 0;1848 pub const RDLCK = if (is_sparc) 1 else 0;
1849 pub const WRLCK = if (is_sparc) 2 else 1;1849 pub const WRLCK = if (is_sparc) 2 else 1;
1850 pub const UNLCK = if (is_sparc) 3 else 2;1850 pub const UNLCK = if (is_sparc) 3 else 2;
1851
1852 pub const LINUX_SPECIFIC_BASE = 1024;
1853
1854 pub const SETLEASE = LINUX_SPECIFIC_BASE + 0;
1855 pub const GETLEASE = LINUX_SPECIFIC_BASE + 1;
1856 pub const NOTIFY = LINUX_SPECIFIC_BASE + 2;
1857 pub const DUPFD_QUERY = LINUX_SPECIFIC_BASE + 3;
1858 pub const CREATED_QUERY = LINUX_SPECIFIC_BASE + 4;
1859 pub const CANCELLK = LINUX_SPECIFIC_BASE + 5;
1860 pub const DUPFD_CLOEXEC = LINUX_SPECIFIC_BASE + 6;
1861 pub const SETPIPE_SZ = LINUX_SPECIFIC_BASE + 7;
1862 pub const GETPIPE_SZ = LINUX_SPECIFIC_BASE + 8;
1863 pub const ADD_SEALS = LINUX_SPECIFIC_BASE + 9;
1864 pub const GET_SEALS = LINUX_SPECIFIC_BASE + 10;
1865 pub const GET_RW_HINT = LINUX_SPECIFIC_BASE + 11;
1866 pub const SET_RW_HINT = LINUX_SPECIFIC_BASE + 12;
1867 pub const GET_FILE_RW_HINT = LINUX_SPECIFIC_BASE + 13;
1868 pub const SET_FILE_RW_HINT = LINUX_SPECIFIC_BASE + 14;
1851};1869};
18521870
1853pub const F_OWNER = enum(i32) {1871pub const F_OWNER = enum(i32) {
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 },