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 {
14981498 defer dir.close(io);
14991499
15001500 var wf = b.addWriteFiles();
1501 b.step("test-docs", "Test code snippets from the docs").dependOn(&wf.step);
15011502
15021503 var it = dir.iterateAssumeFirstIteration();
15031504 while (it.next(io) catch @panic("failed to read dir")) |entry| {
lib/std/Build/Step.zig+20-32
......@@ -386,10 +386,14 @@ pub const ZigProcess = struct {
386386 child: std.process.Child,
387387 multi_reader_buffer: Io.File.MultiReader.Buffer(2),
388388 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
391391 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
393397 pub fn deinit(zp: *ZigProcess, io: Io) void {
394398 zp.child.kill(io);
395399 zp.multi_reader.deinit();
......@@ -417,7 +421,14 @@ pub fn evalZigProcess(
417421
418422 if (s.getZigProcess()) |zp| update: {
419423 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);
421432 const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) {
422433 error.BrokenPipe, error.EndOfStream => |reason| {
423434 std.log.info("{s} restart required: {t}", .{ argv[0], reason });
......@@ -426,7 +437,7 @@ pub fn evalZigProcess(
426437 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
427438 };
428439 _ = term;
429 s.clearZigProcess(gpa);
440 exited = true;
430441 break :update;
431442 },
432443 else => |e| return e,
......@@ -442,7 +453,7 @@ pub fn evalZigProcess(
442453 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
443454 };
444455 s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
445 s.clearZigProcess(gpa);
456 exited = true;
446457 try handleChildProcessTerm(s, term);
447458 return error.MakeFailed;
448459 }
......@@ -467,19 +478,16 @@ pub fn evalZigProcess(
467478 .progress_node = prog_node,
468479 }) 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 };
476481 zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{
477482 zp.child.stdout.?, zp.child.stderr.?,
478483 });
479 if (watch) s.setZigProcess(zp);
484 if (watch) s.cast(Compile).?.zig_process = zp;
480485 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
484492 if (!watch) {
485493 // Send EOF to stdin.
......@@ -670,26 +678,6 @@ pub fn getZigProcess(s: *Step) ?*ZigProcess {
670678 };
671679}
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
693681fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {
694682 const header: std.zig.Client.Message.Header = .{
695683 .tag = tag,
lib/std/Build/Watch.zig+7-11
......@@ -366,15 +366,7 @@ const Os = switch (builtin.os.tag) {
366366 .MaximumLength = @intCast(path_len_bytes),
367367 .Buffer = @constCast(sub_path_w.span().ptr),
368368 };
369 var attr = windows.OBJECT_ATTRIBUTES{
370 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
371 .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd,
372 .Attributes = .{},
373 .ObjectName = &nt_name,
374 .SecurityDescriptor = null,
375 .SecurityQualityOfService = null,
376 };
377 var io: windows.IO_STATUS_BLOCK = undefined;
369 var iosb: windows.IO_STATUS_BLOCK = undefined;
378370
379371 switch (windows.ntdll.NtCreateFile(
380372 &dir_handle,
......@@ -385,14 +377,18 @@ const Os = switch (builtin.os.tag) {
385377 .STANDARD = .{ .SYNCHRONIZE = true },
386378 .GENERIC = .{ .READ = true },
387379 },
388 &attr,
389 &io,
380 &.{
381 .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd,
382 .ObjectName = &nt_name,
383 },
384 &iosb,
390385 null,
391386 .{},
392387 .VALID_FLAGS,
393388 .OPEN,
394389 .{
395390 .DIRECTORY_FILE = true,
391 .IO = .ASYNCHRONOUS,
396392 .OPEN_FOR_BACKUP_INTENT = true,
397393 },
398394 null,
lib/std/Io/Threaded.zig+416-367
......@@ -19,7 +19,7 @@ const Alignment = std.mem.Alignment;
1919const assert = std.debug.assert;
2020const posix = std.posix;
2121const windows = std.os.windows;
22const ws2_32 = std.os.windows.ws2_32;
22const ws2_32 = windows.ws2_32;
2323
2424/// Thread-safe.
2525///
......@@ -76,6 +76,7 @@ environ: Environ,
7676
7777null_file: NullFile = .{},
7878random_file: RandomFile = .{},
79pipe_file: PipeFile = .{},
7980
8081csprng: Csprng = .{},
8182
......@@ -121,7 +122,7 @@ pub const Argv0 = switch (native_os) {
121122
122123const Environ = struct {
123124 /// Unmodified data directly from the OS.
124 process_environ: process.Environ = .empty,
125 process_environ: process.Environ,
125126 /// Protected by `mutex`. Determines whether the other fields have been
126127 /// memoized based on `process_environ`.
127128 initialized: bool = false,
......@@ -131,13 +132,15 @@ const Environ = struct {
131132 /// Protected by `mutex`. Memoized based on `process_environ`.
132133 string: String = .{},
133134 /// ZIG_PROGRESS
134 zig_progress_handle: std.Progress.ParentFileError!u31 = error.EnvironmentVariableMissing,
135 zig_progress_file: std.Progress.ParentFileError!File = error.EnvironmentVariableMissing,
135136 /// Protected by `mutex`. Tracks the problem, if any, that occurred when
136137 /// trying to scan environment variables.
137138 ///
138139 /// Errors are only possible on WASI.
139140 err: ?Error = null,
140141
142 pub const empty: Environ = .{ .process_environ = .empty };
143
141144 pub const Error = Allocator.Error || Io.UnexpectedError;
142145
143146 pub const Exist = struct {
......@@ -193,6 +196,24 @@ pub const RandomFile = switch (native_os) {
193196 },
194197};
195198
199pub const PipeFile = switch (native_os) {
200 .windows => struct {
201 handle: ?windows.HANDLE = null,
202
203 fn deinit(this: *@This()) void {
204 if (this.handle) |handle| {
205 windows.CloseHandle(handle);
206 this.handle = null;
207 }
208 }
209 },
210 else => struct {
211 fn deinit(this: @This()) void {
212 _ = this;
213 }
214 },
215};
216
196217pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
197218 unknown = 0,
198219 _,
......@@ -1498,7 +1519,9 @@ pub const init_single_threaded: Threaded = .{
14981519 .old_sig_pipe = undefined,
14991520 .have_signal_handler = false,
15001521 .argv0 = .empty,
1501 .environ = .{},
1522 .environ = .{ .process_environ = .{
1523 .block = if (process.Environ.Block == process.Environ.GlobalBlock) .global else .empty,
1524 } },
15021525 .worker_threads = .init(null),
15031526 .disable_memory_mapping = false,
15041527};
......@@ -1533,6 +1556,7 @@ pub fn deinit(t: *Threaded) void {
15331556 }
15341557 t.null_file.deinit();
15351558 t.random_file.deinit();
1559 t.pipe_file.deinit();
15361560 t.* = undefined;
15371561}
15381562
......@@ -1576,14 +1600,7 @@ fn worker(t: *Threaded) void {
15761600 },
15771601 },
15781602 },
1579 &.{
1580 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
1581 .RootDirectory = null,
1582 .ObjectName = null,
1583 .Attributes = .{},
1584 .SecurityDescriptor = null,
1585 .SecurityQualityOfService = null,
1586 },
1603 &.{ .ObjectName = null },
15871604 &windows.teb().ClientId,
15881605 ) == .SUCCESS);
15891606 }
......@@ -2595,8 +2612,7 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void {
25952612 // opportunity to find additional ready operations.
25962613 break :t 0;
25972614 }
2598 const max_poll_ms = std.math.maxInt(i32);
2599 break :t max_poll_ms;
2615 break :t std.math.maxInt(i32);
26002616 };
26012617 const syscall = try Syscall.start();
26022618 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
27162732 break :allocation allocation;
27172733 };
27182734 @memcpy(slice[0..poll_buffer_len], storage.slice);
2735 storage.slice = slice;
27192736 }
27202737 storage.slice[len] = .{
27212738 .fd = file.handle,
......@@ -2769,9 +2786,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout
27692786 }
27702787 const d = deadline orelse break :t -1;
27712788 const duration = d.durationFromNow(t_io);
2772 if (duration.raw.nanoseconds <= 0) return error.Timeout;
2773 const max_poll_ms = std.math.maxInt(i32);
2774 break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds()));
2789 break :t @min(@max(0, duration.raw.toMilliseconds()), std.math.maxInt(i32));
27752790 };
27762791 const syscall = try Syscall.start();
27772792 const rc = posix.system.poll(&poll_buffer, poll_storage.len, timeout_ms);
......@@ -3379,12 +3394,8 @@ fn dirCreateDirPathOpenWindows(
33793394 },
33803395 },
33813396 &.{
3382 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
33833397 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
3384 .Attributes = .{},
33853398 .ObjectName = &nt_name,
3386 .SecurityDescriptor = null,
3387 .SecurityQualityOfService = null,
33883399 },
33893400 &io_status_block,
33903401 null,
......@@ -4066,13 +4077,9 @@ fn dirAccessWindows(
40664077 .MaximumLength = path_len_bytes,
40674078 .Buffer = @constCast(sub_path_w.ptr),
40684079 };
4069 var attr: windows.OBJECT_ATTRIBUTES = .{
4070 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
4080 const attr: windows.OBJECT_ATTRIBUTES = .{
40714081 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
4072 .Attributes = .{},
40734082 .ObjectName = &nt_name,
4074 .SecurityDescriptor = null,
4075 .SecurityQualityOfService = null,
40764083 };
40774084 var basic_info: windows.FILE.BASIC_INFORMATION = undefined;
40784085 const syscall: Syscall = try .start();
......@@ -4288,14 +4295,8 @@ fn dirCreateFileWindows(
42884295 .Buffer = @constCast(sub_path_w.ptr),
42894296 };
42904297 const attr: windows.OBJECT_ATTRIBUTES = .{
4291 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
42924298 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
4293 .Attributes = .{
4294 .INHERIT = false,
4295 },
42964299 .ObjectName = &nt_name,
4297 .SecurityDescriptor = null,
4298 .SecurityQualityOfService = null,
42994300 };
43004301 const create_disposition: windows.FILE.CREATE_DISPOSITION = if (flags.exclusive)
43014302 .CREATE
......@@ -4908,17 +4909,6 @@ pub fn dirOpenFileWtf16(
49084909 .MaximumLength = path_len_bytes,
49094910 .Buffer = @constCast(sub_path_w.ptr),
49104911 };
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 };
49224912 var io_status_block: w.IO_STATUS_BLOCK = undefined;
49234913
49244914 // There are multiple kernel bugs being worked around with retries.
......@@ -4937,7 +4927,10 @@ pub fn dirOpenFileWtf16(
49374927 .WRITE = flags.isWrite(),
49384928 },
49394929 },
4940 &attr,
4930 &.{
4931 .RootDirectory = dir_handle,
4932 .ObjectName = &nt_name,
4933 },
49414934 &io_status_block,
49424935 null,
49434936 .{ .NORMAL = true },
......@@ -5305,12 +5298,8 @@ pub fn dirOpenDirWindows(
53055298 },
53065299 },
53075300 &.{
5308 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
53095301 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
5310 .Attributes = .{},
53115302 .ObjectName = &nt_name,
5312 .SecurityDescriptor = null,
5313 .SecurityQualityOfService = null,
53145303 },
53155304 &io_status_block,
53165305 null,
......@@ -6520,12 +6509,8 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
65206509 .SYNCHRONIZE = true,
65216510 } },
65226511 &.{
6523 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
65246512 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
6525 .Attributes = .{},
65266513 .ObjectName = &nt_name,
6527 .SecurityDescriptor = null,
6528 .SecurityQualityOfService = null,
65296514 },
65306515 &io_status_block,
65316516 null,
......@@ -6534,6 +6519,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
65346519 .OPEN,
65356520 .{
65366521 .DIRECTORY_FILE = remove_dir,
6522 .IO = .SYNCHRONOUS_NONALERT,
65376523 .NON_DIRECTORY_FILE = !remove_dir,
65386524 .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead?
65396525 },
......@@ -7345,14 +7331,8 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink
73457331 .Buffer = @constCast(sub_path_w.ptr),
73467332 };
73477333 const attr: windows.OBJECT_ATTRIBUTES = .{
7348 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
73497334 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
7350 .Attributes = .{
7351 .INHERIT = false,
7352 },
73537335 .ObjectName = &nt_name,
7354 .SecurityDescriptor = null,
7355 .SecurityQualityOfService = null,
73567336 };
73577337 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
73587338 var result_handle: windows.HANDLE = undefined;
......@@ -7909,24 +7889,19 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void {
79097889 const t: *Threaded = @ptrCast(@alignCast(userdata));
79107890 _ = t;
79117891
7892 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
79127893 const syscall: Syscall = try .start();
79137894 while (true) {
7914 if (windows.kernel32.FlushFileBuffers(file.handle) != 0) {
7915 return syscall.finish();
7916 }
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 => {
7895 switch (windows.ntdll.NtFlushBuffersFile(file.handle, &io_status_block)) {
7896 .SUCCESS => break syscall.finish(),
7897 .CANCELLED => {
79237898 try syscall.checkCancel();
79247899 continue;
79257900 },
7926 else => |err| {
7927 syscall.finish();
7928 return windows.unexpectedError(err);
7929 },
7901 .INVALID_HANDLE => unreachable,
7902 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time
7903 .UNEXPECTED_NETWORK_ERROR => return syscall.fail(error.InputOutput),
7904 else => |status| return syscall.unexpectedNtstatus(status),
79307905 }
79317906 }
79327907}
......@@ -14446,7 +14421,10 @@ const WindowsEnvironStrings = struct {
1444614421 PATHEXT: ?[:0]const u16 = null,
1444714422
1444814423 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
1445114429 var result: WindowsEnvironStrings = .{};
1445214430 var i: usize = 0;
......@@ -14472,7 +14450,7 @@ const WindowsEnvironStrings = struct {
1447214450
1447314451 inline for (@typeInfo(WindowsEnvironStrings).@"struct".fields) |field| {
1447414452 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;
1447614454 }
1447714455 }
1447814456
......@@ -14491,29 +14469,46 @@ fn scanEnviron(t: *Threaded) void {
1449114469 // This value expires with any call that modifies the environment,
1449214470 // which is outside of this Io implementation's control, so references
1449314471 // 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
1449614477 var i: usize = 0;
1449714478 while (ptr[i] != 0) {
14498 const key_start = i;
1449914479
1450014480 // There are some special environment variables that start with =,
1450114481 // so we need a special case to not treat = as a key/value separator
1450214482 // if it's the first character.
1450314483 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
14504 if (ptr[key_start] == '=') i += 1;
14505
14484 const key_start = i;
14485 if (ptr[i] == '=') i += 1;
1450614486 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
1450714487 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' })) {
1450914495 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' })) {
1451114497 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 };
1451214510 }
1451314511 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
1451714512 }
1451814513 } else if (native_os == .wasi and !builtin.link_libc) {
1451914514 var environ_count: usize = undefined;
......@@ -14559,22 +14554,28 @@ fn scanEnviron(t: *Threaded) void {
1455914554 comptime assert(@sizeOf(Environ.String) == 0);
1456014555 }
1456114556 } else {
14562 for (t.environ.process_environ.block) |opt_line| {
14563 const line = opt_line.?;
14564 var line_i: usize = 0;
14565 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
14566 const key = line[0..line_i];
14557 for (t.environ.process_environ.block.slice) |opt_entry| {
14558 const entry = opt_entry.?;
14559 var entry_i: usize = 0;
14560 while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {}
14561 const key = entry[0..entry_i];
1456714562
14568 var end_i: usize = line_i;
14569 while (line[end_i] != 0) : (end_i += 1) {}
14570 const value = line[line_i + 1 .. end_i :0];
14563 var end_i: usize = entry_i;
14564 while (entry[end_i] != 0) : (end_i += 1) {}
14565 const value = entry[entry_i + 1 .. end_i :0];
1457114566
1457214567 if (std.mem.eql(u8, key, "NO_COLOR")) {
1457314568 t.environ.exist.NO_COLOR = true;
1457414569 } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) {
1457514570 t.environ.exist.CLICOLOR_FORCE = true;
1457614571 } 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 };
1457814579 } else inline for (@typeInfo(Environ.String).@"struct".fields) |field| {
1457914580 if (std.mem.eql(u8, key, field.name)) @field(t.environ.string, field.name) = value;
1458014581 }
......@@ -14597,19 +14598,17 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
1459714598 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);
1459814599 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: {
1460114602 const prog_fd: i32 = -1;
14602 if (options.environ_map) |environ_map| {
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, .{
14603 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
1460814604 .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 });
1461014609 };
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);
1461314612}
1461414613
1461514614fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError {
......@@ -14679,16 +14678,17 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1467914678 const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore);
1468014679 const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined;
1468114680
14682 const prog_pipe: [2]posix.fd_t = p: {
14683 if (options.progress_node.index == .none) {
14684 break :p .{ -1, -1 };
14685 } else {
14686 // We use CLOEXEC for the same reason as in `pipe_flags`.
14687 break :p try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true });
14688 }
14689 };
14681 const prog_pipe: [2]posix.fd_t = if (options.progress_node.index != .none)
14682 // We use CLOEXEC for the same reason as in `pipe_flags`.
14683 try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true })
14684 else
14685 .{ -1, -1 };
1469014686 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
1469214692 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
1469314693 defer arena_allocator.deinit();
1469414694 const arena = arena_allocator.allocator();
......@@ -14708,16 +14708,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1470814708 const prog_fileno = 3;
1470914709 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: {
1471214712 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
14713 if (options.environ_map) |environ_map| {
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, .{
14713 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
1471914714 .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 });
1472114719 };
1472214720
1472314721 // 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
1480014798 }
1480114799 }
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);
1480414802 forkBail(ep1, err);
1480514803 }
1480614804
......@@ -14814,8 +14812,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1481414812 if (options.stderr == .pipe) posix.close(stderr_pipe[1]);
1481514813
1481614814 if (prog_pipe[1] != -1) posix.close(prog_pipe[1]);
14817
14818 options.progress_node.setIpcFd(prog_pipe[0]);
14815 options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });
1481914816
1482014817 return .{
1482114818 .pid = pid,
......@@ -14938,42 +14935,44 @@ fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT
1493814935 // some rare edge cases where our process handle no longer has the
1493914936 // PROCESS_TERMINATE access right, so let's do another check to make
1494014937 // sure the process is really no longer running:
14941 windows.WaitForSingleObjectEx(handle, 0, false) catch return error.AccessDenied;
14942 return error.AlreadyTerminated;
14938 const minimal_timeout: windows.LARGE_INTEGER = -1;
14939 switch (windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &minimal_timeout)) {
14940 .SUCCESS => return error.AlreadyTerminated,
14941 else => return error.AccessDenied,
14942 }
1494314943 },
1494414944 else => |err| return windows.unexpectedError(err),
1494514945 }
1494614946 }
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);
1494814949 childCleanupWindows(child);
1494914950}
1495014951
1495114952fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term {
1495214953 const handle = child.id.?;
1495314954
14954 const syscall: Syscall = try .start();
14955 while (true) switch (windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE)) {
14956 windows.WAIT_OBJECT_0 => break syscall.finish(),
14957 windows.WAIT_ABANDONED, windows.WAIT_TIMEOUT => {
14958 try syscall.checkCancel();
14955 const alertable_syscall: AlertableSyscall = try .start();
14956 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
14957 while (true) switch (windows.ntdll.NtWaitForSingleObject(handle, windows.TRUE, &infinite_timeout)) {
14958 windows.NTSTATUS.WAIT_0 => break alertable_syscall.finish(),
14959 .USER_APC, .ALERTED, .TIMEOUT => {
14960 try alertable_syscall.checkCancel();
1495914961 continue;
1496014962 },
14961 windows.WAIT_FAILED => {
14962 syscall.finish();
14963 switch (windows.GetLastError()) {
14964 else => |err| return windows.unexpectedError(err),
14965 }
14966 },
14967 else => return syscall.fail(error.Unexpected),
14963 else => |status| return alertable_syscall.unexpectedNtstatus(status),
1496814964 };
1496914965
14970 const term: process.Child.Term = x: {
14971 var exit_code: windows.DWORD = undefined;
14972 if (windows.kernel32.GetExitCodeProcess(handle, &exit_code) == 0) {
14973 break :x .{ .unknown = 0 };
14974 } else {
14975 break :x .{ .exited = @as(u8, @truncate(exit_code)) };
14976 }
14966 var info: windows.PROCESS_BASIC_INFORMATION = undefined;
14967 const term: process.Child.Term = switch (windows.ntdll.NtQueryInformationProcess(
14968 handle,
14969 .BasicInformation,
14970 &info,
14971 @sizeOf(windows.PROCESS_BASIC_INFORMATION),
14972 null,
14973 )) {
14974 .SUCCESS => .{ .exited = @as(u8, @truncate(@intFromEnum(info.ExitStatus))) },
14975 else => .{ .unknown = 0 },
1497714976 };
1497814977
1497914978 childCleanupWindows(child);
......@@ -15236,88 +15235,71 @@ fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32
1523615235fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
1523715236 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
1524515238 const any_ignore =
1524615239 options.stdin == .ignore or
1524715240 options.stdout == .ignore or
1524815241 options.stderr == .ignore;
15249
15250 const nul_handle = if (any_ignore) try getNulHandle(t) else undefined;
15251
15252 var g_hChildStd_IN_Rd: ?windows.HANDLE = null;
15253 var g_hChildStd_IN_Wr: ?windows.HANDLE = null;
15254 switch (options.stdin) {
15255 .pipe => {
15256 try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr);
15257 },
15258 .ignore => {
15259 g_hChildStd_IN_Rd = nul_handle;
15260 },
15261 .inherit => {
15262 g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE) catch null;
15263 },
15264 .close => {
15265 g_hChildStd_IN_Rd = null;
15266 },
15267 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
15268 }
15269 errdefer if (options.stdin == .pipe) {
15270 windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr);
15271 };
15272
15273 var g_hChildStd_OUT_Rd: ?windows.HANDLE = null;
15274 var g_hChildStd_OUT_Wr: ?windows.HANDLE = null;
15275 switch (options.stdout) {
15276 .pipe => {
15277 try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr);
15278 },
15279 .ignore => {
15280 g_hChildStd_OUT_Wr = nul_handle;
15281 },
15282 .inherit => {
15283 g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) catch null;
15284 },
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 };
15242 const nul_handle = if (any_ignore) try getNulDevice(t) else undefined;
15243
15244 const any_inherit =
15245 options.stdin == .inherit or
15246 options.stdout == .inherit or
15247 options.stderr == .inherit;
15248 const peb = if (any_inherit) windows.peb() else undefined;
15249
15250 const stdin_pipe = if (options.stdin == .pipe) try t.windowsCreatePipe(.{
15251 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15252 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15253 .outbound = true,
15254 }) else undefined;
15255 errdefer if (options.stdin == .pipe) for (stdin_pipe) |handle| windows.CloseHandle(handle);
15256
15257 const stdout_pipe = if (options.stdout == .pipe) try t.windowsCreatePipe(.{
15258 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15259 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15260 .inbound = true,
15261 }) else undefined;
15262 errdefer if (options.stdout == .pipe) for (stdout_pipe) |handle| windows.CloseHandle(handle);
15263
15264 const stderr_pipe = if (options.stderr == .pipe) try t.windowsCreatePipe(.{
15265 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15266 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15267 .inbound = true,
15268 }) else undefined;
15269 errdefer if (options.stderr == .pipe) for (stderr_pipe) |handle| windows.CloseHandle(handle);
15270
15271 const prog_pipe = if (options.progress_node.index != .none) try t.windowsCreatePipe(.{
15272 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15273 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .ASYNCHRONOUS } },
15274 .inbound = true,
15275 .quota = std.Progress.max_packet_len * 2,
15276 }) else undefined;
15277 errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle);
1531415278
1531515279 var siStartInfo: windows.STARTUPINFOW = .{
1531615280 .cb = @sizeOf(windows.STARTUPINFOW),
15317 .hStdError = g_hChildStd_ERR_Wr,
15318 .hStdOutput = g_hChildStd_OUT_Wr,
15319 .hStdInput = g_hChildStd_IN_Rd,
1532015281 .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
1532215304 .lpReserved = null,
1532315305 .lpDesktop = null,
......@@ -15363,8 +15345,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1536315345 };
1536415346 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;
15367 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
15348 const env_block = env_block: {
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
1536915361 const app_name_wtf8 = options.argv[0];
1537015362 const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8);
......@@ -15439,7 +15431,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1543915431 &app_buf,
1544015432 PATHEXT,
1544115433 &cmd_line_cache,
15442 envp_ptr,
15434 env_block,
1544315435 cwd_w_ptr,
1544415436 flags,
1544515437 &siStartInfo,
......@@ -15474,7 +15466,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1547415466 &app_buf,
1547515467 PATHEXT,
1547615468 &cmd_line_cache,
15477 envp_ptr,
15469 env_block,
1547815470 cwd_w_ptr,
1547915471 flags,
1548015472 &siStartInfo,
......@@ -15494,21 +15486,40 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1549415486 };
1549515487 }
1549615488
15497 if (options.stdin == .pipe) windows.CloseHandle(g_hChildStd_IN_Rd.?);
15498 if (options.stderr == .pipe) windows.CloseHandle(g_hChildStd_ERR_Wr.?);
15499 if (options.stdout == .pipe) windows.CloseHandle(g_hChildStd_OUT_Wr.?);
15489 if (options.progress_node.index != .none) {
15490 windows.CloseHandle(prog_pipe[1]);
15491 options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });
15492 }
1550015493
1550115494 return .{
1550215495 .id = piProcInfo.hProcess,
1550315496 .thread_handle = piProcInfo.hThread,
15504 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = false } } else null,
15505 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
15506 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
15497 .stdin = stdin: switch (options.stdin) {
15498 .pipe => {
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 },
1550715518 .request_resource_usage_statistics = options.request_resource_usage_statistics,
1550815519 };
1550915520}
1551015521
15511fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
15522fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1551215523 {
1551315524 mutexLock(&t.mutex);
1551415525 defer mutexUnlock(&t.mutex);
......@@ -15516,12 +15527,6 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1551615527 }
1551715528
1551815529 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 };
1552515530 var fresh_handle: windows.HANDLE = undefined;
1552615531 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1552715532 var syscall: Syscall = try .start();
......@@ -15532,12 +15537,11 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1553215537 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } },
1553315538 },
1553415539 &.{
15535 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
15536 .RootDirectory = null,
15537 .ObjectName = &nt_name,
15538 .Attributes = .{},
15539 .SecurityDescriptor = null,
15540 .SecurityQualityOfService = null,
15540 .ObjectName = @constCast(&windows.UNICODE_STRING{
15541 .Length = @sizeOf(@TypeOf(device_path)),
15542 .MaximumLength = 0,
15543 .Buffer = @constCast(&device_path),
15544 }),
1554115545 },
1554215546 &io_status_block,
1554315547 .VALID_FLAGS,
......@@ -15564,7 +15568,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1556415568 };
1556515569}
1556615570
15567fn getNulHandle(t: *Threaded) !windows.HANDLE {
15571fn getNulDevice(t: *Threaded) !windows.HANDLE {
1556815572 {
1556915573 mutexLock(&t.mutex);
1557015574 defer mutexUnlock(&t.mutex);
......@@ -15572,44 +15576,26 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE {
1557215576 }
1557315577
1557415578 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;
1559115579 var fresh_handle: windows.HANDLE = undefined;
15580 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1559215581 var syscall: Syscall = try .start();
15593 while (true) switch (windows.ntdll.NtCreateFile(
15582 while (true) switch (windows.ntdll.NtOpenFile(
1559415583 &fresh_handle,
1559515584 .{
1559615585 .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 }),
1559815595 },
15599 &attr,
1560015596 &io_status_block,
15601 null,
15602 .{ .NORMAL = true },
1560315597 .VALID_FLAGS,
15604 .OPEN,
15605 .{
15606 .DIRECTORY_FILE = false,
15607 .NON_DIRECTORY_FILE = true,
15608 .IO = .SYNCHRONOUS_NONALERT,
15609 .OPEN_REPARSE_POINT = false,
15610 },
15611 null,
15612 0,
15598 .{ .IO = .SYNCHRONOUS_NONALERT },
1561315599 )) {
1561415600 .SUCCESS => {
1561515601 syscall.finish();
......@@ -15623,6 +15609,64 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE {
1562315609 return fresh_handle;
1562415610 }
1562515611 },
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 },
1562615670 .DELETE_PENDING => {
1562715671 // This error means that there *was* a file in this location on
1562815672 // the file system, but it was deleted. However, the OS is not
......@@ -15669,7 +15713,7 @@ fn windowsCreateProcessPathExt(
1566915713 app_buf: *std.ArrayList(u16),
1567015714 pathext: [:0]const u16,
1567115715 cmd_line_cache: *WindowsCommandLineCache,
15672 envp_ptr: ?[*:0]const u16,
15716 env_block: ?process.Environ.WindowsBlock,
1567315717 cwd_ptr: ?[*:0]u16,
1567415718 flags: windows.CreateProcessFlags,
1567515719 lpStartupInfo: *windows.STARTUPINFOW,
......@@ -15846,7 +15890,7 @@ fn windowsCreateProcessPathExt(
1584615890 if (windowsCreateProcess(
1584715891 app_name_w.ptr,
1584815892 cmd_line_w.ptr,
15849 envp_ptr,
15893 env_block,
1585015894 cwd_ptr,
1585115895 flags,
1585215896 lpStartupInfo,
......@@ -15906,7 +15950,7 @@ fn windowsCreateProcessPathExt(
1590615950 else
1590715951 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)) |_| {
1591015954 return;
1591115955 } else |err| switch (err) {
1591215956 error.FileNotFound => continue,
......@@ -15930,7 +15974,7 @@ fn windowsCreateProcessPathExt(
1593015974fn windowsCreateProcess(
1593115975 app_name: [*:0]u16,
1593215976 cmd_line: [*:0]u16,
15933 env_ptr: ?[*:0]const u16,
15977 env_block: ?process.Environ.WindowsBlock,
1593415978 cwd_ptr: ?[*:0]u16,
1593515979 flags: windows.CreateProcessFlags,
1593615980 lpStartupInfo: *windows.STARTUPINFOW,
......@@ -15945,7 +15989,7 @@ fn windowsCreateProcess(
1594515989 null,
1594615990 windows.TRUE,
1594715991 flags,
15948 env_ptr,
15992 if (env_block) |block| block.slice.ptr else null,
1594915993 cwd_ptr,
1595015994 lpStartupInfo,
1595115995 lpProcessInformation,
......@@ -16466,11 +16510,11 @@ fn posixExecv(
1646616510 arg0_expand: process.ArgExpansion,
1646716511 file: [*:0]const u8,
1646816512 child_argv: [*:null]?[*:0]const u8,
16469 envp: [*:null]const ?[*:0]const u8,
16513 env_block: process.Environ.PosixBlock,
1647016514 PATH: []const u8,
1647116515) process.ReplaceError {
1647216516 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
1647516519 // Use of PATH_MAX here is valid as the path_buf will be passed
1647616520 // directly to the operating system in posixExecvPath.
......@@ -16498,7 +16542,7 @@ fn posixExecv(
1649816542 .expand => child_argv[0] = full_path,
1649916543 .no_expand => {},
1650016544 }
16501 err = posixExecvPath(full_path, child_argv, envp);
16545 err = posixExecvPath(full_path, child_argv, env_block);
1650216546 switch (err) {
1650316547 error.AccessDenied => seen_eacces = true,
1650416548 error.FileNotFound, error.NotDir => {},
......@@ -16513,10 +16557,10 @@ fn posixExecv(
1651316557pub fn posixExecvPath(
1651416558 path: [*:0]const u8,
1651516559 child_argv: [*:null]const ?[*:0]const u8,
16516 envp: [*:null]const ?[*:0]const u8,
16560 env_block: process.Environ.PosixBlock,
1651716561) process.ReplaceError {
1651816562 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))) {
1652016564 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
1652116565 .@"2BIG" => return error.SystemResources,
1652216566 .MFILE => return error.ProcessFdQuotaExceeded,
......@@ -16548,100 +16592,105 @@ pub fn posixExecvPath(
1654816592 }
1654916593}
1655016594
16551fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
16552 var rd_h: windows.HANDLE = undefined;
16553 var wr_h: windows.HANDLE = undefined;
16554 try windows.CreatePipe(&rd_h, &wr_h, sattr);
16555 errdefer windowsDestroyPipe(rd_h, wr_h);
16556 try windows.SetHandleInformation(wr_h, windows.HANDLE_FLAG_INHERIT, 0);
16557 rd.* = rd_h;
16558 wr.* = wr_h;
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;
16595pub const CreatePipeOptions = struct {
16596 server: End,
16597 client: End,
16598 inbound: bool = false,
16599 outbound: bool = false,
16600 maximum_instances: u32 = 1,
16601 quota: u32 = 4096,
16602 default_timeout: windows.LARGE_INTEGER = -120 * std.time.ns_per_s / 100,
1656816603
16569 // Anonymous pipes are built upon Named pipes.
16570 // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe
16571 // Asynchronous (overlapped) read and write operations are not supported by anonymous pipes.
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];
16604 pub const End = struct {
16605 attributes: windows.OBJECT_ATTRIBUTES.ATTRIBUTES = .{},
16606 mode: windows.FILE.MODE,
1658516607 };
16586
16587 // Create the read handle that can be used with overlapped IO ops.
16588 const read_handle = windows.kernel32.CreateNamedPipeW(
16589 pipe_path.ptr,
16590 windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED,
16591 windows.PIPE_TYPE_BYTE,
16592 1,
16593 4096,
16594 4096,
16595 0,
16596 sattr,
16597 );
16598 if (read_handle == windows.INVALID_HANDLE_VALUE) {
16599 switch (windows.GetLastError()) {
16600 else => |err| return windows.unexpectedError(err),
16601 }
16602 }
16603 errdefer posix.close(read_handle);
16604
16605 var sattr_copy = sattr.*;
16606 const write_handle = windows.kernel32.CreateFileW(
16607 pipe_path.ptr,
16608 .{ .GENERIC = .{ .WRITE = true } },
16609 0,
16610 &sattr_copy,
16611 windows.OPEN_EXISTING,
16612 @bitCast(windows.FILE.ATTRIBUTE{ .NORMAL = true }),
16613 null,
16614 );
16615 if (write_handle == windows.INVALID_HANDLE_VALUE) {
16616 switch (windows.GetLastError()) {
16617 else => |err| return windows.unexpectedError(err),
16618 }
16619 }
16620 errdefer posix.close(write_handle);
16621
16622 try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0);
16623
16624 rd.* = read_handle;
16625 wr.* = write_handle;
16608};
16609pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.HANDLE {
16610 const named_pipe_device = try t.getNamedPipeDevice();
16611 const server_handle = server_handle: {
16612 var handle: windows.HANDLE = undefined;
16613 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
16614 const syscall: Syscall = try .start();
16615 while (true) switch (windows.ntdll.NtCreateNamedPipeFile(
16616 &handle,
16617 .{
16618 .SPECIFIC = .{ .FILE_PIPE = .{
16619 .READ_DATA = options.inbound,
16620 .WRITE_DATA = options.outbound,
16621 .WRITE_ATTRIBUTES = true,
16622 } },
16623 .STANDARD = .{ .SYNCHRONIZE = true },
16624 },
16625 &.{
16626 .RootDirectory = named_pipe_device,
16627 .Attributes = options.server.attributes,
16628 },
16629 &io_status_block,
16630 .{ .READ = true, .WRITE = true },
16631 .CREATE,
16632 options.server.mode,
16633 .{ .TYPE = .BYTE_STREAM },
16634 .{ .MODE = .BYTE_STREAM },
16635 .{ .OPERATION = .QUEUE },
16636 options.maximum_instances,
16637 if (options.inbound) options.quota else 0,
16638 if (options.outbound) options.quota else 0,
16639 &options.default_timeout,
16640 )) {
16641 .SUCCESS => break syscall.finish(),
16642 .CANCELLED => {
16643 try syscall.checkCancel();
16644 continue;
16645 },
16646 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
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 };
1662616688}
1662716689
16628var pipe_name_counter = std.atomic.Value(u32).init(1);
16629
1663016690fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
1663116691 const t: *Threaded = @ptrCast(@alignCast(userdata));
16632
1663316692 t.scanEnviron();
16634
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 };
16693 return t.environ.zig_progress_file;
1664516694}
1664616695
1664716696pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {
......@@ -16737,7 +16786,7 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void {
1673716786 // despite the function being documented to always return TRUE
1673816787 // * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG
1673916788 // 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);
1674116790 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1674216791 var i: usize = 0;
1674316792 const syscall: Syscall = try .start();
lib/std/Io/Threaded/test.zig+10-6
......@@ -181,13 +181,17 @@ test "cancel blocked read from pipe" {
181181 var write_end: Io.File = undefined;
182182 switch (builtin.target.os.tag) {
183183 .wasi => return error.SkipZigTest,
184 .windows => try std.os.windows.CreatePipe(&read_end.handle, &write_end.handle, &.{
185 .nLength = @sizeOf(std.os.windows.SECURITY_ATTRIBUTES),
186 .lpSecurityDescriptor = null,
187 .bInheritHandle = std.os.windows.FALSE,
188 }),
184 .windows => {
185 const pipe = try threaded.windowsCreatePipe(.{
186 .server = .{ .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
187 .client = .{ .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
188 .inbound = true,
189 });
190 read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } };
191 write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } };
192 },
189193 else => {
190 const pipe = try std.Io.Threaded.pipe2(.{});
194 const pipe = try std.Io.Threaded.pipe2(.{ .CLOEXEC = true });
191195 read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } };
192196 write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } };
193197 },
lib/std/Progress.zig+473-472
......@@ -11,7 +11,7 @@ const windows = std.os.windows;
1111const testing = std.testing;
1212const assert = std.debug.assert;
1313const posix = std.posix;
14const Writer = std.Io.Writer;
14const Writer = Io.Writer;
1515
1616/// Currently this API only supports this value being set to stderr, which
1717/// happens automatically inside `start`.
......@@ -21,13 +21,10 @@ io: Io,
2121
2222terminal_mode: TerminalMode,
2323
24update_worker: ?Io.Future(void),
24update_worker: ?Io.Future(WorkerError!void),
2525
2626/// Atomically set by SIGWINCH as well as the root done() function.
2727redraw_event: Io.Event,
28/// Indicates a request to shut down and reset global state.
29/// Accessed atomically.
30done: bool,
3128need_clear: bool,
3229status: Status,
3330
......@@ -43,15 +40,19 @@ draw_buffer: []u8,
4340/// This is in a separate array from `node_storage` but with the same length so
4441/// that it can be iterated over efficiently without trashing too much of the
4542/// CPU cache.
46node_parents: []Node.Parent,
47node_storage: []Node.Storage,
48node_freelist_next: []Node.OptionalIndex,
43node_parents: [node_storage_buffer_len]Node.Parent,
44node_storage: [node_storage_buffer_len]Node.Storage,
45node_freelist_next: [node_storage_buffer_len]Node.OptionalIndex,
4946node_freelist: Freelist,
5047/// This is the number of elements in node arrays which have been used so far. Nodes before this
5148/// index are either active, or on the freelist. The remaining nodes are implicitly free. This
5249/// value may at times temporarily exceed the node count.
5350node_end_index: u32,
5451
52ipc_next: Ipc.SlotAtomic,
53ipc: [ipc_storage_buffer_len]Ipc,
54ipc_files: [ipc_storage_buffer_len]Io.File,
55
5556start_failure: StartFailure,
5657
5758pub const Status = enum {
......@@ -77,6 +78,80 @@ const Freelist = packed struct(u32) {
7778 generation: u24,
7879};
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
80155pub const TerminalMode = union(enum) {
81156 off,
82157 ansi_escape_codes,
......@@ -116,7 +191,7 @@ pub const Node = struct {
116191
117192 pub const none: Node = .{ .index = .none };
118193
119 pub const max_name_len = 40;
194 pub const max_name_len = 120;
120195
121196 const Storage = extern struct {
122197 /// Little endian.
......@@ -127,25 +202,16 @@ pub const Node = struct {
127202 name: [max_name_len]u8 align(@alignOf(usize)),
128203
129204 /// Not thread-safe.
130 fn getIpcFd(s: Storage) ?Io.File.Handle {
131 return if (s.estimated_total_count == std.math.maxInt(u32)) switch (@typeInfo(Io.File.Handle)) {
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;
205 fn getIpcIndex(s: Storage) ?Ipc.Index {
206 return if (s.estimated_total_count == std.math.maxInt(u32)) @bitCast(s.completed_count) else null;
136207 }
137208
138209 /// Thread-safe.
139 fn setIpcFd(s: *Storage, fd: Io.File.Handle) 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 };
210 fn setIpcIndex(s: *Storage, ipc_index: Ipc.Index) void {
145211 // `estimated_total_count` max int indicates the special state that
146212 // causes `completed_count` to be treated as a file descriptor, so
147213 // the order here matters.
148 @atomicStore(u32, &s.completed_count, integer, .monotonic);
214 @atomicStore(u32, &s.completed_count, @bitCast(ipc_index), .monotonic);
149215 @atomicStore(u32, &s.estimated_total_count, std.math.maxInt(u32), .release); // synchronizes with acquire in `serialize`
150216 }
151217
......@@ -155,6 +221,14 @@ pub const Node = struct {
155221 s.estimated_total_count = @byteSwap(s.estimated_total_count);
156222 }
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
158232 comptime {
159233 assert((@sizeOf(Storage) % 4) == 0);
160234 }
......@@ -242,7 +316,7 @@ pub const Node = struct {
242316 }
243317
244318 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) {
246320 // Ran out of node storage memory. Progress for this node will not be tracked.
247321 _ = @atomicRmw(u32, &global_progress.node_end_index, .Sub, 1, .monotonic);
248322 return Node.none;
......@@ -292,15 +366,17 @@ pub const Node = struct {
292366 const index = n.index.unwrap() orelse return;
293367 const storage = storageByIndex(index);
294368 // Avoid u32 max int which is used to indicate a special state.
295 const saturated = @min(std.math.maxInt(u32) - 1, count);
296 @atomicStore(u32, &storage.estimated_total_count, saturated, .monotonic);
369 const saturated_total_count = @min(std.math.maxInt(u32) - 1, count);
370 @atomicStore(u32, &storage.estimated_total_count, saturated_total_count, .monotonic);
297371 }
298372
299373 /// Thread-safe.
300374 pub fn increaseEstimatedTotalItems(n: Node, count: usize) void {
301375 const index = n.index.unwrap() orelse return;
302376 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);
304380 }
305381
306382 /// Finish a started `Node`. Thread-safe.
......@@ -310,11 +386,25 @@ pub const Node = struct {
310386 return;
311387 }
312388 const index = n.index.unwrap() orelse return;
389 const io = global_progress.io;
313390 const parent_ptr = parentByIndex(index);
314391 if (@atomicLoad(Node.Parent, parent_ptr, .monotonic).unwrap()) |parent_index| {
315392 _ = @atomicRmw(u32, &storageByIndex(parent_index).completed_count, .Add, 1, .monotonic);
316393 @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
318408 const freelist = &global_progress.node_freelist;
319409 var old_freelist = @atomicLoad(Freelist, freelist, .monotonic);
320410 while (true) {
......@@ -332,34 +422,52 @@ pub const Node = struct {
332422 };
333423 }
334424 } else {
335 @atomicStore(bool, &global_progress.done, true, .monotonic);
336 const io = global_progress.io;
337 global_progress.redraw_event.set(io);
338 if (global_progress.update_worker) |*worker| worker.await(io);
425 if (global_progress.update_worker) |*worker| worker.cancel(io) catch {};
426 for (&global_progress.ipc, &global_progress.ipc_files) |ipc, ipc_file| {
427 assert(!ipc.locked or !ipc.valid); // missing call to end()
428 if (ipc.locked or ipc.valid) ipc_file.close(io);
429 }
339430 }
340431 }
341432
342 /// Posix-only. Used by `std.process.Child`. Thread-safe.
343 pub fn setIpcFd(node: Node, fd: Io.File.Handle) void {
433 /// Used by `std.process.Child`. Thread-safe.
434 pub fn setIpcFile(node: Node, expected_io_userdata: ?*anyopaque, file: Io.File) void {
344435 const index = node.index.unwrap() orelse return;
345 assert(fd >= 0);
346 assert(fd != posix.STDOUT_FILENO);
347 assert(fd != posix.STDIN_FILENO);
348 assert(fd != posix.STDERR_FILENO);
349 storageByIndex(index).setIpcFd(fd);
436 const io = global_progress.io;
437 assert(io.userdata == expected_io_userdata);
438 for (0..ipc_storage_buffer_len) |_| {
439 const slot: Ipc.Slot = @truncate(
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);
350459 }
351460
352 /// Posix-only. Thread-safe. Assumes the node is storing an IPC file
353 /// descriptor.
354 pub fn getIpcFd(node: Node) ?Io.File.Handle {
355 const index = node.index.unwrap() orelse return null;
356 const storage = storageByIndex(index);
357 const int = @atomicLoad(u32, &storage.completed_count, .monotonic);
358 return switch (@typeInfo(Io.File.Handle)) {
359 .int => @bitCast(int),
360 .pointer => @ptrFromInt(int),
361 else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
362 };
461 pub fn setIpcIndex(node: Node, ipc_index: Ipc.Index) void {
462 storageByIndex(node.index.unwrap() orelse return).setIpcIndex(ipc_index);
463 }
464
465 /// Not thread-safe.
466 pub fn takeIpcIndex(node: Node) ?Ipc.Index {
467 const storage = storageByIndex(node.index.unwrap() orelse return null);
468 assert(storage.estimated_total_count == std.math.maxInt(u32));
469 @atomicStore(u32, &storage.estimated_total_count, 0, .monotonic);
470 return @bitCast(storage.completed_count);
363471 }
364472
365473 fn storageByIndex(index: Node.Index) *Node.Storage {
......@@ -379,7 +487,9 @@ pub const Node = struct {
379487
380488 const storage = storageByIndex(free_index);
381489 @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);
383493 const name_len = @min(max_name_len, name.len);
384494 copyAtomicStore(storage.name[0..name_len], name[0..name_len]);
385495 if (name_len < storage.name.len)
......@@ -406,16 +516,20 @@ var global_progress: Progress = .{
406516 .rows = 0,
407517 .cols = 0,
408518 .draw_buffer = undefined,
409 .done = false,
410519 .need_clear = false,
411520 .status = .working,
412 .start_failure = .unstarted,
413521
414 .node_parents = &node_parents_buffer,
415 .node_storage = &node_storage_buffer,
416 .node_freelist_next = &node_freelist_next_buffer,
522 .node_parents = undefined,
523 .node_storage = undefined,
524 .node_freelist_next = undefined,
417525 .node_freelist = .{ .head = .none, .generation = 0 },
418526 .node_end_index = 0,
527
528 .ipc_next = 0,
529 .ipc = undefined,
530 .ipc_files = undefined,
531
532 .start_failure = .unstarted,
419533};
420534
421535pub const StartFailure = union(enum) {
......@@ -425,17 +539,23 @@ pub const StartFailure = union(enum) {
425539 parent_ipc: error{ UnsupportedOperation, UnrecognizedFormat },
426540};
427541
428const node_storage_buffer_len = 83;
429var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined;
430var node_storage_buffer: [node_storage_buffer_len]Node.Storage = undefined;
431var node_freelist_next_buffer: [node_storage_buffer_len]Node.OptionalIndex = undefined;
542/// One less than a power of two ensures `max_packet_len` is already a power of two.
543const node_storage_buffer_len = ipc_storage_buffer_len - 1;
544
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
433553var default_draw_buffer: [4096]u8 = undefined;
434554
435555var debug_start_trace = std.debug.Trace.init;
436556
437557pub const have_ipc = switch (builtin.os.tag) {
438 .wasi, .freestanding, .windows => false,
558 .wasi, .freestanding => false,
439559 else => true,
440560};
441561
......@@ -467,9 +587,9 @@ pub fn start(io: Io, options: Options) Node {
467587 }
468588 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 });
471592 const root_node = Node.init(@enumFromInt(0), .none, options.root_name, options.estimated_total_items);
472 global_progress.done = false;
473593 global_progress.node_end_index = 1;
474594
475595 assert(options.draw_buffer.len >= 200);
......@@ -477,21 +597,18 @@ pub fn start(io: Io, options: Options) Node {
477597 global_progress.refresh_rate_ns = @intCast(options.refresh_rate_ns.toNanoseconds());
478598 global_progress.initial_delay_ns = @intCast(options.initial_delay_ns.toNanoseconds());
479599
480 if (noop_impl)
481 return Node.none;
600 if (noop_impl) return .none;
482601
483602 global_progress.io = io;
484603
485604 if (io.vtable.progressParentFile(io.userdata)) |ipc_file| {
486605 global_progress.update_worker = io.concurrent(ipcThreadRun, .{ io, ipc_file }) catch |err| {
487606 global_progress.start_failure = .{ .spawn_ipc_worker = err };
488 return Node.none;
607 return .none;
489608 };
490609 } else |env_err| switch (env_err) {
491610 error.EnvironmentVariableMissing => {
492 if (options.disable_printing) {
493 return Node.none;
494 }
611 if (options.disable_printing) return .none;
495612 const stderr: Io.File = .stderr();
496613 global_progress.terminal = stderr;
497614 if (stderr.enableAnsiEscapeCodes(io)) |_| {
......@@ -504,14 +621,12 @@ pub fn start(io: Io, options: Options) Node {
504621 } else |err| switch (err) {
505622 error.Canceled => {
506623 io.recancel();
507 return Node.none;
624 return .none;
508625 },
509626 }
510627 }
511628
512 if (global_progress.terminal_mode == .off) {
513 return Node.none;
514 }
629 if (global_progress.terminal_mode == .off) return .none;
515630
516631 if (have_sigwinch) {
517632 const act: posix.Sigaction = .{
......@@ -530,12 +645,12 @@ pub fn start(io: Io, options: Options) Node {
530645 global_progress.update_worker = future;
531646 } else |err| {
532647 global_progress.start_failure = .{ .spawn_update_worker = err };
533 return Node.none;
648 return .none;
534649 }
535650 },
536651 else => |e| {
537652 global_progress.start_failure = .{ .parent_ipc = e };
538 return Node.none;
653 return .none;
539654 },
540655 }
541656
......@@ -548,58 +663,55 @@ pub fn setStatus(new_status: Status) void {
548663}
549664
550665/// 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 {
552667 const timeout: Io.Timeout = .{ .duration = .{
553668 .clock = .awake,
554669 .raw = .fromNanoseconds(timeout_ns),
555670 } };
556671 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,
558674 };
559675 global_progress.redraw_event.reset();
560676 return resize_flag or (global_progress.cols == 0);
561677}
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 {
564683 // Store this data in the thread so that it does not need to be part of the
565684 // linker data of the main executable.
566685 var serialized_buffer: Serialized.Buffer = undefined;
686 serialized_buffer.init();
687 defer serialized_buffer.batch.cancel(io);
567688
568689 // In this function we bypass the wrapper code inside `Io.lockStderr` /
569690 // `Io.tryLockStderr` in order to avoid clearing the terminal twice.
570691 // We still want to go through the `Io` instance however in case it uses a
571692 // task-switching mutex.
572693
573 {
574 const resize_flag = wait(io, global_progress.initial_delay_ns);
575 if (@atomicLoad(bool, &global_progress.done, .monotonic)) return;
576 maybeUpdateSize(io, resize_flag) catch return;
577
578 const buffer, _ = computeRedraw(&serialized_buffer);
579 if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {
580 defer io.unlockStderr();
581 global_progress.need_clear = true;
582 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
583 }
694 try maybeUpdateSize(io, try wait(io, global_progress.initial_delay_ns));
695 errdefer {
696 const cancel_protection = io.swapCancelProtection(.blocked);
697 defer _ = io.swapCancelProtection(cancel_protection);
698 const stderr = io.vtable.lockStderr(io.userdata, null) catch |err| switch (err) {
699 error.Canceled => unreachable, // blocked
700 };
701 defer io.unlockStderr();
702 clearWrittenWithEscapeCodes(stderr.file_writer) catch {};
584703 }
585
586704 while (true) {
587 const resize_flag = wait(io, global_progress.refresh_rate_ns);
588
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| {
705 const buffer, _ = try computeRedraw(io, &serialized_buffer);
706 if (try io.vtable.tryLockStderr(io.userdata, null)) |locked_stderr| {
599707 defer io.unlockStderr();
600708 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 };
602712 }
713
714 try maybeUpdateSize(io, try wait(io, global_progress.refresh_rate_ns));
603715 }
604716}
605717
......@@ -611,79 +723,60 @@ fn windowsApiWriteMarker() void {
611723 _ = windows.kernel32.WriteConsoleW(handle, &[_]u16{windows_api_start_marker}, 1, &num_chars_written, null);
612724}
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.
615729 var serialized_buffer: Serialized.Buffer = undefined;
730 serialized_buffer.init();
731 defer serialized_buffer.batch.cancel(io);
616732
617733 // In this function we bypass the wrapper code inside `Io.lockStderr` /
618734 // `Io.tryLockStderr` in order to avoid clearing the terminal twice.
619735 // We still want to go through the `Io` instance however in case it uses a
620736 // task-switching mutex.
621737
622 {
623 const resize_flag = wait(io, global_progress.initial_delay_ns);
624 if (@atomicLoad(bool, &global_progress.done, .monotonic)) return;
625 maybeUpdateSize(io, resize_flag) catch return;
626
627 const buffer, const nl_n = computeRedraw(&serialized_buffer);
628 if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {
629 defer io.unlockStderr();
630 windowsApiWriteMarker();
631 global_progress.need_clear = true;
632 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
633 windowsApiMoveToMarker(nl_n) catch return;
634 }
738 try maybeUpdateSize(io, try wait(io, global_progress.initial_delay_ns));
739 errdefer {
740 const cancel_protection = io.swapCancelProtection(.blocked);
741 defer _ = io.swapCancelProtection(cancel_protection);
742 _ = io.vtable.lockStderr(io.userdata, null) catch |err| switch (err) {
743 error.Canceled => unreachable, // blocked
744 };
745 defer io.unlockStderr();
746 clearWrittenWindowsApi() catch {};
635747 }
636
637748 while (true) {
638 const resize_flag = wait(io, global_progress.refresh_rate_ns);
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);
749 const buffer, const nl_n = try computeRedraw(io, &serialized_buffer);
649750 if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {
650751 defer io.unlockStderr();
651 clearWrittenWindowsApi() catch return;
752 try clearWrittenWindowsApi();
652753 windowsApiWriteMarker();
653754 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 };
655758 windowsApiMoveToMarker(nl_n) catch return;
656759 }
760
761 try maybeUpdateSize(io, try wait(io, global_progress.refresh_rate_ns));
657762 }
658763}
659764
660fn ipcThreadRun(io: Io, file: Io.File) void {
765fn ipcThreadRun(io: Io, file: Io.File) WorkerError!void {
661766 // Store this data in the thread so that it does not need to be part of the
662767 // linker data of the main executable.
663768 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 {
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
773 _ = try io.sleep(.fromNanoseconds(global_progress.initial_delay_ns), .awake);
677774 while (true) {
678 _ = wait(io, global_progress.refresh_rate_ns);
679
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,
775 writeIpc(&fw.interface, try serialize(io, &serialized_buffer)) catch |err| switch (err) {
776 error.WriteFailed => return fw.err.?,
686777 };
778
779 _ = try io.sleep(.fromNanoseconds(global_progress.refresh_rate_ns), .awake);
687780 }
688781}
689782
......@@ -862,31 +955,49 @@ const Serialized = struct {
862955 const Buffer = struct {
863956 parents: [node_storage_buffer_len]Node.Parent,
864957 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,
868 storage_copy: [node_storage_buffer_len]Node.Storage,
869 ipc_metadata_fds_copy: [node_storage_buffer_len]Fd,
870 ipc_metadata_copy: [node_storage_buffer_len]SavedMetadata,
871
872 ipc_metadata_fds: [node_storage_buffer_len]Fd,
873 ipc_metadata: [node_storage_buffer_len]SavedMetadata,
959 ipc_start: u8,
960 ipc_end: u8,
961 ipc_data: [ipc_storage_buffer_len]Ipc.Data,
962 ipc_buffers: [ipc_storage_buffer_len][max_packet_len]u8,
963 ipc_vecs: [ipc_storage_buffer_len][1][]u8,
964 batch_storage: [ipc_storage_buffer_len]Io.Operation.Storage,
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 }
874973 };
875974};
876975
877fn serialize(serialized_buffer: *Serialized.Buffer) Serialized {
878 var serialized_len: usize = 0;
879 var any_ipc = false;
976fn serialize(io: Io, serialized_buffer: *Serialized.Buffer) !Serialized {
977 var prev_parents: [node_storage_buffer_len]Node.Parent = undefined;
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
881986 // Iterate all of the nodes and construct a serializable copy of the state that can be examined
882987 // without atomics. The `@min` call is here because `node_end_index` might briefly exceed the
883988 // 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;
885996 for (
886997 global_progress.node_parents[0..end_index],
887998 global_progress.node_storage[0..end_index],
888 serialized_buffer.map[0..end_index],
889 ) |*parent_ptr, *storage_ptr, *map| {
999 map[0..end_index],
1000 ) |*parent_ptr, *storage_ptr, *map_entry| {
8901001 const parent = @atomicLoad(Node.Parent, parent_ptr, .monotonic);
8911002 if (parent == .unused) {
8921003 // 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 {
9001011 // parent, it will just not be printed at all. The general idea here is that performance
9011012 // is more important than 100% correct output every frame, given that this API is likely
9021013 // to be used in hot paths!
903 map.* = .none;
1014 map_entry.* = .none;
9041015 continue;
9051016 }
9061017 const dest_storage = &serialized_buffer.storage[serialized_len];
9071018 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`
9091020 dest_storage.completed_count = @atomicLoad(u32, &storage_ptr.completed_count, .monotonic);
9101021
911 any_ipc = any_ipc or (dest_storage.getIpcFd() != null);
9121022 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;
9141025 serialized_len += 1;
9151026 }
9161027
......@@ -919,266 +1030,212 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized {
9191030 parent.* = switch (parent.*) {
9201031 .unused => unreachable,
9211032 .none => .none,
922 _ => |p| serialized_buffer.map[@intFromEnum(p)].toParent(),
1033 _ => |p| map[@intFromEnum(p)].toParent(),
9231034 };
9241035 }
9251036
926 // Find nodes which correspond to child processes.
927 if (any_ipc)
928 serialized_len = serializeIpc(serialized_len, serialized_buffer);
929
930 return .{
931 .parents = serialized_buffer.parents[0..serialized_len],
932 .storage = serialized_buffer.storage[0..serialized_len],
1037 // Fill pipe buffers.
1038 const batch = &serialized_buffer.batch;
1039 batch.awaitConcurrent(io, .{
1040 .duration = .{ .raw = .zero, .clock = .awake },
1041 }) catch |err| switch (err) {
1042 error.Timeout => {},
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,
9331077 };
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 (
975 serialized_buffer.parents[0..serialized_len],
976 serialized_buffer.storage[0..serialized_len],
977 0..,
1079 // Find nodes which correspond to child processes.
1080 const ipc_start = maybe_ipc_start orelse serialized_len;
1081 serialized_buffer.ipc_start = ipc_start;
1082 for (
1083 serialized_buffer.parents[ipc_start..serialized_len],
1084 serialized_buffer.storage[ipc_start..serialized_len],
1085 ipc_start..,
9781086 ) |main_parent, *main_storage, main_index| {
9791087 if (main_parent == .unused) continue;
980 const file: Io.File = .{
981 .handle = main_storage.getIpcFd() orelse continue,
982 .flags = .{ .nonblocking = true },
983 };
984 const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata);
985 var bytes_read: usize = 0;
986 while (true) {
987 const n = file.readStreaming(io, &.{pipe_buf[bytes_read..]}) catch |err| switch (err) {
988 error.WouldBlock, error.EndOfStream => break,
989 else => |e| {
990 std.log.debug("failed to read child progress data: {t}", .{e});
991 main_storage.completed_count = 0;
992 main_storage.estimated_total_count = 0;
993 continue :main_loop;
994 },
995 };
996 if (opt_saved_metadata) |m| {
997 if (m.remaining_read_trash_bytes > 0) {
998 assert(bytes_read == 0);
999 if (m.remaining_read_trash_bytes >= n) {
1000 m.remaining_read_trash_bytes = @intCast(m.remaining_read_trash_bytes - n);
1001 continue;
1002 }
1003 const src = pipe_buf[m.remaining_read_trash_bytes..n];
1004 @memmove(pipe_buf[0..src.len], src);
1005 m.remaining_read_trash_bytes = 0;
1006 bytes_read = src.len;
1007 continue;
1008 }
1009 }
1010 bytes_read += n;
1011 }
1012 // Ignore all but the last message on the pipe.
1013 var input: []u8 = pipe_buf[0..bytes_read];
1014 if (input.len == 0) {
1015 serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, 0, file.handle);
1016 continue;
1017 }
1018
1019 const storage, const parents = while (true) {
1020 const subtree_len: usize = input[0];
1021 const expected_bytes = 1 + subtree_len * (@sizeOf(Node.Storage) + @sizeOf(Node.Parent));
1022 if (input.len < expected_bytes) {
1023 // Ignore short reads. We'll handle the next full message when it comes instead.
1024 const remaining_read_trash_bytes: u16 = @intCast(expected_bytes - input.len);
1025 serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, remaining_read_trash_bytes, file.handle);
1026 continue :main_loop;
1027 }
1028 if (input.len > expected_bytes) {
1029 input = input[expected_bytes..];
1030 continue;
1031 }
1032 const storage_bytes = input[1..][0 .. subtree_len * @sizeOf(Node.Storage)];
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));
1088 const ipc_index = main_storage.getIpcIndex() orelse continue;
1089 const ipc = &global_progress.ipc[ipc_index.slot];
1090 const ipc_data = &serialized_buffer.ipc_data[ipc_index.slot];
1091 state: switch (ipc_data.state) {
1092 .unused => {
1093 if (@cmpxchgWeak(
1094 Ipc,
1095 ipc,
1096 .{ .locked = false, .valid = true, .generation = ipc_index.generation },
1097 .{ .locked = true, .valid = true, .generation = ipc_index.generation },
1098 .acquire,
1099 .monotonic,
1100 )) |_| continue;
1101
1102 const ipc_vec = &serialized_buffer.ipc_vecs[ipc_index.slot];
1103 ipc_vec.* = .{&serialized_buffer.ipc_buffers[ipc_index.slot]};
1104 batch.addAt(ipc_index.slot, .{ .file_read_streaming = .{
1105 .file = global_progress.ipc_files[ipc_index.slot],
1106 .data = ipc_vec,
1107 } });
1108
1109 ipc_data.* = .{
1110 .state = .pending,
1111 .bytes_read = 0,
1112 .main_index = @intCast(main_index),
1113 .start_index = serialized_len,
1114 .nodes_len = 0,
1115 };
1116 main_storage.completed_count = 0;
1117 main_storage.estimated_total_count = 0;
1118 },
1119 .pending => {
1120 const start_index = ipc_data.start_index;
1121 const nodes_len = @min(ipc_data.nodes_len, node_storage_buffer_len - serialized_len);
1122
1123 main_storage.copyRoot(&prev_storage[ipc_data.main_index]);
1124 @memcpy(
1125 serialized_buffer.storage[serialized_len..][0..nodes_len],
1126 prev_storage[start_index..][0..nodes_len],
1127 );
1128 for (
1129 serialized_buffer.parents[serialized_len..][0..nodes_len],
1130 prev_parents[serialized_len..][0..nodes_len],
1131 ) |*parent, prev_parent| parent.* = switch (prev_parent) {
1132 .none, .unused => .none,
1133 _ => if (@intFromEnum(prev_parent) == ipc_data.main_index)
1134 @enumFromInt(main_index)
1135 else if (@intFromEnum(prev_parent) >= start_index and
1136 @intFromEnum(prev_parent) < start_index + nodes_len)
1137 @enumFromInt(@intFromEnum(prev_parent) - start_index + serialized_len)
1138 else
1139 .none,
1140 };
10411141
1042 // Remember in case the pipe is empty on next update.
1043 ipc_metadata_fds[ipc_metadata_len] = Fd.init(file.handle);
1044 ipc_metadata[ipc_metadata_len] = .{
1045 .remaining_read_trash_bytes = 0,
1046 .start_index = @intCast(serialized_len),
1047 .nodes_len = nodes_len,
1048 .main_index = @intCast(main_index),
1049 };
1050 ipc_metadata_len += 1;
1051
1052 // Mount the root here.
1053 copyRoot(main_storage, &storage[0]);
1054 if (is_big_endian) main_storage.byteSwap();
1055
1056 // Copy the rest of the tree to the end.
1057 const storage_dest = serialized_buffer.storage[serialized_len..][0..nodes_len];
1058 @memcpy(storage_dest, storage[1..][0..nodes_len]);
1059
1060 // Always little-endian over the pipe.
1061 if (is_big_endian) for (storage_dest) |*s| s.byteSwap();
1062
1063 // Patch up parent pointers taking into account how the subtree is mounted.
1064 for (serialized_buffer.parents[serialized_len..][0..nodes_len], parents[1..][0..nodes_len]) |*dest, p| {
1065 dest.* = switch (p) {
1066 // Fix bad data so the rest of the code does not see `unused`.
1067 .none, .unused => .none,
1068 // Root node is being mounted here.
1069 @as(Node.Parent, @enumFromInt(0)) => @enumFromInt(main_index),
1070 // Other nodes mounted at the end.
1071 // Don't trust child data; if the data is outside the expected range, ignore the data.
1072 // This also handles the case when data was truncated.
1073 _ => |off| if (@intFromEnum(off) > nodes_len)
1074 .none
1075 else
1076 @enumFromInt(serialized_len + @intFromEnum(off) - 1),
1077 };
1142 ipc_data.main_index = @intCast(main_index);
1143 ipc_data.start_index = serialized_len;
1144 ipc_data.nodes_len = nodes_len;
1145 serialized_len += nodes_len;
1146 },
1147 .ready => {
1148 const ipc_buffer = &serialized_buffer.ipc_buffers[ipc_index.slot];
1149 const packet_start, const packet_end = ipc_data.findLastPacket(ipc_buffer);
1150 const packet_is_empty = packet_end - packet_start <= 1;
1151 if (!packet_is_empty) {
1152 const storage, const parents, const nodes_len = packet_contents: {
1153 var packet_index: usize = packet_start;
1154 const nodes_len: u16 = ipc_buffer[packet_index];
1155 packet_index += 1;
1156 const storage_bytes =
1157 ipc_buffer[packet_index..][0 .. nodes_len * @sizeOf(Node.Storage)];
1158 packet_index += storage_bytes.len;
1159 const parents_bytes =
1160 ipc_buffer[packet_index..][0 .. nodes_len * @sizeOf(Node.Parent)];
1161 packet_index += parents_bytes.len;
1162 assert(packet_index == packet_end);
1163 const storage: []align(1) const Node.Storage = @ptrCast(storage_bytes);
1164 const parents: []align(1) const Node.Parent = @ptrCast(parents_bytes);
1165 const children_nodes_len =
1166 @min(nodes_len - 1, node_storage_buffer_len - serialized_len);
1167 break :packet_contents .{ storage, parents, children_nodes_len };
1168 };
1169
1170 // Mount the root here.
1171 main_storage.copyRoot(&storage[0]);
1172 if (is_big_endian) main_storage.byteSwap();
1173
1174 // Copy the rest of the tree to the end.
1175 const serialized_storage =
1176 serialized_buffer.storage[serialized_len..][0..nodes_len];
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 },
10781208 }
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;
11081209 }
1109 return null;
1110}
1111
1112fn useSavedIpcData(
1113 start_serialized_len: usize,
1114 serialized_buffer: *Serialized.Buffer,
1115 main_storage: *Node.Storage,
1116 main_index: usize,
1117 opt_saved_metadata: ?*SavedMetadata,
1118 remaining_read_trash_bytes: u16,
1119 fd: Io.File.Handle,
1120) usize {
1121 const parents_copy = &serialized_buffer.parents_copy;
1122 const storage_copy = &serialized_buffer.storage_copy;
1123 const ipc_metadata_fds = &serialized_buffer.ipc_metadata_fds;
1124 const ipc_metadata = &serialized_buffer.ipc_metadata;
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;
1210 serialized_buffer.ipc_end = serialized_len;
1211
1212 // Ignore data from unused pipes. This ensures that if a child process exists we will
1213 // eventually see `EndOfStream` and close the pipe.
1214 if (ready_len > 0) for (
1215 &serialized_buffer.ipc_data,
1216 &serialized_buffer.ipc_buffers,
1217 &serialized_buffer.ipc_vecs,
1218 0..,
1219 ) |*ipc_data, *ipc_buffer, *ipc_vec, ipc_slot| switch (ipc_data.state) {
1220 .unused, .pending => {},
1221 .ready => {
1222 _, const packet_end = ipc_data.findLastPacket(ipc_buffer);
1223 ipc_data.rebase(ipc_buffer, ipc_vec, batch, @intCast(ipc_slot), packet_end);
1224 ready_len -= 1;
1225 },
11401226 };
1227 assert(ready_len == 0);
11411228
1142 const start_index = saved_metadata.start_index;
1143 const nodes_len = @min(saved_metadata.nodes_len, serialized_buffer.storage.len - start_serialized_len);
1144 const old_main_index = saved_metadata.main_index;
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),
1229 return .{
1230 .parents = serialized_buffer.parents[0..serialized_len],
1231 .storage = serialized_buffer.storage[0..serialized_len],
11521232 };
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;
11781233}
11791234
1180fn computeRedraw(serialized_buffer: *Serialized.Buffer) struct { []u8, usize } {
1181 const serialized = serialize(serialized_buffer);
1235fn computeRedraw(io: Io, serialized_buffer: *Serialized.Buffer) !struct { []u8, usize } {
1236 if (global_progress.rows == 0 or global_progress.cols == 0) return error.WindowTooSmall;
1237
1238 const serialized = try serialize(io, serialized_buffer);
11821239
11831240 // Now we can analyze our copy of the graph without atomics, reconstructing
11841241 // children lists which do not exist in the canonical data. These are
......@@ -1413,9 +1470,7 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool {
14131470 return nl_n + 2 < p.rows;
14141471}
14151472
1416var remaining_write_trash_bytes: usize = 0;
1417
1418fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!void {
1473fn writeIpc(writer: *Io.Writer, serialized: Serialized) Io.Writer.Error!void {
14191474 // Byteswap if necessary to ensure little endian over the pipe. This is
14201475 // needed because the parent or child process might be running in qemu.
14211476 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
14261481 const storage = std.mem.sliceAsBytes(serialized.storage);
14271482 const parents = std.mem.sliceAsBytes(serialized.parents);
14281483
1429 var vecs: [3][]const u8 = .{ header, storage, parents };
1430
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 }
1484 var vec = [3][]const u8{ header, storage, parents };
1485 try writer.writeVecAll(&vec);
14851486}
14861487
14871488fn maybeUpdateSize(io: Io, resize_flag: bool) !void {
lib/std/Thread.zig+5-1
......@@ -598,7 +598,11 @@ const WindowsThreadImpl = struct {
598598 }
599599
600600 fn join(self: Impl) void {
601 windows.WaitForSingleObjectEx(self.thread.thread_handle, windows.INFINITE, false) catch unreachable;
601 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
602 switch (windows.ntdll.NtWaitForSingleObject(self.thread.thread_handle, windows.FALSE, &infinite_timeout)) {
603 windows.NTSTATUS.WAIT_0 => {},
604 else => |status| windows.unexpectedStatus(status) catch unreachable,
605 }
602606 windows.CloseHandle(self.thread.thread_handle);
603607 assert(self.thread.completion.load(.seq_cst) == .completed);
604608 self.thread.free();
lib/std/mem/Allocator.zig+13-2
......@@ -452,12 +452,23 @@ pub fn dupe(allocator: Allocator, comptime T: type, m: []const T) Error![]T {
452452 return new_buf;
453453}
454454
455/// Deprecated in favor of `dupeSentinel`
455456/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
456457pub fn dupeZ(allocator: Allocator, comptime T: type, m: []const T) Error![:0]T {
458 return allocator.dupeSentinel(T, m, 0);
459}
460
461/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
462pub fn dupeSentinel(
463 allocator: Allocator,
464 comptime T: type,
465 m: []const T,
466 comptime sentinel: T,
467) Error![:sentinel]T {
457468 const new_buf = try allocator.alloc(T, m.len + 1);
458469 @memcpy(new_buf[0..m.len], m);
459 new_buf[m.len] = 0;
460 return new_buf[0..m.len :0];
470 new_buf[m.len] = sentinel;
471 return new_buf[0..m.len :sentinel];
461472}
462473
463474/// An allocator that always fails to allocate.
lib/std/os/linux.zig+18
......@@ -1848,6 +1848,24 @@ pub const F = struct {
18481848 pub const RDLCK = if (is_sparc) 1 else 0;
18491849 pub const WRLCK = if (is_sparc) 2 else 1;
18501850 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;
18511869};
18521870
18531871pub const F_OWNER = enum(i32) {
lib/std/os/windows.zig+19-250
......@@ -521,7 +521,7 @@ pub const FILE = struct {
521521 _,
522522
523523 pub const VALID_FLAGS: @This() = @enumFromInt(0b11);
524 } = .ASYNCHRONOUS,
524 },
525525 /// The file being opened must not be a directory file or this call
526526 /// fails. The file object being opened can represent a data file, a
527527 /// logical, virtual, or physical device, or a volume.
......@@ -2324,12 +2324,12 @@ pub fn GetProcessHeap() ?*HEAP {
23242324// ref: um/winternl.h
23252325
23262326pub const OBJECT_ATTRIBUTES = extern struct {
2327 Length: ULONG,
2328 RootDirectory: ?HANDLE,
2329 ObjectName: ?*UNICODE_STRING,
2330 Attributes: ATTRIBUTES,
2331 SecurityDescriptor: ?*anyopaque,
2332 SecurityQualityOfService: ?*anyopaque,
2327 Length: ULONG = @sizeOf(OBJECT_ATTRIBUTES),
2328 RootDirectory: ?HANDLE = null,
2329 ObjectName: ?*UNICODE_STRING = @constCast(&UNICODE_STRING.empty),
2330 Attributes: ATTRIBUTES = .{},
2331 SecurityDescriptor: ?*anyopaque = null,
2332 SecurityQualityOfService: ?*anyopaque = null,
23332333
23342334 // Valid values for the Attributes field
23352335 pub const ATTRIBUTES = packed struct(ULONG) {
......@@ -2420,14 +2420,10 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
24202420 .Buffer = @constCast(sub_path_w.ptr),
24212421 };
24222422 const attr: OBJECT_ATTRIBUTES = .{
2423 .Length = @sizeOf(OBJECT_ATTRIBUTES),
24242423 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir,
2425 .Attributes = .{
2426 .INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false,
2427 },
2424 .Attributes = .{ .INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false },
24282425 .ObjectName = &nt_name,
24292426 .SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null,
2430 .SecurityQualityOfService = null,
24312427 };
24322428 var io: IO_STATUS_BLOCK = undefined;
24332429 while (true) {
......@@ -2475,7 +2471,8 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
24752471 // call has failed. There is not really a sane way to handle
24762472 // this other than retrying the creation after the OS finishes
24772473 // the deletion.
2478 _ = kernel32.SleepEx(1, TRUE);
2474 const delay_one_ms: LARGE_INTEGER = -(std.time.ns_per_ms / 100);
2475 _ = ntdll.NtDelayExecution(TRUE, &delay_one_ms);
24792476 continue;
24802477 },
24812478 .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
......@@ -2506,151 +2503,6 @@ pub fn GetCurrentThreadId() DWORD {
25062503pub fn GetLastError() Win32Error {
25072504 return @enumFromInt(teb().LastErrorValue);
25082505}
2509
2510pub const CreatePipeError = error{ Unexpected, SystemResources };
2511
2512var npfs: ?HANDLE = null;
2513
2514/// A Zig wrapper around `NtCreateNamedPipeFile` and `NtCreateFile` syscalls.
2515/// It implements similar behavior to `CreatePipe` and is meant to serve
2516/// as a direct substitute for that call.
2517pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void {
2518 // Up to NT 5.2 (Windows XP/Server 2003), `CreatePipe` would generate a pipe similar to:
2519 //
2520 // \??\pipe\Win32Pipes.{pid}.{count}
2521 //
2522 // where `pid` is the process id and count is a incrementing counter.
2523 // The implementation was changed after NT 6.0 (Vista) to open a handle to the Named Pipe File System
2524 // and use that as the root directory for `NtCreateNamedPipeFile`.
2525 // This object is visible under the NPFS but has no filename attached to it.
2526 //
2527 // This implementation replicates how `CreatePipe` works in modern Windows versions.
2528 const opt_dev_handle = @atomicLoad(?HANDLE, &npfs, .seq_cst);
2529 const dev_handle = opt_dev_handle orelse blk: {
2530 const str = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\NamedPipe\\");
2531 const len: u16 = @truncate(str.len * @sizeOf(u16));
2532 const name: UNICODE_STRING = .{
2533 .Length = len,
2534 .MaximumLength = len,
2535 .Buffer = @ptrCast(@constCast(str)),
2536 };
2537 const attrs: OBJECT_ATTRIBUTES = .{
2538 .ObjectName = @constCast(&name),
2539 .Length = @sizeOf(OBJECT_ATTRIBUTES),
2540 .RootDirectory = null,
2541 .Attributes = .{},
2542 .SecurityDescriptor = null,
2543 .SecurityQualityOfService = null,
2544 };
2545
2546 var iosb: IO_STATUS_BLOCK = undefined;
2547 var handle: HANDLE = undefined;
2548 switch (ntdll.NtCreateFile(
2549 &handle,
2550 .{
2551 .STANDARD = .{ .SYNCHRONIZE = true },
2552 .GENERIC = .{ .READ = true },
2553 },
2554 @constCast(&attrs),
2555 &iosb,
2556 null,
2557 .{},
2558 .VALID_FLAGS,
2559 .OPEN,
2560 .{ .IO = .SYNCHRONOUS_NONALERT },
2561 null,
2562 0,
2563 )) {
2564 .SUCCESS => {},
2565 // Judging from the ReactOS sources this is technically possible.
2566 .INSUFFICIENT_RESOURCES => return error.SystemResources,
2567 .INVALID_PARAMETER => unreachable,
2568 else => |e| return unexpectedStatus(e),
2569 }
2570 if (@cmpxchgStrong(?HANDLE, &npfs, null, handle, .seq_cst, .seq_cst)) |xchg| {
2571 CloseHandle(handle);
2572 break :blk xchg.?;
2573 } else break :blk handle;
2574 };
2575
2576 const name: UNICODE_STRING = .{ .Buffer = null, .Length = 0, .MaximumLength = 0 };
2577 var attrs: OBJECT_ATTRIBUTES = .{
2578 .ObjectName = @constCast(&name),
2579 .Length = @sizeOf(OBJECT_ATTRIBUTES),
2580 .RootDirectory = dev_handle,
2581 .Attributes = .{ .INHERIT = sattr.bInheritHandle != FALSE },
2582 .SecurityDescriptor = sattr.lpSecurityDescriptor,
2583 .SecurityQualityOfService = null,
2584 };
2585
2586 // 120 second relative timeout in 100ns units.
2587 const default_timeout: LARGE_INTEGER = (-120 * std.time.ns_per_s) / 100;
2588 var iosb: IO_STATUS_BLOCK = undefined;
2589 var read: HANDLE = undefined;
2590 switch (ntdll.NtCreateNamedPipeFile(
2591 &read,
2592 .{
2593 .SPECIFIC = .{ .FILE_PIPE = .{
2594 .WRITE_ATTRIBUTES = true,
2595 } },
2596 .STANDARD = .{ .SYNCHRONIZE = true },
2597 .GENERIC = .{ .READ = true },
2598 },
2599 &attrs,
2600 &iosb,
2601 .{ .READ = true, .WRITE = true },
2602 .CREATE,
2603 .{ .IO = .SYNCHRONOUS_NONALERT },
2604 .{ .TYPE = .BYTE_STREAM },
2605 .{ .MODE = .BYTE_STREAM },
2606 .{ .OPERATION = .QUEUE },
2607 1,
2608 4096,
2609 4096,
2610 @constCast(&default_timeout),
2611 )) {
2612 .SUCCESS => {},
2613 .INVALID_PARAMETER => unreachable,
2614 .INSUFFICIENT_RESOURCES => return error.SystemResources,
2615 else => |e| return unexpectedStatus(e),
2616 }
2617 errdefer CloseHandle(read);
2618
2619 attrs.RootDirectory = read;
2620
2621 var write: HANDLE = undefined;
2622 switch (ntdll.NtCreateFile(
2623 &write,
2624 .{
2625 .SPECIFIC = .{ .FILE_PIPE = .{
2626 .READ_ATTRIBUTES = true,
2627 } },
2628 .STANDARD = .{ .SYNCHRONIZE = true },
2629 .GENERIC = .{ .WRITE = true },
2630 },
2631 &attrs,
2632 &iosb,
2633 null,
2634 .{},
2635 .VALID_FLAGS,
2636 .OPEN,
2637 .{
2638 .IO = .SYNCHRONOUS_NONALERT,
2639 .NON_DIRECTORY_FILE = true,
2640 },
2641 null,
2642 0,
2643 )) {
2644 .SUCCESS => {},
2645 .INVALID_PARAMETER => unreachable,
2646 .INSUFFICIENT_RESOURCES => return error.SystemResources,
2647 else => |e| return unexpectedStatus(e),
2648 }
2649
2650 rd.* = read;
2651 wr.* = write;
2652}
2653
26542506/// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls.
26552507/// It implements similar behavior to `DeviceIoControl` and is meant to serve
26562508/// as a direct substitute for that call.
......@@ -2707,66 +2559,6 @@ pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWOR
27072559 return bytes;
27082560}
27092561
2710pub const SetHandleInformationError = error{Unexpected};
2711
2712pub fn SetHandleInformation(h: HANDLE, mask: DWORD, flags: DWORD) SetHandleInformationError!void {
2713 if (kernel32.SetHandleInformation(h, mask, flags) == 0) {
2714 switch (GetLastError()) {
2715 else => |err| return unexpectedError(err),
2716 }
2717 }
2718}
2719
2720pub const WaitForSingleObjectError = error{
2721 WaitAbandoned,
2722 WaitTimeOut,
2723 Unexpected,
2724};
2725
2726pub fn WaitForSingleObject(handle: HANDLE, milliseconds: DWORD) WaitForSingleObjectError!void {
2727 return WaitForSingleObjectEx(handle, milliseconds, false);
2728}
2729
2730pub fn WaitForSingleObjectEx(handle: HANDLE, milliseconds: DWORD, alertable: bool) WaitForSingleObjectError!void {
2731 switch (kernel32.WaitForSingleObjectEx(handle, milliseconds, @intFromBool(alertable))) {
2732 WAIT_ABANDONED => return error.WaitAbandoned,
2733 WAIT_OBJECT_0 => return,
2734 WAIT_TIMEOUT => return error.WaitTimeOut,
2735 WAIT_FAILED => switch (GetLastError()) {
2736 else => |err| return unexpectedError(err),
2737 },
2738 else => return error.Unexpected,
2739 }
2740}
2741
2742pub fn WaitForMultipleObjectsEx(handles: []const HANDLE, waitAll: bool, milliseconds: DWORD, alertable: bool) !u32 {
2743 assert(handles.len > 0 and handles.len <= MAXIMUM_WAIT_OBJECTS);
2744 const nCount: DWORD = @as(DWORD, @intCast(handles.len));
2745 switch (kernel32.WaitForMultipleObjectsEx(
2746 nCount,
2747 handles.ptr,
2748 @intFromBool(waitAll),
2749 milliseconds,
2750 @intFromBool(alertable),
2751 )) {
2752 WAIT_OBJECT_0...WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS => |n| {
2753 const handle_index = n - WAIT_OBJECT_0;
2754 assert(handle_index < nCount);
2755 return handle_index;
2756 },
2757 WAIT_ABANDONED_0...WAIT_ABANDONED_0 + MAXIMUM_WAIT_OBJECTS => |n| {
2758 const handle_index = n - WAIT_ABANDONED_0;
2759 assert(handle_index < nCount);
2760 return error.WaitAbandoned;
2761 },
2762 WAIT_TIMEOUT => return error.WaitTimeOut,
2763 WAIT_FAILED => switch (GetLastError()) {
2764 else => |err| return unexpectedError(err),
2765 },
2766 else => return error.Unexpected,
2767 }
2768}
2769
27702562pub const CreateIoCompletionPortError = error{Unexpected};
27712563
27722564pub fn CreateIoCompletionPort(
......@@ -2878,21 +2670,6 @@ pub fn CloseHandle(hObject: HANDLE) void {
28782670 assert(ntdll.NtClose(hObject) == .SUCCESS);
28792671}
28802672
2881pub const GetStdHandleError = error{
2882 NoStandardHandleAttached,
2883 Unexpected,
2884};
2885
2886pub fn GetStdHandle(handle_id: DWORD) GetStdHandleError!HANDLE {
2887 const handle = kernel32.GetStdHandle(handle_id) orelse return error.NoStandardHandleAttached;
2888 if (handle == INVALID_HANDLE_VALUE) {
2889 switch (GetLastError()) {
2890 else => |err| return unexpectedError(err),
2891 }
2892 }
2893 return handle;
2894}
2895
28962673pub const QueryObjectNameError = error{
28972674 AccessDenied,
28982675 InvalidHandle,
......@@ -3545,6 +3322,12 @@ pub fn nanoSecondsToFileTime(ns: Io.Timestamp) FILETIME {
35453322 };
35463323}
35473324
3325/// Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a
3326/// redundant copy of the uppercase data.
3327pub inline fn toUpperWtf16(c: u16) u16 {
3328 return (if (builtin.os.tag != .windows or @inComptime()) nls.upcaseW else ntdll.RtlUpcaseUnicodeChar)(c);
3329}
3330
35483331/// Compares two WTF16 strings using the equivalent functionality of
35493332/// `RtlEqualUnicodeString` (with case insensitive comparison enabled).
35503333/// This function can be called on any target.
......@@ -3598,19 +3381,12 @@ pub fn eqlIgnoreCaseWtf8(a: []const u8, b: []const u8) bool {
35983381 var a_wtf8_it = std.unicode.Wtf8View.initUnchecked(a).iterator();
35993382 var b_wtf8_it = std.unicode.Wtf8View.initUnchecked(b).iterator();
36003383
3601 // Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a
3602 // redundant copy of the uppercase data.
3603 const upcaseImpl = switch (builtin.os.tag) {
3604 .windows => if (@inComptime()) nls.upcaseW else ntdll.RtlUpcaseUnicodeChar,
3605 else => nls.upcaseW,
3606 };
3607
36083384 while (true) {
36093385 const a_cp = a_wtf8_it.nextCodepoint() orelse break;
36103386 const b_cp = b_wtf8_it.nextCodepoint() orelse return false;
36113387
36123388 if (a_cp <= maxInt(u16) and b_cp <= maxInt(u16)) {
3613 if (a_cp != b_cp and upcaseImpl(@intCast(a_cp)) != upcaseImpl(@intCast(b_cp))) {
3389 if (a_cp != b_cp and toUpperWtf16(@intCast(a_cp)) != toUpperWtf16(@intCast(b_cp))) {
36143390 return false;
36153391 }
36163392 } else if (a_cp != b_cp) {
......@@ -4098,15 +3874,6 @@ pub const Win32Error = @import("windows/win32error.zig").Win32Error;
40983874pub const LANG = @import("windows/lang.zig");
40993875pub const SUBLANG = @import("windows/sublang.zig");
41003876
4101/// The standard input device. Initially, this is the console input buffer, CONIN$.
4102pub const STD_INPUT_HANDLE = maxInt(DWORD) - 10 + 1;
4103
4104/// The standard output device. Initially, this is the active console screen buffer, CONOUT$.
4105pub const STD_OUTPUT_HANDLE = maxInt(DWORD) - 11 + 1;
4106
4107/// The standard error device. Initially, this is the active console screen buffer, CONOUT$.
4108pub const STD_ERROR_HANDLE = maxInt(DWORD) - 12 + 1;
4109
41103877pub const BOOL = c_int;
41113878pub const BOOLEAN = BYTE;
41123879pub const BYTE = u8;
......@@ -5244,6 +5011,8 @@ pub const UNICODE_STRING = extern struct {
52445011 Length: c_ushort,
52455012 MaximumLength: c_ushort,
52465013 Buffer: ?[*]WCHAR,
5014
5015 pub const empty: UNICODE_STRING = .{ .Length = 0, .MaximumLength = 0, .Buffer = null };
52475016};
52485017
52495018pub const ACTIVATION_CONTEXT_DATA = opaque {};
lib/std/os/windows/kernel32.zig-108
......@@ -12,8 +12,6 @@ const FILETIME = windows.FILETIME;
1212const HANDLE = windows.HANDLE;
1313const HANDLER_ROUTINE = windows.HANDLER_ROUTINE;
1414const HMODULE = windows.HMODULE;
15const INIT_ONCE = windows.INIT_ONCE;
16const INIT_ONCE_FN = windows.INIT_ONCE_FN;
1715const LARGE_INTEGER = windows.LARGE_INTEGER;
1816const LPCSTR = windows.LPCSTR;
1917const LPCVOID = windows.LPCVOID;
......@@ -24,7 +22,6 @@ const LPWSTR = windows.LPWSTR;
2422const MODULEENTRY32 = windows.MODULEENTRY32;
2523const OVERLAPPED = windows.OVERLAPPED;
2624const OVERLAPPED_ENTRY = windows.OVERLAPPED_ENTRY;
27const PMEMORY_BASIC_INFORMATION = windows.PMEMORY_BASIC_INFORMATION;
2825const PROCESS_INFORMATION = windows.PROCESS_INFORMATION;
2926const SECURITY_ATTRIBUTES = windows.SECURITY_ATTRIBUTES;
3027const SIZE_T = windows.SIZE_T;
......@@ -37,7 +34,6 @@ const ULONG = windows.ULONG;
3734const ULONG_PTR = windows.ULONG_PTR;
3835const va_list = windows.va_list;
3936const WCHAR = windows.WCHAR;
40const WIN32_FIND_DATAW = windows.WIN32_FIND_DATAW;
4137const Win32Error = windows.Win32Error;
4238const WORD = windows.WORD;
4339
......@@ -59,39 +55,6 @@ pub extern "kernel32" fn CancelIo(
5955 hFile: HANDLE,
6056) callconv(.winapi) BOOL;
6157
62// TODO: Wrapper around NtCancelIoFileEx.
63pub extern "kernel32" fn CancelIoEx(
64 hFile: HANDLE,
65 lpOverlapped: ?*OVERLAPPED,
66) callconv(.winapi) BOOL;
67
68pub extern "kernel32" fn CreateFileW(
69 lpFileName: LPCWSTR,
70 dwDesiredAccess: ACCESS_MASK,
71 dwShareMode: DWORD,
72 lpSecurityAttributes: ?*SECURITY_ATTRIBUTES,
73 dwCreationDisposition: DWORD,
74 dwFlagsAndAttributes: DWORD,
75 hTemplateFile: ?HANDLE,
76) callconv(.winapi) HANDLE;
77
78// TODO A bunch of logic around NtCreateNamedPipe
79pub extern "kernel32" fn CreateNamedPipeW(
80 lpName: LPCWSTR,
81 dwOpenMode: DWORD,
82 dwPipeMode: DWORD,
83 nMaxInstances: DWORD,
84 nOutBufferSize: DWORD,
85 nInBufferSize: DWORD,
86 nDefaultTimeOut: DWORD,
87 lpSecurityAttributes: ?*const SECURITY_ATTRIBUTES,
88) callconv(.winapi) HANDLE;
89
90// TODO: Matches `STD_*_HANDLE` to peb().ProcessParameters.Standard*
91pub extern "kernel32" fn GetStdHandle(
92 nStdHandle: DWORD,
93) callconv(.winapi) ?HANDLE;
94
9558// TODO: Wrapper around NtSetInformationFile + `FILE_POSITION_INFORMATION`.
9659// `FILE_STANDARD_INFORMATION` is also used if dwMoveMethod is `FILE_END`
9760pub extern "kernel32" fn SetFilePointerEx(
......@@ -117,11 +80,6 @@ pub extern "kernel32" fn WriteFile(
11780 in_out_lpOverlapped: ?*OVERLAPPED,
11881) callconv(.winapi) BOOL;
11982
120// TODO: Wrapper around GetStdHandle + NtFlushBuffersFile.
121pub extern "kernel32" fn FlushFileBuffers(
122 hFile: HANDLE,
123) callconv(.winapi) BOOL;
124
12583// TODO: Wrapper around NtSetInformationFile + `FILE_IO_COMPLETION_NOTIFICATION_INFORMATION`.
12684pub extern "kernel32" fn SetFileCompletionNotificationModes(
12785 FileHandle: HANDLE,
......@@ -143,24 +101,6 @@ pub extern "kernel32" fn GetSystemDirectoryW(
143101
144102// I/O - Kernel Objects
145103
146// TODO: Wrapper around GetStdHandle + NtDuplicateObject.
147pub extern "kernel32" fn DuplicateHandle(
148 hSourceProcessHandle: HANDLE,
149 hSourceHandle: HANDLE,
150 hTargetProcessHandle: HANDLE,
151 lpTargetHandle: *HANDLE,
152 dwDesiredAccess: ACCESS_MASK,
153 bInheritHandle: BOOL,
154 dwOptions: DWORD,
155) callconv(.winapi) BOOL;
156
157// TODO: Wrapper around GetStdHandle + NtQueryObject + NtSetInformationObject with .ObjectHandleFlagInformation.
158pub extern "kernel32" fn SetHandleInformation(
159 hObject: HANDLE,
160 dwMask: DWORD,
161 dwFlags: DWORD,
162) callconv(.winapi) BOOL;
163
164104// TODO: Wrapper around NtRemoveIoCompletion.
165105pub extern "kernel32" fn GetQueuedCompletionStatus(
166106 CompletionPort: HANDLE,
......@@ -210,37 +150,6 @@ pub extern "kernel32" fn TerminateProcess(
210150 uExitCode: UINT,
211151) callconv(.winapi) BOOL;
212152
213// TODO: WaitForSingleObjectEx with bAlertable=false.
214pub extern "kernel32" fn WaitForSingleObject(
215 hHandle: HANDLE,
216 dwMilliseconds: DWORD,
217) callconv(.winapi) DWORD;
218
219// TODO: Wrapper for GetStdHandle + NtWaitForSingleObject.
220// Sets up an activation context before calling NtWaitForSingleObject.
221pub extern "kernel32" fn WaitForSingleObjectEx(
222 hHandle: HANDLE,
223 dwMilliseconds: DWORD,
224 bAlertable: BOOL,
225) callconv(.winapi) DWORD;
226
227// TODO: WaitForMultipleObjectsEx with alertable=false
228pub extern "kernel32" fn WaitForMultipleObjects(
229 nCount: DWORD,
230 lpHandle: [*]const HANDLE,
231 bWaitAll: BOOL,
232 dwMilliseconds: DWORD,
233) callconv(.winapi) DWORD;
234
235// TODO: Wrapper around NtWaitForMultipleObjects.
236pub extern "kernel32" fn WaitForMultipleObjectsEx(
237 nCount: DWORD,
238 lpHandle: [*]const HANDLE,
239 bWaitAll: BOOL,
240 dwMilliseconds: DWORD,
241 bAlertable: BOOL,
242) callconv(.winapi) DWORD;
243
244153// Process Management
245154
246155pub extern "kernel32" fn CreateProcessW(
......@@ -256,12 +165,6 @@ pub extern "kernel32" fn CreateProcessW(
256165 lpProcessInformation: *PROCESS_INFORMATION,
257166) callconv(.winapi) BOOL;
258167
259// TODO: implement via ntdll instead
260pub extern "kernel32" fn SleepEx(
261 dwMilliseconds: DWORD,
262 bAlertable: BOOL,
263) callconv(.winapi) DWORD;
264
265168// TODO: Wrapper around NtQueryInformationProcess with `PROCESS_BASIC_INFORMATION`.
266169pub extern "kernel32" fn GetExitCodeProcess(
267170 hProcess: HANDLE,
......@@ -436,14 +339,3 @@ pub extern "kernel32" fn FormatMessageW(
436339
437340// TODO: Getter for teb().LastErrorValue.
438341pub extern "kernel32" fn GetLastError() callconv(.winapi) Win32Error;
439
440// TODO: Wrapper around RtlSetLastWin32Error.
441pub extern "kernel32" fn SetLastError(
442 dwErrCode: Win32Error,
443) callconv(.winapi) void;
444
445// Everything Else
446
447pub extern "kernel32" fn GetSystemInfo(
448 lpSystemInfo: *SYSTEM_INFO,
449) callconv(.winapi) void;
lib/std/os/windows/ntdll.zig+9-4
......@@ -407,6 +407,11 @@ pub extern "ntdll" fn NtCreateNamedPipeFile(
407407 DefaultTimeout: ?*const LARGE_INTEGER,
408408) callconv(.winapi) NTSTATUS;
409409
410pub extern "ntdll" fn NtFlushBuffersFile(
411 FileHandle: HANDLE,
412 IoStatusBlock: *IO_STATUS_BLOCK,
413) callconv(.winapi) NTSTATUS;
414
410415pub extern "ntdll" fn NtMapViewOfSection(
411416 SectionHandle: HANDLE,
412417 ProcessHandle: HANDLE,
......@@ -590,7 +595,7 @@ pub extern "ntdll" fn NtOpenThread(
590595
591596pub extern "ntdll" fn NtCancelSynchronousIoFile(
592597 ThreadHandle: HANDLE,
593 RequestToCancel: ?*IO_STATUS_BLOCK,
598 IoRequestToCancel: ?*IO_STATUS_BLOCK,
594599 IoStatusBlock: *IO_STATUS_BLOCK,
595600) callconv(.winapi) NTSTATUS;
596601
......@@ -606,13 +611,13 @@ pub extern "ntdll" fn NtDelayExecution(
606611 DelayInterval: *const LARGE_INTEGER,
607612) callconv(.winapi) NTSTATUS;
608613
609pub extern "ntdll" fn NtCancelIoFileEx(
614pub extern "ntdll" fn NtCancelIoFile(
610615 FileHandle: HANDLE,
611 IoRequestToCancel: *const IO_STATUS_BLOCK,
612616 IoStatusBlock: *IO_STATUS_BLOCK,
613617) callconv(.winapi) NTSTATUS;
614618
615pub extern "ntdll" fn NtCancelIoFile(
619pub extern "ntdll" fn NtCancelIoFileEx(
616620 FileHandle: HANDLE,
621 IoRequestToCancel: *const IO_STATUS_BLOCK,
617622 IoStatusBlock: *IO_STATUS_BLOCK,
618623) callconv(.winapi) NTSTATUS;
lib/std/process/Environ.zig+438-272
......@@ -4,7 +4,7 @@ const builtin = @import("builtin");
44const native_os = builtin.os.tag;
55
66const std = @import("../std.zig");
7const Allocator = std.mem.Allocator;
7const Allocator = mem.Allocator;
88const assert = std.debug.assert;
99const testing = std.testing;
1010const unicode = std.unicode;
......@@ -14,12 +14,7 @@ const mem = std.mem;
1414/// Unmodified, unprocessed data provided by the operating system.
1515block: Block,
1616
17pub const empty: Environ = .{
18 .block = switch (Block) {
19 void => {},
20 else => &.{},
21 },
22};
17pub const empty: Environ = .{ .block = .empty };
2318
2419/// On WASI without libc, this is `void` because the environment has to be
2520/// queried and heap-allocated at runtime.
......@@ -28,13 +23,65 @@ pub const empty: Environ = .{
2823/// is modified, so a long-lived pointer cannot be used. Therefore, on this
2924/// operating system `void` is also used.
3025pub const Block = switch (native_os) {
31 .windows => void,
26 .windows => GlobalBlock,
3227 .wasi => switch (builtin.link_libc) {
33 false => void,
34 true => [:null]const ?[*:0]const u8,
28 false => GlobalBlock,
29 true => PosixBlock,
3530 },
36 .freestanding, .other => void,
37 else => [:null]const ?[*:0]const u8,
31 .freestanding, .other => GlobalBlock,
32 else => PosixBlock,
33};
34
35pub const GlobalBlock = struct {
36 use_global: bool,
37
38 pub const empty: GlobalBlock = .{ .use_global = false };
39 pub const global: GlobalBlock = .{ .use_global = true };
40
41 pub fn deinit(_: GlobalBlock, _: Allocator) void {}
42};
43
44pub const PosixBlock = struct {
45 slice: [:null]const ?[*:0]const u8,
46
47 pub const empty: PosixBlock = .{ .slice = &.{} };
48
49 pub fn deinit(block: PosixBlock, gpa: Allocator) void {
50 for (block.slice) |entry| gpa.free(mem.span(entry.?));
51 gpa.free(block.slice);
52 }
53
54 pub const View = struct {
55 slice: []const [*:0]const u8,
56
57 pub fn isEmpty(v: View) bool {
58 return v.slice.len == 0;
59 }
60 };
61 pub fn view(block: PosixBlock) View {
62 return .{ .slice = @ptrCast(block.slice) };
63 }
64};
65
66pub const WindowsBlock = struct {
67 slice: [:0]const u16,
68
69 pub const empty: WindowsBlock = .{ .slice = &.{0} };
70
71 pub fn deinit(block: WindowsBlock, gpa: Allocator) void {
72 gpa.free(block.slice);
73 }
74
75 pub const View = struct {
76 ptr: [*:0]const u16,
77
78 pub fn isEmpty(v: View) bool {
79 return v.ptr[0] == 0;
80 }
81 };
82 pub fn view(block: WindowsBlock) View {
83 return .{ .ptr = block.slice.ptr };
84 }
3885};
3986
4087pub const Map = struct {
......@@ -46,47 +93,60 @@ pub const Map = struct {
4693 pub const Size = usize;
4794
4895 pub const EnvNameHashContext = struct {
49 fn upcase(c: u21) u21 {
50 if (c <= std.math.maxInt(u16))
51 return std.os.windows.ntdll.RtlUpcaseUnicodeChar(@as(u16, @intCast(c)));
52 return c;
53 }
54
5596 pub fn hash(self: @This(), s: []const u8) u32 {
5697 _ = self;
57 if (native_os == .windows) {
58 var h = std.hash.Wyhash.init(0);
59 var it = unicode.Wtf8View.initUnchecked(s).iterator();
60 while (it.nextCodepoint()) |cp| {
61 const cp_upper = upcase(cp);
62 h.update(&[_]u8{
63 @as(u8, @intCast((cp_upper >> 16) & 0xff)),
64 @as(u8, @intCast((cp_upper >> 8) & 0xff)),
65 @as(u8, @intCast((cp_upper >> 0) & 0xff)),
66 });
67 }
68 return @truncate(h.final());
98 switch (native_os) {
99 else => return std.array_hash_map.hashString(s),
100 .windows => {
101 var h = std.hash.Wyhash.init(0);
102 var it = unicode.Wtf8View.initUnchecked(s).iterator();
103 while (it.nextCodepoint()) |cp| {
104 const cp_upper = if (std.math.cast(u16, cp)) |wtf16|
105 std.os.windows.toUpperWtf16(wtf16)
106 else
107 cp;
108 h.update(&[_]u8{
109 @truncate(cp_upper >> 0),
110 @truncate(cp_upper >> 8),
111 @truncate(cp_upper >> 16),
112 });
113 }
114 return @truncate(h.final());
115 },
69116 }
70 return std.array_hash_map.hashString(s);
71117 }
72118
73119 pub fn eql(self: @This(), a: []const u8, b: []const u8, b_index: usize) bool {
74120 _ = self;
75121 _ = b_index;
76 if (native_os == .windows) {
77 var it_a = unicode.Wtf8View.initUnchecked(a).iterator();
78 var it_b = unicode.Wtf8View.initUnchecked(b).iterator();
79 while (true) {
80 const c_a = it_a.nextCodepoint() orelse break;
81 const c_b = it_b.nextCodepoint() orelse return false;
82 if (upcase(c_a) != upcase(c_b))
83 return false;
84 }
85 return if (it_b.nextCodepoint()) |_| false else true;
86 }
87 return std.array_hash_map.eqlString(a, b);
122 return eqlKeys(a, b);
88123 }
89124 };
125 fn eqlKeys(a: []const u8, b: []const u8) bool {
126 return switch (native_os) {
127 else => std.array_hash_map.eqlString(a, b),
128 .windows => std.os.windows.eqlIgnoreCaseWtf8(a, b),
129 };
130 }
131
132 pub fn validateKey(key: []const u8) bool {
133 switch (native_os) {
134 else => return key.len > 0 and mem.findAny(u8, key, &.{ 0, '=' }) == null,
135 .windows => {
136 if (!unicode.wtf8ValidateSlice(key)) return false;
137 var it = unicode.Wtf8View.initUnchecked(key).iterator();
138 switch (it.nextCodepoint() orelse return false) {
139 0 => return false,
140 else => {},
141 }
142 while (it.nextCodepoint()) |cp| switch (cp) {
143 0, '=' => return false,
144 else => {},
145 };
146 return true;
147 },
148 }
149 }
90150
91151 /// Create a Map backed by a specific allocator.
92152 /// That allocator will be used for both backing allocations
......@@ -99,30 +159,71 @@ pub const Map = struct {
99159 /// of the stored keys and values.
100160 pub fn deinit(self: *Map) void {
101161 const gpa = self.allocator;
102 var it = self.array_hash_map.iterator();
103 while (it.next()) |entry| {
104 gpa.free(entry.key_ptr.*);
105 gpa.free(entry.value_ptr.*);
106 }
162 for (self.keys()) |key| gpa.free(key);
163 for (self.values()) |value| gpa.free(value);
107164 self.array_hash_map.deinit(gpa);
108165 self.* = undefined;
109166 }
110167
111 pub fn keys(m: *const Map) [][]const u8 {
112 return m.array_hash_map.keys();
168 pub fn keys(map: *const Map) [][]const u8 {
169 return map.array_hash_map.keys();
170 }
171
172 pub fn values(map: *const Map) [][]const u8 {
173 return map.array_hash_map.values();
174 }
175
176 pub fn putPosixBlock(map: *Map, view: PosixBlock.View) Allocator.Error!void {
177 for (view.slice) |entry| {
178 var entry_i: usize = 0;
179 while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {}
180 const key = entry[0..entry_i];
181
182 var end_i: usize = entry_i;
183 while (entry[end_i] != 0) : (end_i += 1) {}
184 const value = entry[entry_i + 1 .. end_i];
185
186 try map.put(key, value);
187 }
113188 }
114189
115 pub fn values(m: *const Map) [][]const u8 {
116 return m.array_hash_map.values();
190 pub fn putWindowsBlock(map: *Map, view: WindowsBlock.View) Allocator.Error!void {
191 var i: usize = 0;
192 while (view.ptr[i] != 0) {
193 const key_start = i;
194
195 // There are some special environment variables that start with =,
196 // so we need a special case to not treat = as a key/value separator
197 // if it's the first character.
198 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
199 if (view.ptr[key_start] == '=') i += 1;
200
201 while (view.ptr[i] != 0 and view.ptr[i] != '=') : (i += 1) {}
202 const key_w = view.ptr[key_start..i];
203 const key = try unicode.wtf16LeToWtf8Alloc(map.allocator, key_w);
204 errdefer map.allocator.free(key);
205
206 if (view.ptr[i] == '=') i += 1;
207
208 const value_start = i;
209 while (view.ptr[i] != 0) : (i += 1) {}
210 const value_w = view.ptr[value_start..i];
211 const value = try unicode.wtf16LeToWtf8Alloc(map.allocator, value_w);
212 errdefer map.allocator.free(value);
213
214 i += 1; // skip over null byte
215
216 try map.putMove(key, value);
217 }
117218 }
118219
119220 /// Same as `put` but the key and value become owned by the Map rather
120221 /// than being copied.
121222 /// If `putMove` fails, the ownership of key and value does not transfer.
122223 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
123 pub fn putMove(self: *Map, key: []u8, value: []u8) !void {
224 pub fn putMove(self: *Map, key: []u8, value: []u8) Allocator.Error!void {
225 assert(validateKey(key));
124226 const gpa = self.allocator;
125 assert(unicode.wtf8ValidateSlice(key));
126227 const get_or_put = try self.array_hash_map.getOrPut(gpa, key);
127228 if (get_or_put.found_existing) {
128229 gpa.free(get_or_put.key_ptr.*);
......@@ -134,8 +235,8 @@ pub const Map = struct {
134235
135236 /// `key` and `value` are copied into the Map.
136237 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
137 pub fn put(self: *Map, key: []const u8, value: []const u8) !void {
138 assert(unicode.wtf8ValidateSlice(key));
238 pub fn put(self: *Map, key: []const u8, value: []const u8) Allocator.Error!void {
239 assert(validateKey(key));
139240 const gpa = self.allocator;
140241 const value_copy = try gpa.dupe(u8, value);
141242 errdefer gpa.free(value_copy);
......@@ -155,7 +256,7 @@ pub const Map = struct {
155256 /// The returned pointer is invalidated if the map resizes.
156257 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
157258 pub fn getPtr(self: Map, key: []const u8) ?*[]const u8 {
158 assert(unicode.wtf8ValidateSlice(key));
259 assert(validateKey(key));
159260 return self.array_hash_map.getPtr(key);
160261 }
161262
......@@ -164,11 +265,12 @@ pub const Map = struct {
164265 /// key is removed from the map.
165266 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
166267 pub fn get(self: Map, key: []const u8) ?[]const u8 {
167 assert(unicode.wtf8ValidateSlice(key));
268 assert(validateKey(key));
168269 return self.array_hash_map.get(key);
169270 }
170271
171272 pub fn contains(m: *const Map, key: []const u8) bool {
273 assert(validateKey(key));
172274 return m.array_hash_map.contains(key);
173275 }
174276
......@@ -181,7 +283,7 @@ pub const Map = struct {
181283 /// This invalidates the value returned by get() for this key.
182284 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
183285 pub fn swapRemove(self: *Map, key: []const u8) bool {
184 assert(unicode.wtf8ValidateSlice(key));
286 assert(validateKey(key));
185287 const kv = self.array_hash_map.fetchSwapRemove(key) orelse return false;
186288 const gpa = self.allocator;
187289 gpa.free(kv.key);
......@@ -198,7 +300,7 @@ pub const Map = struct {
198300 /// This invalidates the value returned by get() for this key.
199301 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
200302 pub fn orderedRemove(self: *Map, key: []const u8) bool {
201 assert(unicode.wtf8ValidateSlice(key));
303 assert(validateKey(key));
202304 const kv = self.array_hash_map.fetchOrderedRemove(key) orelse return false;
203305 const gpa = self.allocator;
204306 gpa.free(kv.key);
......@@ -233,105 +335,120 @@ pub const Map = struct {
233335
234336 /// Creates a null-delimited environment variable block in the format
235337 /// expected by POSIX, from a hash map plus options.
236 pub fn createBlockPosix(
338 pub fn createPosixBlock(
237339 map: *const Map,
238 arena: Allocator,
239 options: CreateBlockPosixOptions,
240 ) Allocator.Error![:null]?[*:0]u8 {
340 gpa: Allocator,
341 options: CreatePosixBlockOptions,
342 ) Allocator.Error!PosixBlock {
241343 const ZigProgressAction = enum { nothing, edit, delete, add };
242 const zig_progress_action: ZigProgressAction = a: {
243 const fd = options.zig_progress_fd orelse break :a .nothing;
244 const exists = map.get("ZIG_PROGRESS") != null;
344 const zig_progress_action: ZigProgressAction = action: {
345 const fd = options.zig_progress_fd orelse break :action .nothing;
346 const exists = map.contains("ZIG_PROGRESS");
245347 if (fd >= 0) {
246 break :a if (exists) .edit else .add;
348 break :action if (exists) .edit else .add;
247349 } else {
248 if (exists) break :a .delete;
350 if (exists) break :action .delete;
249351 }
250 break :a .nothing;
352 break :action .nothing;
251353 };
252354
253 const envp_count: usize = c: {
254 var c: usize = map.count();
355 const envp = try gpa.allocSentinel(?[*:0]u8, len: {
356 var len: usize = map.count();
255357 switch (zig_progress_action) {
256 .add => c += 1,
257 .delete => c -= 1,
358 .add => len += 1,
359 .delete => len -= 1,
258360 .nothing, .edit => {},
259361 }
260 break :c c;
261 };
262
263 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
264 var i: usize = 0;
362 break :len len;
363 }, null);
364 var envp_len: usize = 0;
365 errdefer {
366 envp[envp_len] = null;
367 PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa);
368 }
265369
266370 if (zig_progress_action == .add) {
267 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
268 i += 1;
371 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
372 envp_len += 1;
269373 }
270374
271 {
272 var it = map.iterator();
273 while (it.next()) |pair| {
274 if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) {
275 .add => unreachable,
276 .delete => continue,
277 .edit => {
278 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{
279 pair.key_ptr.*, options.zig_progress_fd.?,
280 }, 0);
281 i += 1;
282 continue;
283 },
284 .nothing => {},
285 };
286
287 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0);
288 i += 1;
289 }
375 for (map.keys(), map.values()) |key, value| {
376 if (mem.eql(u8, key, "ZIG_PROGRESS")) switch (zig_progress_action) {
377 .add => unreachable,
378 .delete => continue,
379 .edit => {
380 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={d}", .{
381 key, options.zig_progress_fd.?,
382 }, 0);
383 envp_len += 1;
384 continue;
385 },
386 .nothing => {},
387 };
388
389 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={s}", .{ key, value }, 0);
390 envp_len += 1;
290391 }
291392
292 assert(i == envp_count);
293 return envp_buf;
393 assert(envp_len == envp.len);
394 return .{ .slice = envp };
294395 }
295396
296397 /// Caller owns result.
297 pub fn createBlockWindows(map: *const Map, gpa: Allocator) error{ OutOfMemory, InvalidWtf8 }![:0]u16 {
398 pub fn createWindowsBlock(
399 map: *const Map,
400 gpa: Allocator,
401 options: CreateWindowsBlockOptions,
402 ) error{ OutOfMemory, InvalidWtf8 }!WindowsBlock {
298403 // count bytes needed
299 const max_chars_needed = x: {
300 // Only need 2 trailing NUL code units for an empty environment
301 var max_chars_needed: usize = if (map.count() == 0) 2 else 1;
302 var it = map.iterator();
303 while (it.next()) |pair| {
304 // +1 for '='
305 // +1 for null byte
306 max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2;
404 const max_chars_needed = max_chars_needed: {
405 var max_chars_needed: usize = "\x00".len;
406 if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
407 max_chars_needed += std.fmt.count("ZIG_PROGRESS={d}\x00", .{@intFromPtr(handle)});
408 };
409 for (map.keys(), map.values()) |key, value| {
410 if (options.zig_progress_handle != null and eqlKeys(key, "ZIG_PROGRESS")) continue;
411 max_chars_needed += key.len + "=".len + value.len + "\x00".len;
307412 }
308 break :x max_chars_needed;
413 break :max_chars_needed @max("\x00\x00".len, max_chars_needed);
309414 };
310 const result = try gpa.alloc(u16, max_chars_needed);
311 errdefer gpa.free(result);
415 const block = try gpa.alloc(u16, max_chars_needed);
416 errdefer gpa.free(block);
312417
313 var it = map.iterator();
314418 var i: usize = 0;
315 while (it.next()) |pair| {
316 i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
317 result[i] = '=';
419 if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
420 @memcpy(
421 block[i..][0.."ZIG_PROGRESS=".len],
422 &[_]u16{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S', '=' },
423 );
424 i += "ZIG_PROGRESS=".len;
425 var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined;
426 const value = std.fmt.bufPrint(&value_buf, "{d}", .{@intFromPtr(handle)}) catch unreachable;
427 for (block[i..][0..value.len], value) |*r, v| r.* = v;
428 i += value.len;
429 block[i] = 0;
318430 i += 1;
319 i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
320 result[i] = 0;
431 };
432 for (map.keys(), map.values()) |key, value| {
433 if (options.zig_progress_handle != null and eqlKeys(key, "ZIG_PROGRESS")) continue;
434 i += try unicode.wtf8ToWtf16Le(block[i..], key);
435 block[i] = '=';
436 i += 1;
437 i += try unicode.wtf8ToWtf16Le(block[i..], value);
438 block[i] = 0;
321439 i += 1;
322440 }
323 result[i] = 0;
324 i += 1;
325441 // An empty environment is a special case that requires a redundant
326442 // NUL terminator. CreateProcess will read the second code unit even
327443 // though theoretically the first should be enough to recognize that the
328444 // environment is empty (see https://nullprogram.com/blog/2023/08/23/)
329 if (map.count() == 0) {
330 result[i] = 0;
445 for (0..2) |_| {
446 block[i] = 0;
331447 i += 1;
332 }
333 const reallocated = try gpa.realloc(result, i);
334 return reallocated[0 .. i - 1 :0];
448 if (i >= 2) break;
449 } else unreachable;
450 const reallocated = try gpa.realloc(block, i);
451 return .{ .slice = reallocated[0 .. i - 1 :0] };
335452 }
336453};
337454
......@@ -344,13 +461,18 @@ pub const CreateMapError = error{
344461
345462/// Allocates a `Map` and copies environment block into it.
346463pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
347 if (native_os == .windows)
348 return createMapWide(std.os.windows.peb().ProcessParameters.Environment, allocator);
349
350 var result = Map.init(allocator);
351 errdefer result.deinit();
464 var map = Map.init(allocator);
465 errdefer map.deinit();
466 if (native_os == .windows) empty: {
467 if (!env.block.use_global) break :empty;
468
469 const peb = std.os.windows.peb();
470 assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
471 defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
472 try map.putWindowsBlock(.{ .ptr = peb.ProcessParameters.Environment });
473 } else if (native_os == .wasi and !builtin.link_libc) empty: {
474 if (!env.block.use_global) break :empty;
352475
353 if (native_os == .wasi and !builtin.link_libc) {
354476 var environ_count: usize = undefined;
355477 var environ_buf_size: usize = undefined;
356478
......@@ -360,7 +482,7 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
360482 }
361483
362484 if (environ_count == 0) {
363 return result;
485 return map;
364486 }
365487
366488 const environ = try allocator.alloc([*:0]u8, environ_count);
......@@ -373,63 +495,9 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
373495 return posix.unexpectedErrno(environ_get_ret);
374496 }
375497
376 for (environ) |line| {
377 const pair = mem.sliceTo(line, 0);
378 var parts = mem.splitScalar(u8, pair, '=');
379 const key = parts.first();
380 const value = parts.rest();
381 try result.put(key, value);
382 }
383 return result;
384 } else {
385 for (env.block) |opt_line| {
386 const line = opt_line.?;
387 var line_i: usize = 0;
388 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
389 const key = line[0..line_i];
390
391 var end_i: usize = line_i;
392 while (line[end_i] != 0) : (end_i += 1) {}
393 const value = line[line_i + 1 .. end_i];
394
395 try result.put(key, value);
396 }
397 return result;
398 }
399}
400
401pub fn createMapWide(ptr: [*:0]u16, gpa: Allocator) CreateMapError!Map {
402 var result = Map.init(gpa);
403 errdefer result.deinit();
404
405 var i: usize = 0;
406 while (ptr[i] != 0) {
407 const key_start = i;
408
409 // There are some special environment variables that start with =,
410 // so we need a special case to not treat = as a key/value separator
411 // if it's the first character.
412 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
413 if (ptr[key_start] == '=') i += 1;
414
415 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
416 const key_w = ptr[key_start..i];
417 const key = try unicode.wtf16LeToWtf8Alloc(gpa, key_w);
418 errdefer gpa.free(key);
419
420 if (ptr[i] == '=') i += 1;
421
422 const value_start = i;
423 while (ptr[i] != 0) : (i += 1) {}
424 const value_w = ptr[value_start..i];
425 const value = try unicode.wtf16LeToWtf8Alloc(gpa, value_w);
426 errdefer gpa.free(value);
427
428 i += 1; // skip over null byte
429
430 try result.putMove(key, value);
431 }
432 return result;
498 try map.putPosixBlock(.{ .slice = environ });
499 } else try map.putPosixBlock(env.block.view());
500 return map;
433501}
434502
435503pub const ContainsError = error{
......@@ -451,6 +519,7 @@ pub const ContainsError = error{
451519/// * `containsConstant`
452520/// * `containsUnempty`
453521pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool {
522 if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8;
454523 var map = try createMap(environ, gpa);
455524 defer map.deinit();
456525 return map.contains(key);
......@@ -464,6 +533,7 @@ pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError
464533/// * `containsUnemptyConstant`
465534/// * `contains`
466535pub fn containsUnempty(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool {
536 if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8;
467537 var map = try createMap(environ, gpa);
468538 defer map.deinit();
469539 const value = map.get(key) orelse return false;
......@@ -516,16 +586,15 @@ pub inline fn containsUnemptyConstant(environ: Environ, comptime key: []const u8
516586/// * `createMap`
517587pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 {
518588 if (mem.findScalar(u8, key, '=') != null) return null;
519 for (environ.block) |opt_line| {
520 const line = opt_line.?;
521 var line_i: usize = 0;
522 while (line[line_i] != 0) : (line_i += 1) {
523 if (line_i == key.len) break;
524 if (line[line_i] != key[line_i]) break;
589 for (environ.block.view().slice) |entry| {
590 var entry_i: usize = 0;
591 while (entry[entry_i] != 0) : (entry_i += 1) {
592 if (entry_i == key.len) break;
593 if (entry[entry_i] != key[entry_i]) break;
525594 }
526 if ((line_i != key.len) or (line[line_i] != '=')) continue;
595 if ((entry_i != key.len) or (entry[entry_i] != '=')) continue;
527596
528 return mem.sliceTo(line + line_i + 1, 0);
597 return mem.sliceTo(entry + entry_i + 1, 0);
529598 }
530599 return null;
531600}
......@@ -541,14 +610,16 @@ pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 {
541610/// * `containsConstant`
542611/// * `contains`
543612pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 {
544 comptime assert(native_os == .windows);
545 comptime assert(@TypeOf(environ.block) == void);
546
547613 // '=' anywhere but the start makes this an invalid environment variable name.
548614 const key_slice = mem.sliceTo(key, 0);
549 if (key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') != null) return null;
615 assert(key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') == null);
550616
551 const ptr = std.os.windows.peb().ProcessParameters.Environment;
617 if (!environ.block.use_global) return null;
618
619 const peb = std.os.windows.peb();
620 assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
621 defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
622 const ptr = peb.ProcessParameters.Environment;
552623
553624 var i: usize = 0;
554625 while (ptr[i] != 0) {
......@@ -558,8 +629,7 @@ pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 {
558629 // so we need a special case to not treat = as a key/value separator
559630 // if it's the first character.
560631 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
561 const equal_search_start: usize = if (key_value[0] == '=') 1 else 0;
562 const equal_index = mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse {
632 const equal_index = mem.findScalarPos(u16, key_value, 1, '=') orelse {
563633 // This is enforced by CreateProcess.
564634 // If violated, CreateProcess will fail with INVALID_PARAMETER.
565635 unreachable; // must contain a =
......@@ -598,13 +668,14 @@ pub const GetAllocError = error{
598668/// See also:
599669/// * `createMap`
600670pub fn getAlloc(environ: Environ, gpa: Allocator, key: []const u8) GetAllocError![]u8 {
671 if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8;
601672 var map = createMap(environ, gpa) catch return error.OutOfMemory;
602673 defer map.deinit();
603674 const val = map.get(key) orelse return error.EnvironmentVariableMissing;
604675 return gpa.dupe(u8, val);
605676}
606677
607pub const CreateBlockPosixOptions = struct {
678pub const CreatePosixBlockOptions = struct {
608679 /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
609680 /// If non-null, negative means to remove the environment variable, and >= 0
610681 /// means to provide it with the given integer.
......@@ -613,67 +684,147 @@ pub const CreateBlockPosixOptions = struct {
613684
614685/// Creates a null-delimited environment variable block in the format expected
615686/// by POSIX, from a different one.
616pub fn createBlockPosix(
687pub fn createPosixBlock(
617688 existing: Environ,
618 arena: Allocator,
619 options: CreateBlockPosixOptions,
620) Allocator.Error![:null]?[*:0]u8 {
621 const contains_zig_progress = for (existing.block) |opt_line| {
622 if (mem.eql(u8, mem.sliceTo(opt_line.?, '='), "ZIG_PROGRESS")) break true;
689 gpa: Allocator,
690 options: CreatePosixBlockOptions,
691) Allocator.Error!PosixBlock {
692 const contains_zig_progress = for (existing.block.view().slice) |entry| {
693 if (mem.eql(u8, mem.sliceTo(entry, '='), "ZIG_PROGRESS")) break true;
623694 } else false;
624695
625696 const ZigProgressAction = enum { nothing, edit, delete, add };
626 const zig_progress_action: ZigProgressAction = a: {
627 const fd = options.zig_progress_fd orelse break :a .nothing;
697 const zig_progress_action: ZigProgressAction = action: {
698 const fd = options.zig_progress_fd orelse break :action .nothing;
628699 if (fd >= 0) {
629 break :a if (contains_zig_progress) .edit else .add;
700 break :action if (contains_zig_progress) .edit else .add;
630701 } else {
631 if (contains_zig_progress) break :a .delete;
702 if (contains_zig_progress) break :action .delete;
632703 }
633 break :a .nothing;
704 break :action .nothing;
634705 };
635706
636 const envp_count: usize = c: {
637 var count: usize = existing.block.len;
707 const envp = try gpa.allocSentinel(?[*:0]u8, len: {
708 var len: usize = existing.block.slice.len;
638709 switch (zig_progress_action) {
639 .add => count += 1,
640 .delete => count -= 1,
710 .add => len += 1,
711 .delete => len -= 1,
641712 .nothing, .edit => {},
642713 }
643 break :c count;
644 };
645
646 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
647 var i: usize = 0;
648 var existing_index: usize = 0;
649
714 break :len len;
715 }, null);
716 var envp_len: usize = 0;
717 errdefer {
718 envp[envp_len] = null;
719 PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa);
720 }
650721 if (zig_progress_action == .add) {
651 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
652 i += 1;
722 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
723 envp_len += 1;
653724 }
654725
655 while (existing.block[existing_index]) |line| : (existing_index += 1) {
656 if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
726 var existing_index: usize = 0;
727 while (existing.block.slice[existing_index]) |entry| : (existing_index += 1) {
728 if (mem.eql(u8, mem.sliceTo(entry, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
657729 .add => unreachable,
658730 .delete => continue,
659731 .edit => {
660 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
661 i += 1;
732 envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
733 envp_len += 1;
662734 continue;
663735 },
664736 .nothing => {},
665737 };
666 envp_buf[i] = try arena.dupeZ(u8, mem.span(line));
667 i += 1;
738 envp[envp_len] = try gpa.dupeZ(u8, mem.span(entry));
739 envp_len += 1;
668740 }
669741
670 assert(i == envp_count);
671 return envp_buf;
742 assert(envp_len == envp.len);
743 return .{ .slice = envp };
672744}
673745
674test "Map.createBlock" {
675 const allocator = testing.allocator;
676 var envmap = Map.init(allocator);
746pub const CreateWindowsBlockOptions = struct {
747 /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
748 /// If non-null, `std.os.windows.INVALID_HANDLE_VALUE` means to remove the
749 /// environment variable, otherwise provide it with the given handle as an integer.
750 zig_progress_handle: ?std.os.windows.HANDLE = null,
751};
752
753/// Creates a null-delimited environment variable block in the format expected
754/// by POSIX, from a different one.
755pub fn createWindowsBlock(
756 existing: Environ,
757 gpa: Allocator,
758 options: CreateWindowsBlockOptions,
759) Allocator.Error!WindowsBlock {
760 if (!existing.block.use_global) return .{
761 .slice = try gpa.dupeSentinel(u16, WindowsBlock.empty.slice, 0),
762 };
763 const peb = std.os.windows.peb();
764 assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
765 defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
766 const existing_block = peb.ProcessParameters.Environment;
767 var ranges: [2]struct { start: usize, end: usize } = undefined;
768 var ranges_len: usize = 0;
769 ranges[ranges_len].start = 0;
770 const zig_progress_key = [_]u16{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S', '=' };
771 const needed_len = needed_len: {
772 var needed_len: usize = "\x00".len;
773 if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
774 needed_len += std.fmt.count("ZIG_PROGRESS={d}\x00", .{@intFromPtr(handle)});
775 };
776 var i: usize = 0;
777 while (existing_block[i] != 0) {
778 const start = i;
779 const entry = mem.sliceTo(existing_block[start..], 0);
780 i += entry.len + "\x00".len;
781 if (options.zig_progress_handle != null and entry.len >= zig_progress_key.len and
782 std.os.windows.eqlIgnoreCaseWtf16(entry[0..zig_progress_key.len], &zig_progress_key))
783 {
784 ranges[ranges_len].end = start;
785 ranges_len += 1;
786 ranges[ranges_len].start = i;
787 } else needed_len += entry.len + "\x00".len;
788 }
789 ranges[ranges_len].end = i;
790 ranges_len += 1;
791 break :needed_len @max("\x00\x00".len, needed_len);
792 };
793 const block = try gpa.alloc(u16, needed_len);
794 errdefer gpa.free(block);
795 var i: usize = 0;
796 if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
797 @memcpy(block[i..][0..zig_progress_key.len], &zig_progress_key);
798 i += zig_progress_key.len;
799 var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined;
800 const value = std.fmt.bufPrint(&value_buf, "{d}", .{@intFromPtr(handle)}) catch unreachable;
801 for (block[i..][0..value.len], value) |*r, v| r.* = v;
802 i += value.len;
803 block[i] = 0;
804 i += 1;
805 };
806 for (ranges[0..ranges_len]) |range| {
807 const range_len = range.end - range.start;
808 @memcpy(block[i..][0..range_len], existing_block[range.start..range.end]);
809 i += range_len;
810 }
811 // An empty environment is a special case that requires a redundant
812 // NUL terminator. CreateProcess will read the second code unit even
813 // though theoretically the first should be enough to recognize that the
814 // environment is empty (see https://nullprogram.com/blog/2023/08/23/)
815 for (0..2) |_| {
816 block[i] = 0;
817 i += 1;
818 if (i >= 2) break;
819 } else unreachable;
820 assert(i == block.len);
821 return .{ .slice = block[0 .. i - 1 :0] };
822}
823
824test "Map.createPosixBlock" {
825 const gpa = testing.allocator;
826
827 var envmap = Map.init(gpa);
677828 defer envmap.deinit();
678829
679830 try envmap.put("HOME", "/home/ifreund");
......@@ -682,29 +833,24 @@ test "Map.createBlock" {
682833 try envmap.put("DEBUGINFOD_URLS", " ");
683834 try envmap.put("XCURSOR_SIZE", "24");
684835
685 var arena = std.heap.ArenaAllocator.init(allocator);
686 defer arena.deinit();
687 const environ = try envmap.createBlockPosix(arena.allocator(), .{});
836 const block = try envmap.createPosixBlock(gpa, .{});
837 defer block.deinit(gpa);
688838
689 try testing.expectEqual(@as(usize, 5), environ.len);
839 try testing.expectEqual(@as(usize, 5), block.slice.len);
690840
691 inline for (.{
841 for (&[_][]const u8{
692842 "HOME=/home/ifreund",
693843 "WAYLAND_DISPLAY=wayland-1",
694844 "DISPLAY=:1",
695845 "DEBUGINFOD_URLS= ",
696846 "XCURSOR_SIZE=24",
697 }) |target| {
698 for (environ) |variable| {
699 if (mem.eql(u8, mem.span(variable orelse continue), target)) break;
700 } else {
701 try testing.expect(false); // Environment variable not found
702 }
703 }
847 }, block.slice) |expected, actual| try testing.expectEqualStrings(expected, mem.span(actual.?));
704848}
705849
706850test Map {
707 var env = Map.init(testing.allocator);
851 const gpa = testing.allocator;
852
853 var env: Map = .init(gpa);
708854 defer env.deinit();
709855
710856 try env.put("SOMETHING_NEW", "hello");
......@@ -740,6 +886,7 @@ test Map {
740886 try testing.expect(env.swapRemove("SOMETHING_NEW"));
741887 try testing.expect(!env.swapRemove("SOMETHING_NEW"));
742888 try testing.expect(env.get("SOMETHING_NEW") == null);
889 try testing.expect(!env.contains("SOMETHING_NEW"));
743890
744891 try testing.expectEqual(@as(Map.Size, 1), env.count());
745892
......@@ -749,10 +896,10 @@ test Map {
749896 try testing.expectEqualStrings("something else", env.get("кириллица").?);
750897
751898 // and WTF-8 that's not valid UTF-8
752 const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{
899 const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(gpa, &[_]u16{
753900 mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate
754901 });
755 defer testing.allocator.free(wtf8_with_surrogate_pair);
902 defer gpa.free(wtf8_with_surrogate_pair);
756903
757904 try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair);
758905 try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?);
......@@ -769,13 +916,9 @@ test "convert from Environ to Map and back again" {
769916 defer map.deinit();
770917 try map.put("FOO", "BAR");
771918 try map.put("A", "");
772 try map.put("", "B");
773
774 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
775 defer arena_allocator.deinit();
776 const arena = arena_allocator.allocator();
777919
778 const environ: Environ = .{ .block = try map.createBlockPosix(arena, .{}) };
920 const environ: Environ = .{ .block = try map.createPosixBlock(gpa, .{}) };
921 defer environ.block.deinit(gpa);
779922
780923 try testing.expectEqual(true, environ.contains(gpa, "FOO"));
781924 try testing.expectEqual(false, environ.contains(gpa, "BAR"));
......@@ -783,7 +926,6 @@ test "convert from Environ to Map and back again" {
783926 try testing.expectEqual(true, environ.containsConstant("A"));
784927 try testing.expectEqual(false, environ.containsUnempty(gpa, "A"));
785928 try testing.expectEqual(false, environ.containsUnemptyConstant("A"));
786 try testing.expectEqual(true, environ.contains(gpa, ""));
787929 try testing.expectEqual(false, environ.contains(gpa, "B"));
788930
789931 try testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(gpa, "BOGUS"));
......@@ -800,23 +942,47 @@ test "convert from Environ to Map and back again" {
800942 try testing.expectEqualDeep(map.values(), map2.values());
801943}
802944
803test createMapWide {
804 if (builtin.cpu.arch.endian() == .big) return error.SkipZigTest; // TODO
945test "Map.putPosixBlock" {
946 const gpa = testing.allocator;
947
948 var map: Map = .init(gpa);
949 defer map.deinit();
950
951 try map.put("FOO", "BAR");
952 try map.put("A", "");
953 try map.put("ZIG_PROGRESS", "unchanged");
954
955 const block = try map.createPosixBlock(gpa, .{});
956 defer block.deinit(gpa);
957
958 var map2: Map = .init(gpa);
959 defer map2.deinit();
960 try map2.putPosixBlock(block.view());
961
962 try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "ZIG_PROGRESS" }, map2.keys());
963 try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "unchanged" }, map2.values());
964}
965
966test "Map.putWindowsBlock" {
967 if (native_os != .windows) return;
805968
806969 const gpa = testing.allocator;
807970
808971 var map: Map = .init(gpa);
809972 defer map.deinit();
973
810974 try map.put("FOO", "BAR");
811975 try map.put("A", "");
812 try map.put("", "B");
976 try map.put("=B", "");
977 try map.put("ZIG_PROGRESS", "unchanged");
813978
814 const environ: [:0]u16 = try map.createBlockWindows(gpa);
815 defer gpa.free(environ);
979 const block = try map.createWindowsBlock(gpa, .{});
980 defer block.deinit(gpa);
816981
817 var map2 = try createMapWide(environ, gpa);
982 var map2: Map = .init(gpa);
818983 defer map2.deinit();
984 try map2.putWindowsBlock(block.view());
819985
820 try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B" }, map2.keys());
821 try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "" }, map2.values());
986 try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B", "ZIG_PROGRESS" }, map2.keys());
987 try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "", "unchanged" }, map2.values());
822988}
lib/std/start.zig+9-8
......@@ -90,15 +90,15 @@ fn _DllMainCRTStartup(
9090fn wasm_freestanding_start() callconv(.c) void {
9191 // This is marked inline because for some reason LLVM in
9292 // release mode fails to inline it, and we want fewer call frames in stack traces.
93 _ = @call(.always_inline, callMain, .{ {}, {} });
93 _ = @call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global });
9494}
9595
9696fn startWasi() callconv(.c) void {
9797 // The function call is marked inline because for some reason LLVM in
9898 // release mode fails to inline it, and we want fewer call frames in stack traces.
9999 switch (builtin.wasi_exec_model) {
100 .reactor => _ = @call(.always_inline, callMain, .{ {}, {} }),
101 .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{ {}, {} })),
100 .reactor => _ = @call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global }),
101 .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global })),
102102 }
103103}
104104
......@@ -476,7 +476,7 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn {
476476 const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
477477 const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)];
478478
479 std.os.windows.ntdll.RtlExitUserProcess(callMain(cmd_line_w, {}));
479 std.os.windows.ntdll.RtlExitUserProcess(callMain(cmd_line_w, .global));
480480}
481481
482482fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn {
......@@ -620,13 +620,14 @@ fn expandStackSize(phdrs: []elf.Phdr) void {
620620}
621621
622622inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) u8 {
623 const env_block: std.process.Environ.Block = .{ .slice = envp };
623624 if (std.Options.debug_threaded_io) |t| {
624625 if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0];
625 t.environ = .{ .process_environ = .{ .block = envp } };
626 t.environ = .{ .process_environ = .{ .block = env_block } };
626627 }
627628 std.Thread.maybeAttachSignalStack();
628629 std.debug.maybeEnableSegfaultHandler();
629 return callMain(argv[0..argc], envp);
630 return callMain(argv[0..argc], env_block);
630631}
631632
632633fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int {
......@@ -648,7 +649,7 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal
648649 std.debug.maybeEnableSegfaultHandler();
649650 const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
650651 const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)];
651 return callMain(cmd_line_w, {});
652 return callMain(cmd_line_w, .global);
652653 },
653654 else => {},
654655 }
......@@ -661,7 +662,7 @@ fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int {
661662 if (@sizeOf(std.Io.Threaded.Argv0) != 0) {
662663 if (std.Options.debug_threaded_io) |t| t.argv0.value = argv[0];
663664 }
664 return callMain(argv, &.{});
665 return callMain(argv, .empty);
665666}
666667
667668/// General error message for a malformed return type
test/standalone/env_vars/main.zig-23
......@@ -12,14 +12,10 @@ pub fn main(init: std.process.Init) !void {
1212 // containsUnempty
1313 {
1414 try std.testing.expect(try environ.containsUnempty(allocator, "FOO"));
15 try std.testing.expect(!(try environ.containsUnempty(allocator, "FOO=")));
16 try std.testing.expect(!(try environ.containsUnempty(allocator, "FO")));
17 try std.testing.expect(!(try environ.containsUnempty(allocator, "FOOO")));
1815 if (builtin.os.tag == .windows) {
1916 try std.testing.expect(try environ.containsUnempty(allocator, "foo"));
2017 }
2118 try std.testing.expect(try environ.containsUnempty(allocator, "EQUALS"));
22 try std.testing.expect(!(try environ.containsUnempty(allocator, "EQUALS=ABC")));
2319 try std.testing.expect(try environ.containsUnempty(allocator, "КИРиллИЦА"));
2420 if (builtin.os.tag == .windows) {
2521 try std.testing.expect(try environ.containsUnempty(allocator, "кирИЛЛица"));
......@@ -35,14 +31,10 @@ pub fn main(init: std.process.Init) !void {
3531 // containsUnemptyConstant
3632 {
3733 try std.testing.expect(environ.containsUnemptyConstant("FOO"));
38 try std.testing.expect(!environ.containsUnemptyConstant("FOO="));
39 try std.testing.expect(!environ.containsUnemptyConstant("FO"));
40 try std.testing.expect(!environ.containsUnemptyConstant("FOOO"));
4134 if (builtin.os.tag == .windows) {
4235 try std.testing.expect(environ.containsUnemptyConstant("foo"));
4336 }
4437 try std.testing.expect(environ.containsUnemptyConstant("EQUALS"));
45 try std.testing.expect(!environ.containsUnemptyConstant("EQUALS=ABC"));
4638 try std.testing.expect(environ.containsUnemptyConstant("КИРиллИЦА"));
4739 if (builtin.os.tag == .windows) {
4840 try std.testing.expect(environ.containsUnemptyConstant("кирИЛЛица"));
......@@ -58,14 +50,10 @@ pub fn main(init: std.process.Init) !void {
5850 // contains
5951 {
6052 try std.testing.expect(try environ.contains(allocator, "FOO"));
61 try std.testing.expect(!(try environ.contains(allocator, "FOO=")));
62 try std.testing.expect(!(try environ.contains(allocator, "FO")));
63 try std.testing.expect(!(try environ.contains(allocator, "FOOO")));
6453 if (builtin.os.tag == .windows) {
6554 try std.testing.expect(try environ.contains(allocator, "foo"));
6655 }
6756 try std.testing.expect(try environ.contains(allocator, "EQUALS"));
68 try std.testing.expect(!(try environ.contains(allocator, "EQUALS=ABC")));
6957 try std.testing.expect(try environ.contains(allocator, "КИРиллИЦА"));
7058 if (builtin.os.tag == .windows) {
7159 try std.testing.expect(try environ.contains(allocator, "кирИЛЛица"));
......@@ -81,14 +69,10 @@ pub fn main(init: std.process.Init) !void {
8169 // containsConstant
8270 {
8371 try std.testing.expect(environ.containsConstant("FOO"));
84 try std.testing.expect(!environ.containsConstant("FOO="));
85 try std.testing.expect(!environ.containsConstant("FO"));
86 try std.testing.expect(!environ.containsConstant("FOOO"));
8772 if (builtin.os.tag == .windows) {
8873 try std.testing.expect(environ.containsConstant("foo"));
8974 }
9075 try std.testing.expect(environ.containsConstant("EQUALS"));
91 try std.testing.expect(!environ.containsConstant("EQUALS=ABC"));
9276 try std.testing.expect(environ.containsConstant("КИРиллИЦА"));
9377 if (builtin.os.tag == .windows) {
9478 try std.testing.expect(environ.containsConstant("кирИЛЛица"));
......@@ -104,14 +88,10 @@ pub fn main(init: std.process.Init) !void {
10488 // getAlloc
10589 {
10690 try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "FOO"));
107 try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOO="));
108 try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FO"));
109 try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOOO"));
11091 if (builtin.os.tag == .windows) {
11192 try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "foo"));
11293 }
11394 try std.testing.expectEqualSlices(u8, "ABC=123", try environ.getAlloc(arena, "EQUALS"));
114 try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "EQUALS=ABC"));
11595 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "КИРиллИЦА"));
11696 if (builtin.os.tag == .windows) {
11797 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "кирИЛЛица"));
......@@ -130,13 +110,10 @@ pub fn main(init: std.process.Init) !void {
130110 defer environ_map.deinit();
131111
132112 try std.testing.expectEqualSlices(u8, "123", environ_map.get("FOO").?);
133 try std.testing.expectEqual(null, environ_map.get("FO"));
134 try std.testing.expectEqual(null, environ_map.get("FOOO"));
135113 if (builtin.os.tag == .windows) {
136114 try std.testing.expectEqualSlices(u8, "123", environ_map.get("foo").?);
137115 }
138116 try std.testing.expectEqualSlices(u8, "ABC=123", environ_map.get("EQUALS").?);
139 try std.testing.expectEqual(null, environ_map.get("EQUALS=ABC"));
140117 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("КИРиллИЦА").?);
141118 if (builtin.os.tag == .windows) {
142119 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("кирИЛЛица").?);
test/standalone/windows_argv/fuzz.zig+7-2
......@@ -125,7 +125,7 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO
125125 .lpReserved2 = null,
126126 .hStdInput = null,
127127 .hStdOutput = null,
128 .hStdError = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null,
128 .hStdError = windows.peb().ProcessParameters.hStdError,
129129 };
130130 var proc_info: windows.PROCESS_INFORMATION = undefined;
131131
......@@ -149,7 +149,12 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO
149149 break :spawn proc_info.hProcess;
150150 };
151151 defer windows.CloseHandle(child_proc);
152 try windows.WaitForSingleObjectEx(child_proc, windows.INFINITE, false);
152 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
153 switch (windows.ntdll.NtWaitForSingleObject(child_proc, windows.FALSE, &infinite_timeout)) {
154 windows.NTSTATUS.WAIT_0 => {},
155 .TIMEOUT => return error.WaitTimeOut,
156 else => |status| return windows.unexpectedStatus(status),
157 }
153158
154159 var exit_code: windows.DWORD = undefined;
155160 if (windows.kernel32.GetExitCodeProcess(child_proc, &exit_code) == 0) {
test/standalone/windows_spawn/main.zig+4-3
......@@ -233,12 +233,13 @@ fn testExecWithCwdInner(gpa: Allocator, io: Io, command: []const u8, cwd: std.pr
233233}
234234
235235fn renameExe(dir: Io.Dir, io: Io, old_sub_path: []const u8, new_sub_path: []const u8) !void {
236 var attempt: u5 = 0;
236 var attempt: u5 = 10;
237237 while (true) break dir.rename(old_sub_path, dir, new_sub_path, io) catch |err| switch (err) {
238238 error.AccessDenied => {
239 if (attempt == 13) return error.AccessDenied;
239 if (attempt == 26) return error.AccessDenied;
240240 // give the kernel a chance to finish closing the executable handle
241 _ = std.os.windows.kernel32.SleepEx(@as(u32, 1) << attempt >> 1, std.os.windows.FALSE);
241 const interval = @as(std.os.windows.LARGE_INTEGER, -1) << attempt;
242 _ = std.os.windows.ntdll.NtDelayExecution(std.os.windows.FALSE, &interval);
242243 attempt += 1;
243244 continue;
244245 },