commit fcef9905ae859601d085576012b81dc05f67c46f (tree)
parent c3edf0ba641fcaf9ccc93e27ca3bf140d4b8e84c
Author: Andrew Kelley <andrew@ziglang.org>
Date: Thu, 5 Feb 2026 01:24:18 +0100
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>
Diffstat:
17 files changed, 1449 insertions(+), 1561 deletions(-)
diff --git a/build.zig b/build.zig
@@ -1498,6 +1498,7 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {
defer dir.close(io);
var wf = b.addWriteFiles();
+ b.step("test-docs", "Test code snippets from the docs").dependOn(&wf.step);
var it = dir.iterateAssumeFirstIteration();
while (it.next(io) catch @panic("failed to read dir")) |entry| {
diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig
@@ -386,10 +386,14 @@ pub const ZigProcess = struct {
child: std.process.Child,
multi_reader_buffer: Io.File.MultiReader.Buffer(2),
multi_reader: Io.File.MultiReader,
- progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void,
+ progress_ipc_index: ?if (std.Progress.have_ipc) std.Progress.Ipc.Index else noreturn,
pub const StreamEnum = enum { stdout, stderr };
+ pub fn saveState(zp: *ZigProcess, prog_node: std.Progress.Node) void {
+ zp.progress_ipc_index = if (std.Progress.have_ipc) prog_node.takeIpcIndex() else null;
+ }
+
pub fn deinit(zp: *ZigProcess, io: Io) void {
zp.child.kill(io);
zp.multi_reader.deinit();
@@ -417,7 +421,14 @@ pub fn evalZigProcess(
if (s.getZigProcess()) |zp| update: {
assert(watch);
- if (std.Progress.have_ipc) if (zp.progress_ipc_fd) |fd| prog_node.setIpcFd(fd);
+ if (zp.progress_ipc_index) |ipc_index| prog_node.setIpcIndex(ipc_index);
+ zp.progress_ipc_index = null;
+ var exited = false;
+ defer if (exited) {
+ s.cast(Compile).?.zig_process = null;
+ zp.deinit(io);
+ gpa.destroy(zp);
+ } else zp.saveState(prog_node);
const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) {
error.BrokenPipe, error.EndOfStream => |reason| {
std.log.info("{s} restart required: {t}", .{ argv[0], reason });
@@ -426,7 +437,7 @@ pub fn evalZigProcess(
return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
};
_ = term;
- s.clearZigProcess(gpa);
+ exited = true;
break :update;
},
else => |e| return e,
@@ -442,7 +453,7 @@ pub fn evalZigProcess(
return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
};
s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
- s.clearZigProcess(gpa);
+ exited = true;
try handleChildProcessTerm(s, term);
return error.MakeFailed;
}
@@ -467,19 +478,16 @@ pub fn evalZigProcess(
.progress_node = prog_node,
}) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err });
- zp.* = .{
- .child = zp.child,
- .multi_reader_buffer = undefined,
- .multi_reader = undefined,
- .progress_ipc_fd = if (std.Progress.have_ipc) prog_node.getIpcFd() else {},
- };
zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{
zp.child.stdout.?, zp.child.stderr.?,
});
- if (watch) s.setZigProcess(zp);
+ if (watch) s.cast(Compile).?.zig_process = zp;
defer if (!watch) zp.deinit(io);
- const result = try zigProcessUpdate(s, zp, watch, web_server, gpa);
+ const result = result: {
+ defer if (watch) zp.saveState(prog_node);
+ break :result try zigProcessUpdate(s, zp, watch, web_server, gpa);
+ };
if (!watch) {
// Send EOF to stdin.
@@ -670,26 +678,6 @@ pub fn getZigProcess(s: *Step) ?*ZigProcess {
};
}
-fn setZigProcess(s: *Step, zp: *ZigProcess) void {
- switch (s.id) {
- .compile => s.cast(Compile).?.zig_process = zp,
- else => unreachable,
- }
-}
-
-fn clearZigProcess(s: *Step, gpa: Allocator) void {
- switch (s.id) {
- .compile => {
- const compile = s.cast(Compile).?;
- if (compile.zig_process) |zp| {
- gpa.destroy(zp);
- compile.zig_process = null;
- }
- },
- else => unreachable,
- }
-}
-
fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {
const header: std.zig.Client.Message.Header = .{
.tag = tag,
diff --git a/lib/std/Build/Watch.zig b/lib/std/Build/Watch.zig
@@ -366,15 +366,7 @@ const Os = switch (builtin.os.tag) {
.MaximumLength = @intCast(path_len_bytes),
.Buffer = @constCast(sub_path_w.span().ptr),
};
- var attr = windows.OBJECT_ATTRIBUTES{
- .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
- .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd,
- .Attributes = .{},
- .ObjectName = &nt_name,
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
- };
- var io: windows.IO_STATUS_BLOCK = undefined;
+ var iosb: windows.IO_STATUS_BLOCK = undefined;
switch (windows.ntdll.NtCreateFile(
&dir_handle,
@@ -385,14 +377,18 @@ const Os = switch (builtin.os.tag) {
.STANDARD = .{ .SYNCHRONIZE = true },
.GENERIC = .{ .READ = true },
},
- &attr,
- &io,
+ &.{
+ .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd,
+ .ObjectName = &nt_name,
+ },
+ &iosb,
null,
.{},
.VALID_FLAGS,
.OPEN,
.{
.DIRECTORY_FILE = true,
+ .IO = .ASYNCHRONOUS,
.OPEN_FOR_BACKUP_INTENT = true,
},
null,
diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig
@@ -19,7 +19,7 @@ const Alignment = std.mem.Alignment;
const assert = std.debug.assert;
const posix = std.posix;
const windows = std.os.windows;
-const ws2_32 = std.os.windows.ws2_32;
+const ws2_32 = windows.ws2_32;
/// Thread-safe.
///
@@ -76,6 +76,7 @@ environ: Environ,
null_file: NullFile = .{},
random_file: RandomFile = .{},
+pipe_file: PipeFile = .{},
csprng: Csprng = .{},
@@ -121,7 +122,7 @@ pub const Argv0 = switch (native_os) {
const Environ = struct {
/// Unmodified data directly from the OS.
- process_environ: process.Environ = .empty,
+ process_environ: process.Environ,
/// Protected by `mutex`. Determines whether the other fields have been
/// memoized based on `process_environ`.
initialized: bool = false,
@@ -131,13 +132,15 @@ const Environ = struct {
/// Protected by `mutex`. Memoized based on `process_environ`.
string: String = .{},
/// ZIG_PROGRESS
- zig_progress_handle: std.Progress.ParentFileError!u31 = error.EnvironmentVariableMissing,
+ zig_progress_file: std.Progress.ParentFileError!File = error.EnvironmentVariableMissing,
/// Protected by `mutex`. Tracks the problem, if any, that occurred when
/// trying to scan environment variables.
///
/// Errors are only possible on WASI.
err: ?Error = null,
+ pub const empty: Environ = .{ .process_environ = .empty };
+
pub const Error = Allocator.Error || Io.UnexpectedError;
pub const Exist = struct {
@@ -193,6 +196,24 @@ pub const RandomFile = switch (native_os) {
},
};
+pub const PipeFile = switch (native_os) {
+ .windows => struct {
+ handle: ?windows.HANDLE = null,
+
+ fn deinit(this: *@This()) void {
+ if (this.handle) |handle| {
+ windows.CloseHandle(handle);
+ this.handle = null;
+ }
+ }
+ },
+ else => struct {
+ fn deinit(this: @This()) void {
+ _ = this;
+ }
+ },
+};
+
pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
unknown = 0,
_,
@@ -1498,7 +1519,9 @@ pub const init_single_threaded: Threaded = .{
.old_sig_pipe = undefined,
.have_signal_handler = false,
.argv0 = .empty,
- .environ = .{},
+ .environ = .{ .process_environ = .{
+ .block = if (process.Environ.Block == process.Environ.GlobalBlock) .global else .empty,
+ } },
.worker_threads = .init(null),
.disable_memory_mapping = false,
};
@@ -1533,6 +1556,7 @@ pub fn deinit(t: *Threaded) void {
}
t.null_file.deinit();
t.random_file.deinit();
+ t.pipe_file.deinit();
t.* = undefined;
}
@@ -1576,14 +1600,7 @@ fn worker(t: *Threaded) void {
},
},
},
- &.{
- .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
- .RootDirectory = null,
- .ObjectName = null,
- .Attributes = .{},
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
- },
+ &.{ .ObjectName = null },
&windows.teb().ClientId,
) == .SUCCESS);
}
@@ -2595,8 +2612,7 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void {
// opportunity to find additional ready operations.
break :t 0;
}
- const max_poll_ms = std.math.maxInt(i32);
- break :t max_poll_ms;
+ break :t std.math.maxInt(i32);
};
const syscall = try Syscall.start();
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
break :allocation allocation;
};
@memcpy(slice[0..poll_buffer_len], storage.slice);
+ storage.slice = slice;
}
storage.slice[len] = .{
.fd = file.handle,
@@ -2769,9 +2786,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout
}
const d = deadline orelse break :t -1;
const duration = d.durationFromNow(t_io);
- if (duration.raw.nanoseconds <= 0) return error.Timeout;
- const max_poll_ms = std.math.maxInt(i32);
- break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds()));
+ break :t @min(@max(0, duration.raw.toMilliseconds()), std.math.maxInt(i32));
};
const syscall = try Syscall.start();
const rc = posix.system.poll(&poll_buffer, poll_storage.len, timeout_ms);
@@ -3379,12 +3394,8 @@ fn dirCreateDirPathOpenWindows(
},
},
&.{
- .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
.RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
- .Attributes = .{},
.ObjectName = &nt_name,
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
},
&io_status_block,
null,
@@ -4066,13 +4077,9 @@ fn dirAccessWindows(
.MaximumLength = path_len_bytes,
.Buffer = @constCast(sub_path_w.ptr),
};
- var attr: windows.OBJECT_ATTRIBUTES = .{
- .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
+ const attr: windows.OBJECT_ATTRIBUTES = .{
.RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
- .Attributes = .{},
.ObjectName = &nt_name,
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
};
var basic_info: windows.FILE.BASIC_INFORMATION = undefined;
const syscall: Syscall = try .start();
@@ -4288,14 +4295,8 @@ fn dirCreateFileWindows(
.Buffer = @constCast(sub_path_w.ptr),
};
const attr: windows.OBJECT_ATTRIBUTES = .{
- .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
.RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
- .Attributes = .{
- .INHERIT = false,
- },
.ObjectName = &nt_name,
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
};
const create_disposition: windows.FILE.CREATE_DISPOSITION = if (flags.exclusive)
.CREATE
@@ -4908,17 +4909,6 @@ pub fn dirOpenFileWtf16(
.MaximumLength = path_len_bytes,
.Buffer = @constCast(sub_path_w.ptr),
};
- var attr: w.OBJECT_ATTRIBUTES = .{
- .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
- .RootDirectory = dir_handle,
- .Attributes = .{
- // TODO should we set INHERIT=false?
- //.INHERIT = false,
- },
- .ObjectName = &nt_name,
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
- };
var io_status_block: w.IO_STATUS_BLOCK = undefined;
// There are multiple kernel bugs being worked around with retries.
@@ -4937,7 +4927,10 @@ pub fn dirOpenFileWtf16(
.WRITE = flags.isWrite(),
},
},
- &attr,
+ &.{
+ .RootDirectory = dir_handle,
+ .ObjectName = &nt_name,
+ },
&io_status_block,
null,
.{ .NORMAL = true },
@@ -5305,12 +5298,8 @@ pub fn dirOpenDirWindows(
},
},
&.{
- .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
.RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
- .Attributes = .{},
.ObjectName = &nt_name,
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
},
&io_status_block,
null,
@@ -6520,12 +6509,8 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
.SYNCHRONIZE = true,
} },
&.{
- .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
.RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
- .Attributes = .{},
.ObjectName = &nt_name,
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
},
&io_status_block,
null,
@@ -6534,6 +6519,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
.OPEN,
.{
.DIRECTORY_FILE = remove_dir,
+ .IO = .SYNCHRONOUS_NONALERT,
.NON_DIRECTORY_FILE = !remove_dir,
.OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead?
},
@@ -7345,14 +7331,8 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink
.Buffer = @constCast(sub_path_w.ptr),
};
const attr: windows.OBJECT_ATTRIBUTES = .{
- .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
.RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
- .Attributes = .{
- .INHERIT = false,
- },
.ObjectName = &nt_name,
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
};
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
var result_handle: windows.HANDLE = undefined;
@@ -7909,24 +7889,19 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
_ = t;
+ var io_status_block: windows.IO_STATUS_BLOCK = undefined;
const syscall: Syscall = try .start();
while (true) {
- if (windows.kernel32.FlushFileBuffers(file.handle) != 0) {
- return syscall.finish();
- }
- switch (windows.GetLastError()) {
- .SUCCESS => unreachable, // `FlushFileBuffers` returned nonzero
- .INVALID_HANDLE => unreachable,
- .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time
- .UNEXP_NET_ERR => return syscall.fail(error.InputOutput),
- .OPERATION_ABORTED => {
+ switch (windows.ntdll.NtFlushBuffersFile(file.handle, &io_status_block)) {
+ .SUCCESS => break syscall.finish(),
+ .CANCELLED => {
try syscall.checkCancel();
continue;
},
- else => |err| {
- syscall.finish();
- return windows.unexpectedError(err);
- },
+ .INVALID_HANDLE => unreachable,
+ .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time
+ .UNEXPECTED_NETWORK_ERROR => return syscall.fail(error.InputOutput),
+ else => |status| return syscall.unexpectedNtstatus(status),
}
}
}
@@ -14446,7 +14421,10 @@ const WindowsEnvironStrings = struct {
PATHEXT: ?[:0]const u16 = null,
fn scan() WindowsEnvironStrings {
- const ptr = windows.peb().ProcessParameters.Environment;
+ const peb = windows.peb();
+ assert(windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
+ defer assert(windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
+ const ptr = peb.ProcessParameters.Environment;
var result: WindowsEnvironStrings = .{};
var i: usize = 0;
@@ -14472,7 +14450,7 @@ const WindowsEnvironStrings = struct {
inline for (@typeInfo(WindowsEnvironStrings).@"struct".fields) |field| {
const field_name_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(field.name);
- if (std.os.windows.eqlIgnoreCaseWtf16(key_w, field_name_w)) @field(result, field.name) = value_w;
+ if (windows.eqlIgnoreCaseWtf16(key_w, field_name_w)) @field(result, field.name) = value_w;
}
}
@@ -14491,29 +14469,46 @@ fn scanEnviron(t: *Threaded) void {
// This value expires with any call that modifies the environment,
// which is outside of this Io implementation's control, so references
// must be short-lived.
- const ptr = windows.peb().ProcessParameters.Environment;
+ const peb = windows.peb();
+ assert(windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
+ defer assert(windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
+ const ptr = peb.ProcessParameters.Environment;
var i: usize = 0;
while (ptr[i] != 0) {
- const key_start = i;
// There are some special environment variables that start with =,
// so we need a special case to not treat = as a key/value separator
// if it's the first character.
// https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
- if (ptr[key_start] == '=') i += 1;
-
+ const key_start = i;
+ if (ptr[i] == '=') i += 1;
while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
const key_w = ptr[key_start..i];
- if (std.mem.eql(u16, key_w, &.{ 'N', 'O', '_', 'C', 'O', 'L', 'O', 'R' })) {
+
+ const value_start = i + 1;
+ while (ptr[i] != 0) : (i += 1) {} // skip over '=' and value
+ const value_w = ptr[value_start..i];
+ i += 1; // skip over null byte
+
+ if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'N', 'O', '_', 'C', 'O', 'L', 'O', 'R' })) {
t.environ.exist.NO_COLOR = true;
- } else if (std.mem.eql(u16, key_w, &.{ 'C', 'L', 'I', 'C', 'O', 'L', 'O', 'R', '_', 'F', 'O', 'R', 'C', 'E' })) {
+ } else if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'C', 'L', 'I', 'C', 'O', 'L', 'O', 'R', '_', 'F', 'O', 'R', 'C', 'E' })) {
t.environ.exist.CLICOLOR_FORCE = true;
+ } else if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S' })) {
+ t.environ.zig_progress_file = file: {
+ var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined;
+ const len = std.unicode.calcWtf8Len(value_w);
+ if (len > value_buf.len) break :file error.UnrecognizedFormat;
+ assert(std.unicode.wtf16LeToWtf8(&value_buf, value_w) == len);
+ break :file .{
+ .handle = @ptrFromInt(std.fmt.parseInt(usize, value_buf[0..len], 10) catch
+ break :file error.UnrecognizedFormat),
+ .flags = .{ .nonblocking = true },
+ };
+ };
}
comptime assert(@sizeOf(Environ.String) == 0);
-
- while (ptr[i] != 0) : (i += 1) {} // skip over '=' and value
- i += 1; // skip over null byte
}
} else if (native_os == .wasi and !builtin.link_libc) {
var environ_count: usize = undefined;
@@ -14559,22 +14554,28 @@ fn scanEnviron(t: *Threaded) void {
comptime assert(@sizeOf(Environ.String) == 0);
}
} else {
- for (t.environ.process_environ.block) |opt_line| {
- const line = opt_line.?;
- var line_i: usize = 0;
- while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
- const key = line[0..line_i];
+ for (t.environ.process_environ.block.slice) |opt_entry| {
+ const entry = opt_entry.?;
+ var entry_i: usize = 0;
+ while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {}
+ const key = entry[0..entry_i];
- var end_i: usize = line_i;
- while (line[end_i] != 0) : (end_i += 1) {}
- const value = line[line_i + 1 .. end_i :0];
+ var end_i: usize = entry_i;
+ while (entry[end_i] != 0) : (end_i += 1) {}
+ const value = entry[entry_i + 1 .. end_i :0];
if (std.mem.eql(u8, key, "NO_COLOR")) {
t.environ.exist.NO_COLOR = true;
} else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) {
t.environ.exist.CLICOLOR_FORCE = true;
} else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) {
- t.environ.zig_progress_handle = std.fmt.parseInt(u31, value, 10) catch error.UnrecognizedFormat;
+ t.environ.zig_progress_file = file: {
+ break :file .{
+ .handle = std.fmt.parseInt(u31, value, 10) catch
+ break :file error.UnrecognizedFormat,
+ .flags = .{ .nonblocking = true },
+ };
+ };
} else inline for (@typeInfo(Environ.String).@"struct".fields) |field| {
if (std.mem.eql(u8, key, field.name)) @field(t.environ.string, field.name) = value;
}
@@ -14597,19 +14598,17 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);
for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
- const envp: [*:null]const ?[*:0]const u8 = m: {
+ const env_block = env_block: {
const prog_fd: i32 = -1;
- if (options.environ_map) |environ_map| {
- break :m (try environ_map.createBlockPosix(arena, .{
- .zig_progress_fd = prog_fd,
- })).ptr;
- }
- break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{
+ if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
.zig_progress_fd = prog_fd,
- })).ptr;
+ });
+ break :env_block try t.environ.process_environ.createPosixBlock(arena, .{
+ .zig_progress_fd = prog_fd,
+ });
};
- return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH);
+ return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
}
fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError {
@@ -14679,16 +14678,17 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore);
const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined;
- const prog_pipe: [2]posix.fd_t = p: {
- if (options.progress_node.index == .none) {
- break :p .{ -1, -1 };
- } else {
- // We use CLOEXEC for the same reason as in `pipe_flags`.
- break :p try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true });
- }
- };
+ const prog_pipe: [2]posix.fd_t = if (options.progress_node.index != .none)
+ // We use CLOEXEC for the same reason as in `pipe_flags`.
+ try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true })
+ else
+ .{ -1, -1 };
errdefer destroyPipe(prog_pipe);
+ if (native_os == .linux and prog_pipe[0] != -1) {
+ _ = posix.system.fcntl(prog_pipe[0], posix.F.SETPIPE_SZ, @as(u32, std.Progress.max_packet_len * 2));
+ }
+
var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
@@ -14708,16 +14708,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
const prog_fileno = 3;
comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno);
- const envp: [*:null]const ?[*:0]const u8 = m: {
+ const env_block = env_block: {
const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
- if (options.environ_map) |environ_map| {
- break :m (try environ_map.createBlockPosix(arena, .{
- .zig_progress_fd = prog_fd,
- })).ptr;
- }
- break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{
+ if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
.zig_progress_fd = prog_fd,
- })).ptr;
+ });
+ break :env_block try t.environ.process_environ.createPosixBlock(arena, .{
+ .zig_progress_fd = prog_fd,
+ });
};
// 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
}
}
- const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH);
+ const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
forkBail(ep1, err);
}
@@ -14814,8 +14812,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
if (options.stderr == .pipe) posix.close(stderr_pipe[1]);
if (prog_pipe[1] != -1) posix.close(prog_pipe[1]);
-
- options.progress_node.setIpcFd(prog_pipe[0]);
+ options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });
return .{
.pid = pid,
@@ -14938,42 +14935,44 @@ fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT
// some rare edge cases where our process handle no longer has the
// PROCESS_TERMINATE access right, so let's do another check to make
// sure the process is really no longer running:
- windows.WaitForSingleObjectEx(handle, 0, false) catch return error.AccessDenied;
- return error.AlreadyTerminated;
+ const minimal_timeout: windows.LARGE_INTEGER = -1;
+ switch (windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &minimal_timeout)) {
+ .SUCCESS => return error.AlreadyTerminated,
+ else => return error.AccessDenied,
+ }
},
else => |err| return windows.unexpectedError(err),
}
}
- _ = windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE);
+ const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
+ _ = windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &infinite_timeout);
childCleanupWindows(child);
}
fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term {
const handle = child.id.?;
- const syscall: Syscall = try .start();
- while (true) switch (windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE)) {
- windows.WAIT_OBJECT_0 => break syscall.finish(),
- windows.WAIT_ABANDONED, windows.WAIT_TIMEOUT => {
- try syscall.checkCancel();
+ const alertable_syscall: AlertableSyscall = try .start();
+ const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
+ while (true) switch (windows.ntdll.NtWaitForSingleObject(handle, windows.TRUE, &infinite_timeout)) {
+ windows.NTSTATUS.WAIT_0 => break alertable_syscall.finish(),
+ .USER_APC, .ALERTED, .TIMEOUT => {
+ try alertable_syscall.checkCancel();
continue;
},
- windows.WAIT_FAILED => {
- syscall.finish();
- switch (windows.GetLastError()) {
- else => |err| return windows.unexpectedError(err),
- }
- },
- else => return syscall.fail(error.Unexpected),
+ else => |status| return alertable_syscall.unexpectedNtstatus(status),
};
- const term: process.Child.Term = x: {
- var exit_code: windows.DWORD = undefined;
- if (windows.kernel32.GetExitCodeProcess(handle, &exit_code) == 0) {
- break :x .{ .unknown = 0 };
- } else {
- break :x .{ .exited = @as(u8, @truncate(exit_code)) };
- }
+ var info: windows.PROCESS_BASIC_INFORMATION = undefined;
+ const term: process.Child.Term = switch (windows.ntdll.NtQueryInformationProcess(
+ handle,
+ .BasicInformation,
+ &info,
+ @sizeOf(windows.PROCESS_BASIC_INFORMATION),
+ null,
+ )) {
+ .SUCCESS => .{ .exited = @as(u8, @truncate(@intFromEnum(info.ExitStatus))) },
+ else => .{ .unknown = 0 },
};
childCleanupWindows(child);
@@ -15236,88 +15235,71 @@ fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32
fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- var saAttr: windows.SECURITY_ATTRIBUTES = .{
- .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
- .bInheritHandle = windows.TRUE,
- .lpSecurityDescriptor = null,
- };
-
const any_ignore =
options.stdin == .ignore or
options.stdout == .ignore or
options.stderr == .ignore;
-
- const nul_handle = if (any_ignore) try getNulHandle(t) else undefined;
-
- var g_hChildStd_IN_Rd: ?windows.HANDLE = null;
- var g_hChildStd_IN_Wr: ?windows.HANDLE = null;
- switch (options.stdin) {
- .pipe => {
- try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr);
- },
- .ignore => {
- g_hChildStd_IN_Rd = nul_handle;
- },
- .inherit => {
- g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE) catch null;
- },
- .close => {
- g_hChildStd_IN_Rd = null;
- },
- .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
- }
- errdefer if (options.stdin == .pipe) {
- windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr);
- };
-
- var g_hChildStd_OUT_Rd: ?windows.HANDLE = null;
- var g_hChildStd_OUT_Wr: ?windows.HANDLE = null;
- switch (options.stdout) {
- .pipe => {
- try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr);
- },
- .ignore => {
- g_hChildStd_OUT_Wr = nul_handle;
- },
- .inherit => {
- g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) catch null;
- },
- .close => {
- g_hChildStd_OUT_Wr = null;
- },
- .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
- }
- errdefer if (options.stdout == .pipe) {
- windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr);
- };
-
- var g_hChildStd_ERR_Rd: ?windows.HANDLE = null;
- var g_hChildStd_ERR_Wr: ?windows.HANDLE = null;
- switch (options.stderr) {
- .pipe => {
- try windowsMakeAsyncPipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr);
- },
- .ignore => {
- g_hChildStd_ERR_Wr = nul_handle;
- },
- .inherit => {
- g_hChildStd_ERR_Wr = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null;
- },
- .close => {
- g_hChildStd_ERR_Wr = null;
- },
- .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
- }
- errdefer if (options.stderr == .pipe) {
- windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr);
- };
+ const nul_handle = if (any_ignore) try getNulDevice(t) else undefined;
+
+ const any_inherit =
+ options.stdin == .inherit or
+ options.stdout == .inherit or
+ options.stderr == .inherit;
+ const peb = if (any_inherit) windows.peb() else undefined;
+
+ const stdin_pipe = if (options.stdin == .pipe) try t.windowsCreatePipe(.{
+ .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
+ .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
+ .outbound = true,
+ }) else undefined;
+ errdefer if (options.stdin == .pipe) for (stdin_pipe) |handle| windows.CloseHandle(handle);
+
+ const stdout_pipe = if (options.stdout == .pipe) try t.windowsCreatePipe(.{
+ .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
+ .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
+ .inbound = true,
+ }) else undefined;
+ errdefer if (options.stdout == .pipe) for (stdout_pipe) |handle| windows.CloseHandle(handle);
+
+ const stderr_pipe = if (options.stderr == .pipe) try t.windowsCreatePipe(.{
+ .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
+ .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
+ .inbound = true,
+ }) else undefined;
+ errdefer if (options.stderr == .pipe) for (stderr_pipe) |handle| windows.CloseHandle(handle);
+
+ const prog_pipe = if (options.progress_node.index != .none) try t.windowsCreatePipe(.{
+ .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
+ .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .ASYNCHRONOUS } },
+ .inbound = true,
+ .quota = std.Progress.max_packet_len * 2,
+ }) else undefined;
+ errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle);
var siStartInfo: windows.STARTUPINFOW = .{
.cb = @sizeOf(windows.STARTUPINFOW),
- .hStdError = g_hChildStd_ERR_Wr,
- .hStdOutput = g_hChildStd_OUT_Wr,
- .hStdInput = g_hChildStd_IN_Rd,
.dwFlags = windows.STARTF_USESTDHANDLES,
+ .hStdInput = switch (options.stdin) {
+ .inherit => peb.ProcessParameters.hStdInput,
+ .file => |file| file.handle,
+ .ignore => nul_handle,
+ .pipe => stdin_pipe[1],
+ .close => null,
+ },
+ .hStdOutput = switch (options.stdout) {
+ .inherit => peb.ProcessParameters.hStdOutput,
+ .file => |file| file.handle,
+ .ignore => nul_handle,
+ .pipe => stdout_pipe[1],
+ .close => null,
+ },
+ .hStdError = switch (options.stderr) {
+ .inherit => peb.ProcessParameters.hStdError,
+ .file => |file| file.handle,
+ .ignore => nul_handle,
+ .pipe => stderr_pipe[1],
+ .close => null,
+ },
.lpReserved = null,
.lpDesktop = null,
@@ -15363,8 +15345,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
};
const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
- const maybe_envp_buf = if (options.environ_map) |environ_map| try environ_map.createBlockWindows(arena) else null;
- const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
+ const env_block = env_block: {
+ const prog_handle = if (options.progress_node.index != .none)
+ prog_pipe[1]
+ else
+ windows.INVALID_HANDLE_VALUE;
+ if (options.environ_map) |environ_map| break :env_block try environ_map.createWindowsBlock(arena, .{
+ .zig_progress_handle = prog_handle,
+ });
+ break :env_block try t.environ.process_environ.createWindowsBlock(arena, .{
+ .zig_progress_handle = if (options.progress_node.index != .none) prog_pipe[1] else windows.INVALID_HANDLE_VALUE,
+ });
+ };
const app_name_wtf8 = options.argv[0];
const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8);
@@ -15439,7 +15431,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
&app_buf,
PATHEXT,
&cmd_line_cache,
- envp_ptr,
+ env_block,
cwd_w_ptr,
flags,
&siStartInfo,
@@ -15474,7 +15466,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
&app_buf,
PATHEXT,
&cmd_line_cache,
- envp_ptr,
+ env_block,
cwd_w_ptr,
flags,
&siStartInfo,
@@ -15494,21 +15486,40 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
};
}
- if (options.stdin == .pipe) windows.CloseHandle(g_hChildStd_IN_Rd.?);
- if (options.stderr == .pipe) windows.CloseHandle(g_hChildStd_ERR_Wr.?);
- if (options.stdout == .pipe) windows.CloseHandle(g_hChildStd_OUT_Wr.?);
+ if (options.progress_node.index != .none) {
+ windows.CloseHandle(prog_pipe[1]);
+ options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });
+ }
return .{
.id = piProcInfo.hProcess,
.thread_handle = piProcInfo.hThread,
- .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = false } } else null,
- .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
- .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
+ .stdin = stdin: switch (options.stdin) {
+ .pipe => {
+ windows.CloseHandle(stdin_pipe[1]);
+ break :stdin .{ .handle = stdin_pipe[0], .flags = .{ .nonblocking = false } };
+ },
+ else => null,
+ },
+ .stdout = stdout: switch (options.stdout) {
+ .pipe => {
+ windows.CloseHandle(stdout_pipe[1]);
+ break :stdout .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = true } };
+ },
+ else => null,
+ },
+ .stderr = stderr: switch (options.stderr) {
+ .pipe => {
+ windows.CloseHandle(stderr_pipe[1]);
+ break :stderr .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = true } };
+ },
+ else => null,
+ },
.request_resource_usage_statistics = options.request_resource_usage_statistics,
};
}
-fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
+fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
{
mutexLock(&t.mutex);
defer mutexUnlock(&t.mutex);
@@ -15516,12 +15527,6 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
}
const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' };
-
- var nt_name: windows.UNICODE_STRING = .{
- .Length = device_path.len * 2,
- .MaximumLength = 0,
- .Buffer = @constCast(&device_path),
- };
var fresh_handle: windows.HANDLE = undefined;
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
var syscall: Syscall = try .start();
@@ -15532,12 +15537,11 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
.SPECIFIC = .{ .FILE = .{ .READ_DATA = true } },
},
&.{
- .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
- .RootDirectory = null,
- .ObjectName = &nt_name,
- .Attributes = .{},
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
+ .ObjectName = @constCast(&windows.UNICODE_STRING{
+ .Length = @sizeOf(@TypeOf(device_path)),
+ .MaximumLength = 0,
+ .Buffer = @constCast(&device_path),
+ }),
},
&io_status_block,
.VALID_FLAGS,
@@ -15564,7 +15568,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
};
}
-fn getNulHandle(t: *Threaded) !windows.HANDLE {
+fn getNulDevice(t: *Threaded) !windows.HANDLE {
{
mutexLock(&t.mutex);
defer mutexUnlock(&t.mutex);
@@ -15572,44 +15576,26 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE {
}
const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' };
- var nt_name: windows.UNICODE_STRING = .{
- .Length = device_path.len * 2,
- .MaximumLength = 0,
- .Buffer = @constCast(&device_path),
- };
- const attr: windows.OBJECT_ATTRIBUTES = .{
- .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
- .RootDirectory = null,
- .Attributes = .{
- .INHERIT = true,
- },
- .ObjectName = &nt_name,
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
- };
- var io_status_block: windows.IO_STATUS_BLOCK = undefined;
var fresh_handle: windows.HANDLE = undefined;
+ var io_status_block: windows.IO_STATUS_BLOCK = undefined;
var syscall: Syscall = try .start();
- while (true) switch (windows.ntdll.NtCreateFile(
+ while (true) switch (windows.ntdll.NtOpenFile(
&fresh_handle,
.{
.STANDARD = .{ .SYNCHRONIZE = true },
- .GENERIC = .{ .WRITE = true, .READ = true },
+ .SPECIFIC = .{ .FILE = .{ .READ_DATA = true, .WRITE_DATA = true } },
+ },
+ &.{
+ .Attributes = .{ .INHERIT = true },
+ .ObjectName = @constCast(&windows.UNICODE_STRING{
+ .Length = @sizeOf(@TypeOf(device_path)),
+ .MaximumLength = 0,
+ .Buffer = @constCast(&device_path),
+ }),
},
- &attr,
&io_status_block,
- null,
- .{ .NORMAL = true },
.VALID_FLAGS,
- .OPEN,
- .{
- .DIRECTORY_FILE = false,
- .NON_DIRECTORY_FILE = true,
- .IO = .SYNCHRONOUS_NONALERT,
- .OPEN_REPARSE_POINT = false,
- },
- null,
- 0,
+ .{ .IO = .SYNCHRONOUS_NONALERT },
)) {
.SUCCESS => {
syscall.finish();
@@ -15623,6 +15609,64 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE {
return fresh_handle;
}
},
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
+ .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status),
+ .INVALID_HANDLE => |status| return syscall.ntstatusBug(status),
+ .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
+ .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
+ .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
+ .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
+ .SHARING_VIOLATION => return syscall.fail(error.AccessDenied),
+ .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
+ .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice),
+ .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
+ .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
+ .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
+ else => |status| return syscall.unexpectedNtstatus(status),
+ };
+}
+
+fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE {
+ {
+ mutexLock(&t.mutex);
+ defer mutexUnlock(&t.mutex);
+ if (t.pipe_file.handle) |handle| return handle;
+ }
+
+ const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'a', 'm', 'e', 'd', 'P', 'i', 'p', 'e', '\\' };
+ var fresh_handle: windows.HANDLE = undefined;
+ var io_status_block: windows.IO_STATUS_BLOCK = undefined;
+ var syscall: Syscall = try .start();
+ while (true) switch (windows.ntdll.NtOpenFile(
+ &fresh_handle,
+ .{ .STANDARD = .{ .SYNCHRONIZE = true } },
+ &.{
+ .ObjectName = @constCast(&windows.UNICODE_STRING{
+ .Length = @sizeOf(@TypeOf(device_path)),
+ .MaximumLength = 0,
+ .Buffer = @constCast(&device_path),
+ }),
+ },
+ &io_status_block,
+ .VALID_FLAGS,
+ .{ .IO = .SYNCHRONOUS_NONALERT },
+ )) {
+ .SUCCESS => {
+ syscall.finish();
+ mutexLock(&t.mutex); // Another thread might have won the race.
+ defer mutexUnlock(&t.mutex);
+ if (t.pipe_file.handle) |prev_handle| {
+ windows.CloseHandle(fresh_handle);
+ return prev_handle;
+ } else {
+ t.pipe_file.handle = fresh_handle;
+ return fresh_handle;
+ }
+ },
.DELETE_PENDING => {
// This error means that there *was* a file in this location on
// the file system, but it was deleted. However, the OS is not
@@ -15669,7 +15713,7 @@ fn windowsCreateProcessPathExt(
app_buf: *std.ArrayList(u16),
pathext: [:0]const u16,
cmd_line_cache: *WindowsCommandLineCache,
- envp_ptr: ?[*:0]const u16,
+ env_block: ?process.Environ.WindowsBlock,
cwd_ptr: ?[*:0]u16,
flags: windows.CreateProcessFlags,
lpStartupInfo: *windows.STARTUPINFOW,
@@ -15846,7 +15890,7 @@ fn windowsCreateProcessPathExt(
if (windowsCreateProcess(
app_name_w.ptr,
cmd_line_w.ptr,
- envp_ptr,
+ env_block,
cwd_ptr,
flags,
lpStartupInfo,
@@ -15906,7 +15950,7 @@ fn windowsCreateProcessPathExt(
else
full_app_name;
- if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {
+ if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, env_block, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {
return;
} else |err| switch (err) {
error.FileNotFound => continue,
@@ -15930,7 +15974,7 @@ fn windowsCreateProcessPathExt(
fn windowsCreateProcess(
app_name: [*:0]u16,
cmd_line: [*:0]u16,
- env_ptr: ?[*:0]const u16,
+ env_block: ?process.Environ.WindowsBlock,
cwd_ptr: ?[*:0]u16,
flags: windows.CreateProcessFlags,
lpStartupInfo: *windows.STARTUPINFOW,
@@ -15945,7 +15989,7 @@ fn windowsCreateProcess(
null,
windows.TRUE,
flags,
- env_ptr,
+ if (env_block) |block| block.slice.ptr else null,
cwd_ptr,
lpStartupInfo,
lpProcessInformation,
@@ -16466,11 +16510,11 @@ fn posixExecv(
arg0_expand: process.ArgExpansion,
file: [*:0]const u8,
child_argv: [*:null]?[*:0]const u8,
- envp: [*:null]const ?[*:0]const u8,
+ env_block: process.Environ.PosixBlock,
PATH: []const u8,
) process.ReplaceError {
const file_slice = std.mem.sliceTo(file, 0);
- if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, envp);
+ if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, env_block);
// Use of PATH_MAX here is valid as the path_buf will be passed
// directly to the operating system in posixExecvPath.
@@ -16498,7 +16542,7 @@ fn posixExecv(
.expand => child_argv[0] = full_path,
.no_expand => {},
}
- err = posixExecvPath(full_path, child_argv, envp);
+ err = posixExecvPath(full_path, child_argv, env_block);
switch (err) {
error.AccessDenied => seen_eacces = true,
error.FileNotFound, error.NotDir => {},
@@ -16513,10 +16557,10 @@ fn posixExecv(
pub fn posixExecvPath(
path: [*:0]const u8,
child_argv: [*:null]const ?[*:0]const u8,
- envp: [*:null]const ?[*:0]const u8,
+ env_block: process.Environ.PosixBlock,
) process.ReplaceError {
try Thread.checkCancel();
- switch (posix.errno(posix.system.execve(path, child_argv, envp))) {
+ switch (posix.errno(posix.system.execve(path, child_argv, env_block.slice.ptr))) {
.FAULT => |err| return errnoBug(err), // Bad pointer parameter.
.@"2BIG" => return error.SystemResources,
.MFILE => return error.ProcessFdQuotaExceeded,
@@ -16548,100 +16592,105 @@ pub fn posixExecvPath(
}
}
-fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
- var rd_h: windows.HANDLE = undefined;
- var wr_h: windows.HANDLE = undefined;
- try windows.CreatePipe(&rd_h, &wr_h, sattr);
- errdefer windowsDestroyPipe(rd_h, wr_h);
- try windows.SetHandleInformation(wr_h, windows.HANDLE_FLAG_INHERIT, 0);
- rd.* = rd_h;
- wr.* = wr_h;
-}
-
-fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void {
- if (rd) |h| posix.close(h);
- if (wr) |h| posix.close(h);
-}
-
-fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
- var tmp_bufw: [128]u16 = undefined;
+pub const CreatePipeOptions = struct {
+ server: End,
+ client: End,
+ inbound: bool = false,
+ outbound: bool = false,
+ maximum_instances: u32 = 1,
+ quota: u32 = 4096,
+ default_timeout: windows.LARGE_INTEGER = -120 * std.time.ns_per_s / 100,
- // Anonymous pipes are built upon Named pipes.
- // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe
- // Asynchronous (overlapped) read and write operations are not supported by anonymous pipes.
- // https://docs.microsoft.com/en-us/windows/win32/ipc/anonymous-pipe-operations
- const pipe_path = blk: {
- var tmp_buf: [128]u8 = undefined;
- // Forge a random path for the pipe.
- const pipe_path = std.fmt.bufPrintSentinel(
- &tmp_buf,
- "\\\\.\\pipe\\zig-childprocess-{d}-{d}",
- .{ windows.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) },
- 0,
- ) catch unreachable;
- const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable;
- tmp_bufw[len] = 0;
- break :blk tmp_bufw[0..len :0];
+ pub const End = struct {
+ attributes: windows.OBJECT_ATTRIBUTES.ATTRIBUTES = .{},
+ mode: windows.FILE.MODE,
};
-
- // Create the read handle that can be used with overlapped IO ops.
- const read_handle = windows.kernel32.CreateNamedPipeW(
- pipe_path.ptr,
- windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED,
- windows.PIPE_TYPE_BYTE,
- 1,
- 4096,
- 4096,
- 0,
- sattr,
- );
- if (read_handle == windows.INVALID_HANDLE_VALUE) {
- switch (windows.GetLastError()) {
- else => |err| return windows.unexpectedError(err),
- }
- }
- errdefer posix.close(read_handle);
-
- var sattr_copy = sattr.*;
- const write_handle = windows.kernel32.CreateFileW(
- pipe_path.ptr,
- .{ .GENERIC = .{ .WRITE = true } },
- 0,
- &sattr_copy,
- windows.OPEN_EXISTING,
- @bitCast(windows.FILE.ATTRIBUTE{ .NORMAL = true }),
- null,
- );
- if (write_handle == windows.INVALID_HANDLE_VALUE) {
- switch (windows.GetLastError()) {
- else => |err| return windows.unexpectedError(err),
- }
- }
- errdefer posix.close(write_handle);
-
- try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0);
-
- rd.* = read_handle;
- wr.* = write_handle;
+};
+pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.HANDLE {
+ const named_pipe_device = try t.getNamedPipeDevice();
+ const server_handle = server_handle: {
+ var handle: windows.HANDLE = undefined;
+ var io_status_block: windows.IO_STATUS_BLOCK = undefined;
+ const syscall: Syscall = try .start();
+ while (true) switch (windows.ntdll.NtCreateNamedPipeFile(
+ &handle,
+ .{
+ .SPECIFIC = .{ .FILE_PIPE = .{
+ .READ_DATA = options.inbound,
+ .WRITE_DATA = options.outbound,
+ .WRITE_ATTRIBUTES = true,
+ } },
+ .STANDARD = .{ .SYNCHRONIZE = true },
+ },
+ &.{
+ .RootDirectory = named_pipe_device,
+ .Attributes = options.server.attributes,
+ },
+ &io_status_block,
+ .{ .READ = true, .WRITE = true },
+ .CREATE,
+ options.server.mode,
+ .{ .TYPE = .BYTE_STREAM },
+ .{ .MODE = .BYTE_STREAM },
+ .{ .OPERATION = .QUEUE },
+ options.maximum_instances,
+ if (options.inbound) options.quota else 0,
+ if (options.outbound) options.quota else 0,
+ &options.default_timeout,
+ )) {
+ .SUCCESS => break syscall.finish(),
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
+ .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
+ else => |status| return syscall.unexpectedNtstatus(status),
+ };
+ break :server_handle handle;
+ };
+ errdefer windows.CloseHandle(server_handle);
+ const client_handle = client_handle: {
+ var handle: windows.HANDLE = undefined;
+ var io_status_block: windows.IO_STATUS_BLOCK = undefined;
+ const syscall: Syscall = try .start();
+ while (true) switch (windows.ntdll.NtOpenFile(
+ &handle,
+ .{
+ .SPECIFIC = .{ .FILE_PIPE = .{
+ .READ_DATA = options.outbound,
+ .WRITE_DATA = options.inbound,
+ .WRITE_ATTRIBUTES = true,
+ } },
+ .STANDARD = .{ .SYNCHRONIZE = true },
+ },
+ &.{
+ .RootDirectory = server_handle,
+ .Attributes = options.client.attributes,
+ },
+ &io_status_block,
+ .{ .READ = true, .WRITE = true },
+ options.client.mode,
+ )) {
+ .SUCCESS => break syscall.finish(),
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
+ .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
+ else => |status| return syscall.unexpectedNtstatus(status),
+ };
+ break :client_handle handle;
+ };
+ errdefer windows.CloseHandle(client_handle);
+ return .{ server_handle, client_handle };
}
-var pipe_name_counter = std.atomic.Value(u32).init(1);
-
fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
const t: *Threaded = @ptrCast(@alignCast(userdata));
-
t.scanEnviron();
-
- const int = try t.environ.zig_progress_handle;
-
- return .{
- .handle = switch (@typeInfo(Io.File.Handle)) {
- .int => int,
- .pointer => @ptrFromInt(int),
- else => return error.UnsupportedOperation,
- },
- .flags = .{ .nonblocking = false },
- };
+ return t.environ.zig_progress_file;
}
pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {
@@ -16737,7 +16786,7 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void {
// despite the function being documented to always return TRUE
// * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG
// Therefore, that function is avoided in favor of using the device directly.
- const cng_device = try getCngHandle(t);
+ const cng_device = try getCngDevice(t);
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
var i: usize = 0;
const syscall: Syscall = try .start();
diff --git a/lib/std/Io/Threaded/test.zig b/lib/std/Io/Threaded/test.zig
@@ -181,13 +181,17 @@ test "cancel blocked read from pipe" {
var write_end: Io.File = undefined;
switch (builtin.target.os.tag) {
.wasi => return error.SkipZigTest,
- .windows => try std.os.windows.CreatePipe(&read_end.handle, &write_end.handle, &.{
- .nLength = @sizeOf(std.os.windows.SECURITY_ATTRIBUTES),
- .lpSecurityDescriptor = null,
- .bInheritHandle = std.os.windows.FALSE,
- }),
+ .windows => {
+ const pipe = try threaded.windowsCreatePipe(.{
+ .server = .{ .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
+ .client = .{ .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
+ .inbound = true,
+ });
+ read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } };
+ write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } };
+ },
else => {
- const pipe = try std.Io.Threaded.pipe2(.{});
+ const pipe = try std.Io.Threaded.pipe2(.{ .CLOEXEC = true });
read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } };
write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } };
},
diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig
@@ -11,7 +11,7 @@ const windows = std.os.windows;
const testing = std.testing;
const assert = std.debug.assert;
const posix = std.posix;
-const Writer = std.Io.Writer;
+const Writer = Io.Writer;
/// Currently this API only supports this value being set to stderr, which
/// happens automatically inside `start`.
@@ -21,13 +21,10 @@ io: Io,
terminal_mode: TerminalMode,
-update_worker: ?Io.Future(void),
+update_worker: ?Io.Future(WorkerError!void),
/// Atomically set by SIGWINCH as well as the root done() function.
redraw_event: Io.Event,
-/// Indicates a request to shut down and reset global state.
-/// Accessed atomically.
-done: bool,
need_clear: bool,
status: Status,
@@ -43,15 +40,19 @@ draw_buffer: []u8,
/// This is in a separate array from `node_storage` but with the same length so
/// that it can be iterated over efficiently without trashing too much of the
/// CPU cache.
-node_parents: []Node.Parent,
-node_storage: []Node.Storage,
-node_freelist_next: []Node.OptionalIndex,
+node_parents: [node_storage_buffer_len]Node.Parent,
+node_storage: [node_storage_buffer_len]Node.Storage,
+node_freelist_next: [node_storage_buffer_len]Node.OptionalIndex,
node_freelist: Freelist,
/// This is the number of elements in node arrays which have been used so far. Nodes before this
/// index are either active, or on the freelist. The remaining nodes are implicitly free. This
/// value may at times temporarily exceed the node count.
node_end_index: u32,
+ipc_next: Ipc.SlotAtomic,
+ipc: [ipc_storage_buffer_len]Ipc,
+ipc_files: [ipc_storage_buffer_len]Io.File,
+
start_failure: StartFailure,
pub const Status = enum {
@@ -77,6 +78,80 @@ const Freelist = packed struct(u32) {
generation: u24,
};
+pub const Ipc = packed struct(u32) {
+ /// mutex protecting `file` use, only locked by `serializeIpc`
+ locked: bool,
+ /// when unlocked: whether `file` is defined
+ /// when locked: whether `file` does not need to be closed
+ valid: bool,
+ unused: @Int(.unsigned, 32 - 2 - @bitSizeOf(Generation)) = 0,
+ generation: Generation,
+
+ pub const Slot = std.math.IntFittingRange(0, ipc_storage_buffer_len - 1);
+ pub const Generation = @Int(.unsigned, 32 - @bitSizeOf(Slot));
+
+ const SlotAtomic = @Int(.unsigned, std.math.ceilPowerOfTwoAssert(usize, @min(@bitSizeOf(Slot), 8)));
+
+ pub const Index = packed struct(u32) {
+ slot: Slot,
+ generation: Generation,
+ };
+
+ const Data = struct {
+ state: State,
+ bytes_read: u16,
+ main_index: u8,
+ start_index: u8,
+ nodes_len: u8,
+
+ const State = enum { unused, pending, ready };
+
+ /// No operations have been started on this file.
+ const unused: Data = .{
+ .state = .unused,
+ .bytes_read = 0,
+ .main_index = 0,
+ .start_index = 0,
+ .nodes_len = 0,
+ };
+
+ fn findLastPacket(data: *const Data, buffer: *const [max_packet_len]u8) struct { u16, u16 } {
+ assert(data.state == .ready);
+ var packet_start: u16 = 0;
+ var packet_end: u16 = 0;
+ const bytes_read = data.bytes_read;
+ while (bytes_read - packet_end >= 1) {
+ const nodes_len: u16 = buffer[packet_end];
+ const packet_len = 1 + nodes_len * (@sizeOf(Node.Storage) + @sizeOf(Node.Parent));
+ if (packet_end + packet_len > bytes_read) break;
+ packet_start = packet_end;
+ packet_end += packet_len;
+ }
+ return .{ packet_start, packet_end };
+ }
+
+ fn rebase(
+ data: *Data,
+ buffer: *[max_packet_len]u8,
+ vec: *[1][]u8,
+ batch: *std.Io.Batch,
+ slot: Slot,
+ packet_end: u16,
+ ) void {
+ assert(data.state == .ready);
+ const remaining = buffer[packet_end..data.bytes_read];
+ @memmove(buffer[0..remaining.len], remaining);
+ vec.* = .{buffer[remaining.len..]};
+ batch.addAt(slot, .{ .file_read_streaming = .{
+ .file = global_progress.ipc_files[slot],
+ .data = vec,
+ } });
+ data.state = .pending;
+ data.bytes_read = @intCast(remaining.len);
+ }
+ };
+};
+
pub const TerminalMode = union(enum) {
off,
ansi_escape_codes,
@@ -116,7 +191,7 @@ pub const Node = struct {
pub const none: Node = .{ .index = .none };
- pub const max_name_len = 40;
+ pub const max_name_len = 120;
const Storage = extern struct {
/// Little endian.
@@ -127,25 +202,16 @@ pub const Node = struct {
name: [max_name_len]u8 align(@alignOf(usize)),
/// Not thread-safe.
- fn getIpcFd(s: Storage) ?Io.File.Handle {
- return if (s.estimated_total_count == std.math.maxInt(u32)) switch (@typeInfo(Io.File.Handle)) {
- .int => @bitCast(s.completed_count),
- .pointer => @ptrFromInt(s.completed_count),
- else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
- } else null;
+ fn getIpcIndex(s: Storage) ?Ipc.Index {
+ return if (s.estimated_total_count == std.math.maxInt(u32)) @bitCast(s.completed_count) else null;
}
/// Thread-safe.
- fn setIpcFd(s: *Storage, fd: Io.File.Handle) void {
- const integer: u32 = switch (@typeInfo(Io.File.Handle)) {
- .int => @bitCast(fd),
- .pointer => @intFromPtr(fd),
- else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
- };
+ fn setIpcIndex(s: *Storage, ipc_index: Ipc.Index) void {
// `estimated_total_count` max int indicates the special state that
// causes `completed_count` to be treated as a file descriptor, so
// the order here matters.
- @atomicStore(u32, &s.completed_count, integer, .monotonic);
+ @atomicStore(u32, &s.completed_count, @bitCast(ipc_index), .monotonic);
@atomicStore(u32, &s.estimated_total_count, std.math.maxInt(u32), .release); // synchronizes with acquire in `serialize`
}
@@ -155,6 +221,14 @@ pub const Node = struct {
s.estimated_total_count = @byteSwap(s.estimated_total_count);
}
+ fn copyRoot(dest: *Node.Storage, src: *align(1) const Node.Storage) void {
+ dest.* = .{
+ .completed_count = src.completed_count,
+ .estimated_total_count = src.estimated_total_count,
+ .name = if (src.name[0] == 0) dest.name else src.name,
+ };
+ }
+
comptime {
assert((@sizeOf(Storage) % 4) == 0);
}
@@ -242,7 +316,7 @@ pub const Node = struct {
}
const free_index = @atomicRmw(u32, &global_progress.node_end_index, .Add, 1, .monotonic);
- if (free_index >= global_progress.node_storage.len) {
+ if (free_index >= node_storage_buffer_len) {
// Ran out of node storage memory. Progress for this node will not be tracked.
_ = @atomicRmw(u32, &global_progress.node_end_index, .Sub, 1, .monotonic);
return Node.none;
@@ -292,15 +366,17 @@ pub const Node = struct {
const index = n.index.unwrap() orelse return;
const storage = storageByIndex(index);
// Avoid u32 max int which is used to indicate a special state.
- const saturated = @min(std.math.maxInt(u32) - 1, count);
- @atomicStore(u32, &storage.estimated_total_count, saturated, .monotonic);
+ const saturated_total_count = @min(std.math.maxInt(u32) - 1, count);
+ @atomicStore(u32, &storage.estimated_total_count, saturated_total_count, .monotonic);
}
/// Thread-safe.
pub fn increaseEstimatedTotalItems(n: Node, count: usize) void {
const index = n.index.unwrap() orelse return;
const storage = storageByIndex(index);
- _ = @atomicRmw(u32, &storage.estimated_total_count, .Add, std.math.lossyCast(u32, count), .monotonic);
+ // Avoid u32 max int which is used to indicate a special state.
+ const saturated_total_count = @min(std.math.maxInt(u32) - 1, count);
+ _ = @atomicRmw(u32, &storage.estimated_total_count, .Add, saturated_total_count, .monotonic);
}
/// Finish a started `Node`. Thread-safe.
@@ -310,11 +386,25 @@ pub const Node = struct {
return;
}
const index = n.index.unwrap() orelse return;
+ const io = global_progress.io;
const parent_ptr = parentByIndex(index);
if (@atomicLoad(Node.Parent, parent_ptr, .monotonic).unwrap()) |parent_index| {
_ = @atomicRmw(u32, &storageByIndex(parent_index).completed_count, .Add, 1, .monotonic);
@atomicStore(Node.Parent, parent_ptr, .unused, .monotonic);
+ if (storageByIndex(index).getIpcIndex()) |ipc_index| {
+ const file = global_progress.ipc_files[ipc_index.slot];
+ const ipc = @atomicRmw(
+ Ipc,
+ &global_progress.ipc[ipc_index.slot],
+ .And,
+ .{ .locked = true, .valid = false, .generation = std.math.maxInt(Ipc.Generation) },
+ .release,
+ );
+ assert(ipc.valid and ipc.generation == ipc_index.generation);
+ if (!ipc.locked) file.close(io);
+ }
+
const freelist = &global_progress.node_freelist;
var old_freelist = @atomicLoad(Freelist, freelist, .monotonic);
while (true) {
@@ -332,34 +422,52 @@ pub const Node = struct {
};
}
} else {
- @atomicStore(bool, &global_progress.done, true, .monotonic);
- const io = global_progress.io;
- global_progress.redraw_event.set(io);
- if (global_progress.update_worker) |*worker| worker.await(io);
+ if (global_progress.update_worker) |*worker| worker.cancel(io) catch {};
+ for (&global_progress.ipc, &global_progress.ipc_files) |ipc, ipc_file| {
+ assert(!ipc.locked or !ipc.valid); // missing call to end()
+ if (ipc.locked or ipc.valid) ipc_file.close(io);
+ }
}
}
- /// Posix-only. Used by `std.process.Child`. Thread-safe.
- pub fn setIpcFd(node: Node, fd: Io.File.Handle) void {
+ /// Used by `std.process.Child`. Thread-safe.
+ pub fn setIpcFile(node: Node, expected_io_userdata: ?*anyopaque, file: Io.File) void {
const index = node.index.unwrap() orelse return;
- assert(fd >= 0);
- assert(fd != posix.STDOUT_FILENO);
- assert(fd != posix.STDIN_FILENO);
- assert(fd != posix.STDERR_FILENO);
- storageByIndex(index).setIpcFd(fd);
+ const io = global_progress.io;
+ assert(io.userdata == expected_io_userdata);
+ for (0..ipc_storage_buffer_len) |_| {
+ const slot: Ipc.Slot = @truncate(
+ @atomicRmw(Ipc.SlotAtomic, &global_progress.ipc_next, .Add, 1, .monotonic),
+ );
+ if (slot >= ipc_storage_buffer_len) continue;
+ const ipc_ptr = &global_progress.ipc[slot];
+ const ipc = @atomicLoad(Ipc, ipc_ptr, .monotonic);
+ if (ipc.locked or ipc.valid) continue;
+ const generation = ipc.generation +% 1;
+ if (@cmpxchgWeak(
+ Ipc,
+ ipc_ptr,
+ ipc,
+ .{ .locked = false, .valid = true, .generation = generation },
+ .acquire,
+ .monotonic,
+ )) |_| continue;
+ global_progress.ipc_files[slot] = file;
+ storageByIndex(index).setIpcIndex(.{ .slot = slot, .generation = generation });
+ break;
+ } else file.close(io);
}
- /// Posix-only. Thread-safe. Assumes the node is storing an IPC file
- /// descriptor.
- pub fn getIpcFd(node: Node) ?Io.File.Handle {
- const index = node.index.unwrap() orelse return null;
- const storage = storageByIndex(index);
- const int = @atomicLoad(u32, &storage.completed_count, .monotonic);
- return switch (@typeInfo(Io.File.Handle)) {
- .int => @bitCast(int),
- .pointer => @ptrFromInt(int),
- else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
- };
+ pub fn setIpcIndex(node: Node, ipc_index: Ipc.Index) void {
+ storageByIndex(node.index.unwrap() orelse return).setIpcIndex(ipc_index);
+ }
+
+ /// Not thread-safe.
+ pub fn takeIpcIndex(node: Node) ?Ipc.Index {
+ const storage = storageByIndex(node.index.unwrap() orelse return null);
+ assert(storage.estimated_total_count == std.math.maxInt(u32));
+ @atomicStore(u32, &storage.estimated_total_count, 0, .monotonic);
+ return @bitCast(storage.completed_count);
}
fn storageByIndex(index: Node.Index) *Node.Storage {
@@ -379,7 +487,9 @@ pub const Node = struct {
const storage = storageByIndex(free_index);
@atomicStore(u32, &storage.completed_count, 0, .monotonic);
- @atomicStore(u32, &storage.estimated_total_count, std.math.lossyCast(u32, estimated_total_items), .monotonic);
+ // Avoid u32 max int which is used to indicate a special state.
+ const saturated_total_count = @min(std.math.maxInt(u32) - 1, estimated_total_items);
+ @atomicStore(u32, &storage.estimated_total_count, saturated_total_count, .monotonic);
const name_len = @min(max_name_len, name.len);
copyAtomicStore(storage.name[0..name_len], name[0..name_len]);
if (name_len < storage.name.len)
@@ -406,16 +516,20 @@ var global_progress: Progress = .{
.rows = 0,
.cols = 0,
.draw_buffer = undefined,
- .done = false,
.need_clear = false,
.status = .working,
- .start_failure = .unstarted,
- .node_parents = &node_parents_buffer,
- .node_storage = &node_storage_buffer,
- .node_freelist_next = &node_freelist_next_buffer,
+ .node_parents = undefined,
+ .node_storage = undefined,
+ .node_freelist_next = undefined,
.node_freelist = .{ .head = .none, .generation = 0 },
.node_end_index = 0,
+
+ .ipc_next = 0,
+ .ipc = undefined,
+ .ipc_files = undefined,
+
+ .start_failure = .unstarted,
};
pub const StartFailure = union(enum) {
@@ -425,17 +539,23 @@ pub const StartFailure = union(enum) {
parent_ipc: error{ UnsupportedOperation, UnrecognizedFormat },
};
-const node_storage_buffer_len = 83;
-var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined;
-var node_storage_buffer: [node_storage_buffer_len]Node.Storage = undefined;
-var node_freelist_next_buffer: [node_storage_buffer_len]Node.OptionalIndex = undefined;
+/// One less than a power of two ensures `max_packet_len` is already a power of two.
+const node_storage_buffer_len = ipc_storage_buffer_len - 1;
+
+/// Power of two to avoid wasted `ipc_next` increments.
+const ipc_storage_buffer_len = 128;
+
+pub const max_packet_len = std.math.ceilPowerOfTwoAssert(
+ usize,
+ 1 + node_storage_buffer_len * (@sizeOf(Node.Storage) + @sizeOf(Node.OptionalIndex)),
+);
var default_draw_buffer: [4096]u8 = undefined;
var debug_start_trace = std.debug.Trace.init;
pub const have_ipc = switch (builtin.os.tag) {
- .wasi, .freestanding, .windows => false,
+ .wasi, .freestanding => false,
else => true,
};
@@ -467,9 +587,9 @@ pub fn start(io: Io, options: Options) Node {
}
debug_start_trace.add("first initialized here");
- @memset(global_progress.node_parents, .unused);
+ @memset(&global_progress.node_parents, .unused);
+ @memset(&global_progress.ipc, .{ .locked = false, .valid = false, .generation = 0 });
const root_node = Node.init(@enumFromInt(0), .none, options.root_name, options.estimated_total_items);
- global_progress.done = false;
global_progress.node_end_index = 1;
assert(options.draw_buffer.len >= 200);
@@ -477,21 +597,18 @@ pub fn start(io: Io, options: Options) Node {
global_progress.refresh_rate_ns = @intCast(options.refresh_rate_ns.toNanoseconds());
global_progress.initial_delay_ns = @intCast(options.initial_delay_ns.toNanoseconds());
- if (noop_impl)
- return Node.none;
+ if (noop_impl) return .none;
global_progress.io = io;
if (io.vtable.progressParentFile(io.userdata)) |ipc_file| {
global_progress.update_worker = io.concurrent(ipcThreadRun, .{ io, ipc_file }) catch |err| {
global_progress.start_failure = .{ .spawn_ipc_worker = err };
- return Node.none;
+ return .none;
};
} else |env_err| switch (env_err) {
error.EnvironmentVariableMissing => {
- if (options.disable_printing) {
- return Node.none;
- }
+ if (options.disable_printing) return .none;
const stderr: Io.File = .stderr();
global_progress.terminal = stderr;
if (stderr.enableAnsiEscapeCodes(io)) |_| {
@@ -504,14 +621,12 @@ pub fn start(io: Io, options: Options) Node {
} else |err| switch (err) {
error.Canceled => {
io.recancel();
- return Node.none;
+ return .none;
},
}
}
- if (global_progress.terminal_mode == .off) {
- return Node.none;
- }
+ if (global_progress.terminal_mode == .off) return .none;
if (have_sigwinch) {
const act: posix.Sigaction = .{
@@ -530,12 +645,12 @@ pub fn start(io: Io, options: Options) Node {
global_progress.update_worker = future;
} else |err| {
global_progress.start_failure = .{ .spawn_update_worker = err };
- return Node.none;
+ return .none;
}
},
else => |e| {
global_progress.start_failure = .{ .parent_ipc = e };
- return Node.none;
+ return .none;
},
}
@@ -548,58 +663,55 @@ pub fn setStatus(new_status: Status) void {
}
/// Returns whether a resize is needed to learn the terminal size.
-fn wait(io: Io, timeout_ns: u64) bool {
+fn wait(io: Io, timeout_ns: u64) Io.Cancelable!bool {
const timeout: Io.Timeout = .{ .duration = .{
.clock = .awake,
.raw = .fromNanoseconds(timeout_ns),
} };
const resize_flag = if (global_progress.redraw_event.waitTimeout(io, timeout)) |_| true else |err| switch (err) {
- error.Timeout, error.Canceled => false,
+ error.Timeout => false,
+ error.Canceled => |e| return e,
};
global_progress.redraw_event.reset();
return resize_flag or (global_progress.cols == 0);
}
-fn updateTask(io: Io) void {
+const WorkerError = error{WindowTooSmall} || Io.ConcurrentError || Io.Cancelable ||
+ Io.File.Writer.Error || Io.Operation.FileReadStreaming.Error;
+
+fn updateTask(io: Io) WorkerError!void {
// Store this data in the thread so that it does not need to be part of the
// linker data of the main executable.
var serialized_buffer: Serialized.Buffer = undefined;
+ serialized_buffer.init();
+ defer serialized_buffer.batch.cancel(io);
// In this function we bypass the wrapper code inside `Io.lockStderr` /
// `Io.tryLockStderr` in order to avoid clearing the terminal twice.
// We still want to go through the `Io` instance however in case it uses a
// task-switching mutex.
- {
- const resize_flag = wait(io, global_progress.initial_delay_ns);
- if (@atomicLoad(bool, &global_progress.done, .monotonic)) return;
- maybeUpdateSize(io, resize_flag) catch return;
-
- const buffer, _ = computeRedraw(&serialized_buffer);
- if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {
- defer io.unlockStderr();
- global_progress.need_clear = true;
- locked_stderr.file_writer.interface.writeAll(buffer) catch return;
- }
+ try maybeUpdateSize(io, try wait(io, global_progress.initial_delay_ns));
+ errdefer {
+ const cancel_protection = io.swapCancelProtection(.blocked);
+ defer _ = io.swapCancelProtection(cancel_protection);
+ const stderr = io.vtable.lockStderr(io.userdata, null) catch |err| switch (err) {
+ error.Canceled => unreachable, // blocked
+ };
+ defer io.unlockStderr();
+ clearWrittenWithEscapeCodes(stderr.file_writer) catch {};
}
-
while (true) {
- const resize_flag = wait(io, global_progress.refresh_rate_ns);
-
- if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
- const stderr = io.vtable.lockStderr(io.userdata, null) catch return;
- defer io.unlockStderr();
- return clearWrittenWithEscapeCodes(stderr.file_writer) catch {};
- }
-
- maybeUpdateSize(io, resize_flag) catch return;
-
- const buffer, _ = computeRedraw(&serialized_buffer);
- if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {
+ const buffer, _ = try computeRedraw(io, &serialized_buffer);
+ if (try io.vtable.tryLockStderr(io.userdata, null)) |locked_stderr| {
defer io.unlockStderr();
global_progress.need_clear = true;
- locked_stderr.file_writer.interface.writeAll(buffer) catch return;
+ locked_stderr.file_writer.interface.writeAll(buffer) catch |err| switch (err) {
+ error.WriteFailed => return locked_stderr.file_writer.err.?,
+ };
}
+
+ try maybeUpdateSize(io, try wait(io, global_progress.refresh_rate_ns));
}
}
@@ -611,79 +723,60 @@ fn windowsApiWriteMarker() void {
_ = windows.kernel32.WriteConsoleW(handle, &[_]u16{windows_api_start_marker}, 1, &num_chars_written, null);
}
-fn windowsApiUpdateTask(io: Io) void {
+fn windowsApiUpdateTask(io: Io) WorkerError!void {
+ // Store this data in the thread so that it does not need to be part of the
+ // linker data of the main executable.
var serialized_buffer: Serialized.Buffer = undefined;
+ serialized_buffer.init();
+ defer serialized_buffer.batch.cancel(io);
// In this function we bypass the wrapper code inside `Io.lockStderr` /
// `Io.tryLockStderr` in order to avoid clearing the terminal twice.
// We still want to go through the `Io` instance however in case it uses a
// task-switching mutex.
- {
- const resize_flag = wait(io, global_progress.initial_delay_ns);
- if (@atomicLoad(bool, &global_progress.done, .monotonic)) return;
- maybeUpdateSize(io, resize_flag) catch return;
-
- const buffer, const nl_n = computeRedraw(&serialized_buffer);
- if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {
- defer io.unlockStderr();
- windowsApiWriteMarker();
- global_progress.need_clear = true;
- locked_stderr.file_writer.interface.writeAll(buffer) catch return;
- windowsApiMoveToMarker(nl_n) catch return;
- }
+ try maybeUpdateSize(io, try wait(io, global_progress.initial_delay_ns));
+ errdefer {
+ const cancel_protection = io.swapCancelProtection(.blocked);
+ defer _ = io.swapCancelProtection(cancel_protection);
+ _ = io.vtable.lockStderr(io.userdata, null) catch |err| switch (err) {
+ error.Canceled => unreachable, // blocked
+ };
+ defer io.unlockStderr();
+ clearWrittenWindowsApi() catch {};
}
-
while (true) {
- const resize_flag = wait(io, global_progress.refresh_rate_ns);
-
- if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
- _ = io.vtable.lockStderr(io.userdata, null) catch return;
- defer io.unlockStderr();
- return clearWrittenWindowsApi() catch {};
- }
-
- maybeUpdateSize(io, resize_flag) catch return;
-
- const buffer, const nl_n = computeRedraw(&serialized_buffer);
+ const buffer, const nl_n = try computeRedraw(io, &serialized_buffer);
if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {
defer io.unlockStderr();
- clearWrittenWindowsApi() catch return;
+ try clearWrittenWindowsApi();
windowsApiWriteMarker();
global_progress.need_clear = true;
- locked_stderr.file_writer.interface.writeAll(buffer) catch return;
+ locked_stderr.file_writer.interface.writeAll(buffer) catch |err| switch (err) {
+ error.WriteFailed => return locked_stderr.file_writer.err.?,
+ };
windowsApiMoveToMarker(nl_n) catch return;
}
+
+ try maybeUpdateSize(io, try wait(io, global_progress.refresh_rate_ns));
}
}
-fn ipcThreadRun(io: Io, file: Io.File) void {
+fn ipcThreadRun(io: Io, file: Io.File) WorkerError!void {
// Store this data in the thread so that it does not need to be part of the
// linker data of the main executable.
var serialized_buffer: Serialized.Buffer = undefined;
+ serialized_buffer.init();
+ defer serialized_buffer.batch.cancel(io);
+ var fw = file.writerStreaming(io, &.{});
- {
- _ = wait(io, global_progress.initial_delay_ns);
-
- if (@atomicLoad(bool, &global_progress.done, .monotonic))
- return;
-
- const serialized = serialize(&serialized_buffer);
- writeIpc(io, file, serialized) catch |err| switch (err) {
- error.BrokenPipe => return,
- };
- }
-
+ _ = try io.sleep(.fromNanoseconds(global_progress.initial_delay_ns), .awake);
while (true) {
- _ = wait(io, global_progress.refresh_rate_ns);
-
- if (@atomicLoad(bool, &global_progress.done, .monotonic))
- return;
-
- const serialized = serialize(&serialized_buffer);
- writeIpc(io, file, serialized) catch |err| switch (err) {
- error.BrokenPipe => return,
+ writeIpc(&fw.interface, try serialize(io, &serialized_buffer)) catch |err| switch (err) {
+ error.WriteFailed => return fw.err.?,
};
+
+ _ = try io.sleep(.fromNanoseconds(global_progress.refresh_rate_ns), .awake);
}
}
@@ -862,31 +955,49 @@ const Serialized = struct {
const Buffer = struct {
parents: [node_storage_buffer_len]Node.Parent,
storage: [node_storage_buffer_len]Node.Storage,
- map: [node_storage_buffer_len]Node.OptionalIndex,
- parents_copy: [node_storage_buffer_len]Node.Parent,
- storage_copy: [node_storage_buffer_len]Node.Storage,
- ipc_metadata_fds_copy: [node_storage_buffer_len]Fd,
- ipc_metadata_copy: [node_storage_buffer_len]SavedMetadata,
-
- ipc_metadata_fds: [node_storage_buffer_len]Fd,
- ipc_metadata: [node_storage_buffer_len]SavedMetadata,
+ ipc_start: u8,
+ ipc_end: u8,
+ ipc_data: [ipc_storage_buffer_len]Ipc.Data,
+ ipc_buffers: [ipc_storage_buffer_len][max_packet_len]u8,
+ ipc_vecs: [ipc_storage_buffer_len][1][]u8,
+ batch_storage: [ipc_storage_buffer_len]Io.Operation.Storage,
+ batch: Io.Batch,
+
+ fn init(buffer: *Buffer) void {
+ buffer.ipc_start = 0;
+ buffer.ipc_end = 0;
+ @memset(&buffer.ipc_data, .unused);
+ buffer.batch = .init(&buffer.batch_storage);
+ }
};
};
-fn serialize(serialized_buffer: *Serialized.Buffer) Serialized {
- var serialized_len: usize = 0;
- var any_ipc = false;
+fn serialize(io: Io, serialized_buffer: *Serialized.Buffer) !Serialized {
+ var prev_parents: [node_storage_buffer_len]Node.Parent = undefined;
+ var prev_storage: [node_storage_buffer_len]Node.Storage = undefined;
+ {
+ const ipc_start = serialized_buffer.ipc_start;
+ const ipc_end = serialized_buffer.ipc_end;
+ @memcpy(prev_parents[ipc_start..ipc_end], serialized_buffer.parents[ipc_start..ipc_end]);
+ @memcpy(prev_storage[ipc_start..ipc_end], serialized_buffer.storage[ipc_start..ipc_end]);
+ }
// Iterate all of the nodes and construct a serializable copy of the state that can be examined
// without atomics. The `@min` call is here because `node_end_index` might briefly exceed the
// node count sometimes.
- const end_index = @min(@atomicLoad(u32, &global_progress.node_end_index, .monotonic), global_progress.node_storage.len);
+ const end_index = @min(
+ @atomicLoad(u32, &global_progress.node_end_index, .monotonic),
+ node_storage_buffer_len,
+ );
+ var map: [node_storage_buffer_len]Node.OptionalIndex = undefined;
+ var serialized_len: u8 = 0;
+ var maybe_ipc_start: ?u8 = null;
for (
global_progress.node_parents[0..end_index],
global_progress.node_storage[0..end_index],
- serialized_buffer.map[0..end_index],
- ) |*parent_ptr, *storage_ptr, *map| {
+ map[0..end_index],
+ ) |*parent_ptr, *storage_ptr, *map_entry| {
const parent = @atomicLoad(Node.Parent, parent_ptr, .monotonic);
if (parent == .unused) {
// 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 {
// parent, it will just not be printed at all. The general idea here is that performance
// is more important than 100% correct output every frame, given that this API is likely
// to be used in hot paths!
- map.* = .none;
+ map_entry.* = .none;
continue;
}
const dest_storage = &serialized_buffer.storage[serialized_len];
copyAtomicLoad(&dest_storage.name, &storage_ptr.name);
- dest_storage.estimated_total_count = @atomicLoad(u32, &storage_ptr.estimated_total_count, .acquire); // sychronizes with release in `setIpcFd`
+ dest_storage.estimated_total_count = @atomicLoad(u32, &storage_ptr.estimated_total_count, .acquire); // sychronizes with release in `setIpcIndex`
dest_storage.completed_count = @atomicLoad(u32, &storage_ptr.completed_count, .monotonic);
- any_ipc = any_ipc or (dest_storage.getIpcFd() != null);
serialized_buffer.parents[serialized_len] = parent;
- map.* = @enumFromInt(serialized_len);
+ map_entry.* = @enumFromInt(serialized_len);
+ if (maybe_ipc_start == null and dest_storage.getIpcIndex() != null) maybe_ipc_start = serialized_len;
serialized_len += 1;
}
@@ -919,266 +1030,212 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized {
parent.* = switch (parent.*) {
.unused => unreachable,
.none => .none,
- _ => |p| serialized_buffer.map[@intFromEnum(p)].toParent(),
+ _ => |p| map[@intFromEnum(p)].toParent(),
};
}
- // Find nodes which correspond to child processes.
- if (any_ipc)
- serialized_len = serializeIpc(serialized_len, serialized_buffer);
-
- return .{
- .parents = serialized_buffer.parents[0..serialized_len],
- .storage = serialized_buffer.storage[0..serialized_len],
+ // Fill pipe buffers.
+ const batch = &serialized_buffer.batch;
+ batch.awaitConcurrent(io, .{
+ .duration = .{ .raw = .zero, .clock = .awake },
+ }) catch |err| switch (err) {
+ error.Timeout => {},
+ else => |e| return e,
+ };
+ var ready_len: u8 = 0;
+ while (batch.next()) |operation| switch (operation.index) {
+ 0...ipc_storage_buffer_len - 1 => {
+ const ipc_data = &serialized_buffer.ipc_data[operation.index];
+ ipc_data.bytes_read += @intCast(
+ operation.result.file_read_streaming catch |err| switch (err) {
+ error.EndOfStream => {
+ const file = global_progress.ipc_files[operation.index];
+ const ipc = @atomicRmw(
+ Ipc,
+ &global_progress.ipc[operation.index],
+ .And,
+ .{
+ .locked = false,
+ .valid = true,
+ .generation = std.math.maxInt(Ipc.Generation),
+ },
+ .release,
+ );
+ assert(ipc.locked);
+ if (!ipc.valid) file.close(io);
+ ipc_data.* = .unused;
+ continue;
+ },
+ else => |e| return e,
+ },
+ );
+ assert(ipc_data.state == .pending);
+ ipc_data.state = .ready;
+ ready_len += 1;
+ },
+ else => unreachable,
};
-}
-
-const SavedMetadata = struct {
- remaining_read_trash_bytes: u16,
- main_index: u8,
- start_index: u8,
- nodes_len: u8,
-};
-
-const Fd = enum(i32) {
- _,
-
- fn init(fd: Io.File.Handle) Fd {
- return @enumFromInt(if (is_windows) @as(isize, @bitCast(@intFromPtr(fd))) else fd);
- }
-
- fn get(fd: Fd) Io.File.Handle {
- return if (is_windows)
- @ptrFromInt(@as(usize, @bitCast(@as(isize, @intFromEnum(fd)))))
- else
- @intFromEnum(fd);
- }
-};
-
-var ipc_metadata_len: u8 = 0;
-
-fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buffer) usize {
- const io = global_progress.io;
- const ipc_metadata_fds_copy = &serialized_buffer.ipc_metadata_fds_copy;
- const ipc_metadata_copy = &serialized_buffer.ipc_metadata_copy;
- const ipc_metadata_fds = &serialized_buffer.ipc_metadata_fds;
- const ipc_metadata = &serialized_buffer.ipc_metadata;
-
- var serialized_len = start_serialized_len;
- var pipe_buf: [2 * 4096]u8 = undefined;
-
- const old_ipc_metadata_fds = ipc_metadata_fds_copy[0..ipc_metadata_len];
- const old_ipc_metadata = ipc_metadata_copy[0..ipc_metadata_len];
- ipc_metadata_len = 0;
- main_loop: for (
- serialized_buffer.parents[0..serialized_len],
- serialized_buffer.storage[0..serialized_len],
- 0..,
+ // Find nodes which correspond to child processes.
+ const ipc_start = maybe_ipc_start orelse serialized_len;
+ serialized_buffer.ipc_start = ipc_start;
+ for (
+ serialized_buffer.parents[ipc_start..serialized_len],
+ serialized_buffer.storage[ipc_start..serialized_len],
+ ipc_start..,
) |main_parent, *main_storage, main_index| {
if (main_parent == .unused) continue;
- const file: Io.File = .{
- .handle = main_storage.getIpcFd() orelse continue,
- .flags = .{ .nonblocking = true },
- };
- const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata);
- var bytes_read: usize = 0;
- while (true) {
- const n = file.readStreaming(io, &.{pipe_buf[bytes_read..]}) catch |err| switch (err) {
- error.WouldBlock, error.EndOfStream => break,
- else => |e| {
- std.log.debug("failed to read child progress data: {t}", .{e});
- main_storage.completed_count = 0;
- main_storage.estimated_total_count = 0;
- continue :main_loop;
- },
- };
- if (opt_saved_metadata) |m| {
- if (m.remaining_read_trash_bytes > 0) {
- assert(bytes_read == 0);
- if (m.remaining_read_trash_bytes >= n) {
- m.remaining_read_trash_bytes = @intCast(m.remaining_read_trash_bytes - n);
- continue;
- }
- const src = pipe_buf[m.remaining_read_trash_bytes..n];
- @memmove(pipe_buf[0..src.len], src);
- m.remaining_read_trash_bytes = 0;
- bytes_read = src.len;
- continue;
- }
- }
- bytes_read += n;
- }
- // Ignore all but the last message on the pipe.
- var input: []u8 = pipe_buf[0..bytes_read];
- if (input.len == 0) {
- serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, 0, file.handle);
- continue;
- }
-
- const storage, const parents = while (true) {
- const subtree_len: usize = input[0];
- const expected_bytes = 1 + subtree_len * (@sizeOf(Node.Storage) + @sizeOf(Node.Parent));
- if (input.len < expected_bytes) {
- // Ignore short reads. We'll handle the next full message when it comes instead.
- const remaining_read_trash_bytes: u16 = @intCast(expected_bytes - input.len);
- serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, remaining_read_trash_bytes, file.handle);
- continue :main_loop;
- }
- if (input.len > expected_bytes) {
- input = input[expected_bytes..];
- continue;
- }
- const storage_bytes = input[1..][0 .. subtree_len * @sizeOf(Node.Storage)];
- const parents_bytes = input[1 + storage_bytes.len ..][0 .. subtree_len * @sizeOf(Node.Parent)];
- break .{
- std.mem.bytesAsSlice(Node.Storage, storage_bytes),
- std.mem.bytesAsSlice(Node.Parent, parents_bytes),
- };
- };
-
- const nodes_len: u8 = @intCast(@min(parents.len - 1, serialized_buffer.storage.len - serialized_len));
+ const ipc_index = main_storage.getIpcIndex() orelse continue;
+ const ipc = &global_progress.ipc[ipc_index.slot];
+ const ipc_data = &serialized_buffer.ipc_data[ipc_index.slot];
+ state: switch (ipc_data.state) {
+ .unused => {
+ if (@cmpxchgWeak(
+ Ipc,
+ ipc,
+ .{ .locked = false, .valid = true, .generation = ipc_index.generation },
+ .{ .locked = true, .valid = true, .generation = ipc_index.generation },
+ .acquire,
+ .monotonic,
+ )) |_| continue;
+
+ const ipc_vec = &serialized_buffer.ipc_vecs[ipc_index.slot];
+ ipc_vec.* = .{&serialized_buffer.ipc_buffers[ipc_index.slot]};
+ batch.addAt(ipc_index.slot, .{ .file_read_streaming = .{
+ .file = global_progress.ipc_files[ipc_index.slot],
+ .data = ipc_vec,
+ } });
+
+ ipc_data.* = .{
+ .state = .pending,
+ .bytes_read = 0,
+ .main_index = @intCast(main_index),
+ .start_index = serialized_len,
+ .nodes_len = 0,
+ };
+ main_storage.completed_count = 0;
+ main_storage.estimated_total_count = 0;
+ },
+ .pending => {
+ const start_index = ipc_data.start_index;
+ const nodes_len = @min(ipc_data.nodes_len, node_storage_buffer_len - serialized_len);
+
+ main_storage.copyRoot(&prev_storage[ipc_data.main_index]);
+ @memcpy(
+ serialized_buffer.storage[serialized_len..][0..nodes_len],
+ prev_storage[start_index..][0..nodes_len],
+ );
+ for (
+ serialized_buffer.parents[serialized_len..][0..nodes_len],
+ prev_parents[serialized_len..][0..nodes_len],
+ ) |*parent, prev_parent| parent.* = switch (prev_parent) {
+ .none, .unused => .none,
+ _ => if (@intFromEnum(prev_parent) == ipc_data.main_index)
+ @enumFromInt(main_index)
+ else if (@intFromEnum(prev_parent) >= start_index and
+ @intFromEnum(prev_parent) < start_index + nodes_len)
+ @enumFromInt(@intFromEnum(prev_parent) - start_index + serialized_len)
+ else
+ .none,
+ };
- // Remember in case the pipe is empty on next update.
- ipc_metadata_fds[ipc_metadata_len] = Fd.init(file.handle);
- ipc_metadata[ipc_metadata_len] = .{
- .remaining_read_trash_bytes = 0,
- .start_index = @intCast(serialized_len),
- .nodes_len = nodes_len,
- .main_index = @intCast(main_index),
- };
- ipc_metadata_len += 1;
-
- // Mount the root here.
- copyRoot(main_storage, &storage[0]);
- if (is_big_endian) main_storage.byteSwap();
-
- // Copy the rest of the tree to the end.
- const storage_dest = serialized_buffer.storage[serialized_len..][0..nodes_len];
- @memcpy(storage_dest, storage[1..][0..nodes_len]);
-
- // Always little-endian over the pipe.
- if (is_big_endian) for (storage_dest) |*s| s.byteSwap();
-
- // Patch up parent pointers taking into account how the subtree is mounted.
- for (serialized_buffer.parents[serialized_len..][0..nodes_len], parents[1..][0..nodes_len]) |*dest, p| {
- dest.* = switch (p) {
- // Fix bad data so the rest of the code does not see `unused`.
- .none, .unused => .none,
- // Root node is being mounted here.
- @as(Node.Parent, @enumFromInt(0)) => @enumFromInt(main_index),
- // Other nodes mounted at the end.
- // Don't trust child data; if the data is outside the expected range, ignore the data.
- // This also handles the case when data was truncated.
- _ => |off| if (@intFromEnum(off) > nodes_len)
- .none
- else
- @enumFromInt(serialized_len + @intFromEnum(off) - 1),
- };
+ ipc_data.main_index = @intCast(main_index);
+ ipc_data.start_index = serialized_len;
+ ipc_data.nodes_len = nodes_len;
+ serialized_len += nodes_len;
+ },
+ .ready => {
+ const ipc_buffer = &serialized_buffer.ipc_buffers[ipc_index.slot];
+ const packet_start, const packet_end = ipc_data.findLastPacket(ipc_buffer);
+ const packet_is_empty = packet_end - packet_start <= 1;
+ if (!packet_is_empty) {
+ const storage, const parents, const nodes_len = packet_contents: {
+ var packet_index: usize = packet_start;
+ const nodes_len: u16 = ipc_buffer[packet_index];
+ packet_index += 1;
+ const storage_bytes =
+ ipc_buffer[packet_index..][0 .. nodes_len * @sizeOf(Node.Storage)];
+ packet_index += storage_bytes.len;
+ const parents_bytes =
+ ipc_buffer[packet_index..][0 .. nodes_len * @sizeOf(Node.Parent)];
+ packet_index += parents_bytes.len;
+ assert(packet_index == packet_end);
+ const storage: []align(1) const Node.Storage = @ptrCast(storage_bytes);
+ const parents: []align(1) const Node.Parent = @ptrCast(parents_bytes);
+ const children_nodes_len =
+ @min(nodes_len - 1, node_storage_buffer_len - serialized_len);
+ break :packet_contents .{ storage, parents, children_nodes_len };
+ };
+
+ // Mount the root here.
+ main_storage.copyRoot(&storage[0]);
+ if (is_big_endian) main_storage.byteSwap();
+
+ // Copy the rest of the tree to the end.
+ const serialized_storage =
+ serialized_buffer.storage[serialized_len..][0..nodes_len];
+ @memcpy(serialized_storage, storage[1..][0..nodes_len]);
+ if (is_big_endian) for (serialized_storage) |*s| s.byteSwap();
+
+ // Patch up parent pointers taking into account how the subtree is mounted.
+ for (
+ serialized_buffer.parents[serialized_len..][0..nodes_len],
+ parents[1..][0..nodes_len],
+ ) |*parent, prev_parent| parent.* = switch (prev_parent) {
+ // Fix bad data so the rest of the code does not see `unused`.
+ .none, .unused => .none,
+ // Root node is being mounted here.
+ @as(Node.Parent, @enumFromInt(0)) => @enumFromInt(main_index),
+ // Other nodes mounted at the end.
+ // Don't trust child data; if the data is outside the expected range,
+ // ignore the data. This also handles the case when data was truncated.
+ _ => if (@intFromEnum(prev_parent) <= nodes_len)
+ @enumFromInt(@intFromEnum(prev_parent) - 1 + serialized_len)
+ else
+ .none,
+ };
+
+ ipc_data.main_index = @intCast(main_index);
+ ipc_data.start_index = serialized_len;
+ ipc_data.nodes_len = nodes_len;
+ serialized_len += nodes_len;
+ }
+ const ipc_vec = &serialized_buffer.ipc_vecs[ipc_index.slot];
+ ipc_data.rebase(ipc_buffer, ipc_vec, batch, ipc_index.slot, packet_end);
+ ready_len -= 1;
+ if (packet_is_empty) continue :state .pending;
+ },
}
-
- serialized_len += nodes_len;
- }
-
- // Save a copy in case any pipes are empty on the next update.
- @memcpy(serialized_buffer.parents_copy[0..serialized_len], serialized_buffer.parents[0..serialized_len]);
- @memcpy(serialized_buffer.storage_copy[0..serialized_len], serialized_buffer.storage[0..serialized_len]);
- @memcpy(ipc_metadata_fds_copy[0..ipc_metadata_len], ipc_metadata_fds[0..ipc_metadata_len]);
- @memcpy(ipc_metadata_copy[0..ipc_metadata_len], ipc_metadata[0..ipc_metadata_len]);
-
- return serialized_len;
-}
-
-fn copyRoot(dest: *Node.Storage, src: *align(1) Node.Storage) void {
- dest.* = .{
- .completed_count = src.completed_count,
- .estimated_total_count = src.estimated_total_count,
- .name = if (src.name[0] == 0) dest.name else src.name,
- };
-}
-
-fn findOld(
- ipc_fd: Io.File.Handle,
- old_metadata_fds: []Fd,
- old_metadata: []SavedMetadata,
-) ?*SavedMetadata {
- for (old_metadata_fds, old_metadata) |fd, *m| {
- if (fd.get() == ipc_fd)
- return m;
}
- return null;
-}
-
-fn useSavedIpcData(
- start_serialized_len: usize,
- serialized_buffer: *Serialized.Buffer,
- main_storage: *Node.Storage,
- main_index: usize,
- opt_saved_metadata: ?*SavedMetadata,
- remaining_read_trash_bytes: u16,
- fd: Io.File.Handle,
-) usize {
- const parents_copy = &serialized_buffer.parents_copy;
- const storage_copy = &serialized_buffer.storage_copy;
- const ipc_metadata_fds = &serialized_buffer.ipc_metadata_fds;
- const ipc_metadata = &serialized_buffer.ipc_metadata;
-
- const saved_metadata = opt_saved_metadata orelse {
- main_storage.completed_count = 0;
- main_storage.estimated_total_count = 0;
- if (remaining_read_trash_bytes > 0) {
- ipc_metadata_fds[ipc_metadata_len] = Fd.init(fd);
- ipc_metadata[ipc_metadata_len] = .{
- .remaining_read_trash_bytes = remaining_read_trash_bytes,
- .start_index = @intCast(start_serialized_len),
- .nodes_len = 0,
- .main_index = @intCast(main_index),
- };
- ipc_metadata_len += 1;
- }
- return start_serialized_len;
+ serialized_buffer.ipc_end = serialized_len;
+
+ // Ignore data from unused pipes. This ensures that if a child process exists we will
+ // eventually see `EndOfStream` and close the pipe.
+ if (ready_len > 0) for (
+ &serialized_buffer.ipc_data,
+ &serialized_buffer.ipc_buffers,
+ &serialized_buffer.ipc_vecs,
+ 0..,
+ ) |*ipc_data, *ipc_buffer, *ipc_vec, ipc_slot| switch (ipc_data.state) {
+ .unused, .pending => {},
+ .ready => {
+ _, const packet_end = ipc_data.findLastPacket(ipc_buffer);
+ ipc_data.rebase(ipc_buffer, ipc_vec, batch, @intCast(ipc_slot), packet_end);
+ ready_len -= 1;
+ },
};
+ assert(ready_len == 0);
- const start_index = saved_metadata.start_index;
- const nodes_len = @min(saved_metadata.nodes_len, serialized_buffer.storage.len - start_serialized_len);
- const old_main_index = saved_metadata.main_index;
-
- ipc_metadata_fds[ipc_metadata_len] = Fd.init(fd);
- ipc_metadata[ipc_metadata_len] = .{
- .remaining_read_trash_bytes = remaining_read_trash_bytes,
- .start_index = @intCast(start_serialized_len),
- .nodes_len = nodes_len,
- .main_index = @intCast(main_index),
+ return .{
+ .parents = serialized_buffer.parents[0..serialized_len],
+ .storage = serialized_buffer.storage[0..serialized_len],
};
- ipc_metadata_len += 1;
-
- const parents = parents_copy[start_index..][0..nodes_len];
- const storage = storage_copy[start_index..][0..nodes_len];
-
- copyRoot(main_storage, &storage_copy[old_main_index]);
-
- @memcpy(serialized_buffer.storage[start_serialized_len..][0..storage.len], storage);
-
- for (serialized_buffer.parents[start_serialized_len..][0..parents.len], parents) |*dest, p| {
- dest.* = switch (p) {
- .none, .unused => .none,
- _ => |prev| d: {
- if (@intFromEnum(prev) == old_main_index) {
- break :d @enumFromInt(main_index);
- } else if (@intFromEnum(prev) > nodes_len) {
- break :d .none;
- } else {
- break :d @enumFromInt(@intFromEnum(prev) - start_index + start_serialized_len);
- }
- },
- };
- }
-
- return start_serialized_len + storage.len;
}
-fn computeRedraw(serialized_buffer: *Serialized.Buffer) struct { []u8, usize } {
- const serialized = serialize(serialized_buffer);
+fn computeRedraw(io: Io, serialized_buffer: *Serialized.Buffer) !struct { []u8, usize } {
+ if (global_progress.rows == 0 or global_progress.cols == 0) return error.WindowTooSmall;
+
+ const serialized = try serialize(io, serialized_buffer);
// Now we can analyze our copy of the graph without atomics, reconstructing
// children lists which do not exist in the canonical data. These are
@@ -1413,9 +1470,7 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool {
return nl_n + 2 < p.rows;
}
-var remaining_write_trash_bytes: usize = 0;
-
-fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!void {
+fn writeIpc(writer: *Io.Writer, serialized: Serialized) Io.Writer.Error!void {
// Byteswap if necessary to ensure little endian over the pipe. This is
// needed because the parent or child process might be running in qemu.
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
const storage = std.mem.sliceAsBytes(serialized.storage);
const parents = std.mem.sliceAsBytes(serialized.parents);
- var vecs: [3][]const u8 = .{ header, storage, parents };
-
- // Ensures the packet can fit in the pipe buffer.
- const upper_bound_msg_len = 1 + node_storage_buffer_len * @sizeOf(Node.Storage) +
- node_storage_buffer_len * @sizeOf(Node.OptionalIndex);
- comptime assert(upper_bound_msg_len <= 4096);
-
- while (remaining_write_trash_bytes > 0) {
- // We do this in a separate write call to give a better chance for the
- // writev below to be in a single packet.
- const n = @min(parents.len, remaining_write_trash_bytes);
- if (file.writeStreaming(io, &.{}, &.{parents[0..n]}, 1)) |written| {
- remaining_write_trash_bytes -= written;
- continue;
- } else |err| switch (err) {
- error.WouldBlock => return,
- error.BrokenPipe => return error.BrokenPipe,
- else => |e| {
- std.log.debug("failed to send progress to parent process: {t}", .{e});
- return error.BrokenPipe;
- },
- }
- }
-
- // If this write would block we do not want to keep trying, but we need to
- // know if a partial message was written.
- if (writevNonblock(io, file, &vecs)) |written| {
- const total = header.len + storage.len + parents.len;
- if (written < total) {
- remaining_write_trash_bytes = total - written;
- }
- } else |err| switch (err) {
- error.WouldBlock => {},
- error.BrokenPipe => return error.BrokenPipe,
- else => |e| {
- std.log.debug("failed to send progress to parent process: {t}", .{e});
- return error.BrokenPipe;
- },
- }
-}
-
-fn writevNonblock(io: Io, file: Io.File, iov: [][]const u8) Io.File.Writer.Error!usize {
- var iov_index: usize = 0;
- var written: usize = 0;
- var total_written: usize = 0;
- while (true) {
- while (if (iov_index < iov.len)
- written >= iov[iov_index].len
- else
- return total_written) : (iov_index += 1) written -= iov[iov_index].len;
- iov[iov_index].ptr += written;
- iov[iov_index].len -= written;
- written = try file.writeStreaming(io, &.{}, iov, 1);
- if (written == 0) return total_written;
- total_written += written;
- }
+ var vec = [3][]const u8{ header, storage, parents };
+ try writer.writeVecAll(&vec);
}
fn maybeUpdateSize(io: Io, resize_flag: bool) !void {
diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig
@@ -598,7 +598,11 @@ const WindowsThreadImpl = struct {
}
fn join(self: Impl) void {
- windows.WaitForSingleObjectEx(self.thread.thread_handle, windows.INFINITE, false) catch unreachable;
+ const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
+ switch (windows.ntdll.NtWaitForSingleObject(self.thread.thread_handle, windows.FALSE, &infinite_timeout)) {
+ windows.NTSTATUS.WAIT_0 => {},
+ else => |status| windows.unexpectedStatus(status) catch unreachable,
+ }
windows.CloseHandle(self.thread.thread_handle);
assert(self.thread.completion.load(.seq_cst) == .completed);
self.thread.free();
diff --git a/lib/std/mem/Allocator.zig b/lib/std/mem/Allocator.zig
@@ -452,12 +452,23 @@ pub fn dupe(allocator: Allocator, comptime T: type, m: []const T) Error![]T {
return new_buf;
}
+/// Deprecated in favor of `dupeSentinel`
/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
pub fn dupeZ(allocator: Allocator, comptime T: type, m: []const T) Error![:0]T {
+ return allocator.dupeSentinel(T, m, 0);
+}
+
+/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
+pub fn dupeSentinel(
+ allocator: Allocator,
+ comptime T: type,
+ m: []const T,
+ comptime sentinel: T,
+) Error![:sentinel]T {
const new_buf = try allocator.alloc(T, m.len + 1);
@memcpy(new_buf[0..m.len], m);
- new_buf[m.len] = 0;
- return new_buf[0..m.len :0];
+ new_buf[m.len] = sentinel;
+ return new_buf[0..m.len :sentinel];
}
/// An allocator that always fails to allocate.
diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig
@@ -1848,6 +1848,24 @@ pub const F = struct {
pub const RDLCK = if (is_sparc) 1 else 0;
pub const WRLCK = if (is_sparc) 2 else 1;
pub const UNLCK = if (is_sparc) 3 else 2;
+
+ pub const LINUX_SPECIFIC_BASE = 1024;
+
+ pub const SETLEASE = LINUX_SPECIFIC_BASE + 0;
+ pub const GETLEASE = LINUX_SPECIFIC_BASE + 1;
+ pub const NOTIFY = LINUX_SPECIFIC_BASE + 2;
+ pub const DUPFD_QUERY = LINUX_SPECIFIC_BASE + 3;
+ pub const CREATED_QUERY = LINUX_SPECIFIC_BASE + 4;
+ pub const CANCELLK = LINUX_SPECIFIC_BASE + 5;
+ pub const DUPFD_CLOEXEC = LINUX_SPECIFIC_BASE + 6;
+ pub const SETPIPE_SZ = LINUX_SPECIFIC_BASE + 7;
+ pub const GETPIPE_SZ = LINUX_SPECIFIC_BASE + 8;
+ pub const ADD_SEALS = LINUX_SPECIFIC_BASE + 9;
+ pub const GET_SEALS = LINUX_SPECIFIC_BASE + 10;
+ pub const GET_RW_HINT = LINUX_SPECIFIC_BASE + 11;
+ pub const SET_RW_HINT = LINUX_SPECIFIC_BASE + 12;
+ pub const GET_FILE_RW_HINT = LINUX_SPECIFIC_BASE + 13;
+ pub const SET_FILE_RW_HINT = LINUX_SPECIFIC_BASE + 14;
};
pub const F_OWNER = enum(i32) {
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig
@@ -521,7 +521,7 @@ pub const FILE = struct {
_,
pub const VALID_FLAGS: @This() = @enumFromInt(0b11);
- } = .ASYNCHRONOUS,
+ },
/// The file being opened must not be a directory file or this call
/// fails. The file object being opened can represent a data file, a
/// logical, virtual, or physical device, or a volume.
@@ -2324,12 +2324,12 @@ pub fn GetProcessHeap() ?*HEAP {
// ref: um/winternl.h
pub const OBJECT_ATTRIBUTES = extern struct {
- Length: ULONG,
- RootDirectory: ?HANDLE,
- ObjectName: ?*UNICODE_STRING,
- Attributes: ATTRIBUTES,
- SecurityDescriptor: ?*anyopaque,
- SecurityQualityOfService: ?*anyopaque,
+ Length: ULONG = @sizeOf(OBJECT_ATTRIBUTES),
+ RootDirectory: ?HANDLE = null,
+ ObjectName: ?*UNICODE_STRING = @constCast(&UNICODE_STRING.empty),
+ Attributes: ATTRIBUTES = .{},
+ SecurityDescriptor: ?*anyopaque = null,
+ SecurityQualityOfService: ?*anyopaque = null,
// Valid values for the Attributes field
pub const ATTRIBUTES = packed struct(ULONG) {
@@ -2420,14 +2420,10 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
.Buffer = @constCast(sub_path_w.ptr),
};
const attr: OBJECT_ATTRIBUTES = .{
- .Length = @sizeOf(OBJECT_ATTRIBUTES),
.RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir,
- .Attributes = .{
- .INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false,
- },
+ .Attributes = .{ .INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false },
.ObjectName = &nt_name,
.SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null,
- .SecurityQualityOfService = null,
};
var io: IO_STATUS_BLOCK = undefined;
while (true) {
@@ -2475,7 +2471,8 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
// call has failed. There is not really a sane way to handle
// this other than retrying the creation after the OS finishes
// the deletion.
- _ = kernel32.SleepEx(1, TRUE);
+ const delay_one_ms: LARGE_INTEGER = -(std.time.ns_per_ms / 100);
+ _ = ntdll.NtDelayExecution(TRUE, &delay_one_ms);
continue;
},
.VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
@@ -2506,151 +2503,6 @@ pub fn GetCurrentThreadId() DWORD {
pub fn GetLastError() Win32Error {
return @enumFromInt(teb().LastErrorValue);
}
-
-pub const CreatePipeError = error{ Unexpected, SystemResources };
-
-var npfs: ?HANDLE = null;
-
-/// A Zig wrapper around `NtCreateNamedPipeFile` and `NtCreateFile` syscalls.
-/// It implements similar behavior to `CreatePipe` and is meant to serve
-/// as a direct substitute for that call.
-pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void {
- // Up to NT 5.2 (Windows XP/Server 2003), `CreatePipe` would generate a pipe similar to:
- //
- // \??\pipe\Win32Pipes.{pid}.{count}
- //
- // where `pid` is the process id and count is a incrementing counter.
- // The implementation was changed after NT 6.0 (Vista) to open a handle to the Named Pipe File System
- // and use that as the root directory for `NtCreateNamedPipeFile`.
- // This object is visible under the NPFS but has no filename attached to it.
- //
- // This implementation replicates how `CreatePipe` works in modern Windows versions.
- const opt_dev_handle = @atomicLoad(?HANDLE, &npfs, .seq_cst);
- const dev_handle = opt_dev_handle orelse blk: {
- const str = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\NamedPipe\\");
- const len: u16 = @truncate(str.len * @sizeOf(u16));
- const name: UNICODE_STRING = .{
- .Length = len,
- .MaximumLength = len,
- .Buffer = @ptrCast(@constCast(str)),
- };
- const attrs: OBJECT_ATTRIBUTES = .{
- .ObjectName = @constCast(&name),
- .Length = @sizeOf(OBJECT_ATTRIBUTES),
- .RootDirectory = null,
- .Attributes = .{},
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
- };
-
- var iosb: IO_STATUS_BLOCK = undefined;
- var handle: HANDLE = undefined;
- switch (ntdll.NtCreateFile(
- &handle,
- .{
- .STANDARD = .{ .SYNCHRONIZE = true },
- .GENERIC = .{ .READ = true },
- },
- @constCast(&attrs),
- &iosb,
- null,
- .{},
- .VALID_FLAGS,
- .OPEN,
- .{ .IO = .SYNCHRONOUS_NONALERT },
- null,
- 0,
- )) {
- .SUCCESS => {},
- // Judging from the ReactOS sources this is technically possible.
- .INSUFFICIENT_RESOURCES => return error.SystemResources,
- .INVALID_PARAMETER => unreachable,
- else => |e| return unexpectedStatus(e),
- }
- if (@cmpxchgStrong(?HANDLE, &npfs, null, handle, .seq_cst, .seq_cst)) |xchg| {
- CloseHandle(handle);
- break :blk xchg.?;
- } else break :blk handle;
- };
-
- const name: UNICODE_STRING = .{ .Buffer = null, .Length = 0, .MaximumLength = 0 };
- var attrs: OBJECT_ATTRIBUTES = .{
- .ObjectName = @constCast(&name),
- .Length = @sizeOf(OBJECT_ATTRIBUTES),
- .RootDirectory = dev_handle,
- .Attributes = .{ .INHERIT = sattr.bInheritHandle != FALSE },
- .SecurityDescriptor = sattr.lpSecurityDescriptor,
- .SecurityQualityOfService = null,
- };
-
- // 120 second relative timeout in 100ns units.
- const default_timeout: LARGE_INTEGER = (-120 * std.time.ns_per_s) / 100;
- var iosb: IO_STATUS_BLOCK = undefined;
- var read: HANDLE = undefined;
- switch (ntdll.NtCreateNamedPipeFile(
- &read,
- .{
- .SPECIFIC = .{ .FILE_PIPE = .{
- .WRITE_ATTRIBUTES = true,
- } },
- .STANDARD = .{ .SYNCHRONIZE = true },
- .GENERIC = .{ .READ = true },
- },
- &attrs,
- &iosb,
- .{ .READ = true, .WRITE = true },
- .CREATE,
- .{ .IO = .SYNCHRONOUS_NONALERT },
- .{ .TYPE = .BYTE_STREAM },
- .{ .MODE = .BYTE_STREAM },
- .{ .OPERATION = .QUEUE },
- 1,
- 4096,
- 4096,
- @constCast(&default_timeout),
- )) {
- .SUCCESS => {},
- .INVALID_PARAMETER => unreachable,
- .INSUFFICIENT_RESOURCES => return error.SystemResources,
- else => |e| return unexpectedStatus(e),
- }
- errdefer CloseHandle(read);
-
- attrs.RootDirectory = read;
-
- var write: HANDLE = undefined;
- switch (ntdll.NtCreateFile(
- &write,
- .{
- .SPECIFIC = .{ .FILE_PIPE = .{
- .READ_ATTRIBUTES = true,
- } },
- .STANDARD = .{ .SYNCHRONIZE = true },
- .GENERIC = .{ .WRITE = true },
- },
- &attrs,
- &iosb,
- null,
- .{},
- .VALID_FLAGS,
- .OPEN,
- .{
- .IO = .SYNCHRONOUS_NONALERT,
- .NON_DIRECTORY_FILE = true,
- },
- null,
- 0,
- )) {
- .SUCCESS => {},
- .INVALID_PARAMETER => unreachable,
- .INSUFFICIENT_RESOURCES => return error.SystemResources,
- else => |e| return unexpectedStatus(e),
- }
-
- rd.* = read;
- wr.* = write;
-}
-
/// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls.
/// It implements similar behavior to `DeviceIoControl` and is meant to serve
/// as a direct substitute for that call.
@@ -2707,66 +2559,6 @@ pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWOR
return bytes;
}
-pub const SetHandleInformationError = error{Unexpected};
-
-pub fn SetHandleInformation(h: HANDLE, mask: DWORD, flags: DWORD) SetHandleInformationError!void {
- if (kernel32.SetHandleInformation(h, mask, flags) == 0) {
- switch (GetLastError()) {
- else => |err| return unexpectedError(err),
- }
- }
-}
-
-pub const WaitForSingleObjectError = error{
- WaitAbandoned,
- WaitTimeOut,
- Unexpected,
-};
-
-pub fn WaitForSingleObject(handle: HANDLE, milliseconds: DWORD) WaitForSingleObjectError!void {
- return WaitForSingleObjectEx(handle, milliseconds, false);
-}
-
-pub fn WaitForSingleObjectEx(handle: HANDLE, milliseconds: DWORD, alertable: bool) WaitForSingleObjectError!void {
- switch (kernel32.WaitForSingleObjectEx(handle, milliseconds, @intFromBool(alertable))) {
- WAIT_ABANDONED => return error.WaitAbandoned,
- WAIT_OBJECT_0 => return,
- WAIT_TIMEOUT => return error.WaitTimeOut,
- WAIT_FAILED => switch (GetLastError()) {
- else => |err| return unexpectedError(err),
- },
- else => return error.Unexpected,
- }
-}
-
-pub fn WaitForMultipleObjectsEx(handles: []const HANDLE, waitAll: bool, milliseconds: DWORD, alertable: bool) !u32 {
- assert(handles.len > 0 and handles.len <= MAXIMUM_WAIT_OBJECTS);
- const nCount: DWORD = @as(DWORD, @intCast(handles.len));
- switch (kernel32.WaitForMultipleObjectsEx(
- nCount,
- handles.ptr,
- @intFromBool(waitAll),
- milliseconds,
- @intFromBool(alertable),
- )) {
- WAIT_OBJECT_0...WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS => |n| {
- const handle_index = n - WAIT_OBJECT_0;
- assert(handle_index < nCount);
- return handle_index;
- },
- WAIT_ABANDONED_0...WAIT_ABANDONED_0 + MAXIMUM_WAIT_OBJECTS => |n| {
- const handle_index = n - WAIT_ABANDONED_0;
- assert(handle_index < nCount);
- return error.WaitAbandoned;
- },
- WAIT_TIMEOUT => return error.WaitTimeOut,
- WAIT_FAILED => switch (GetLastError()) {
- else => |err| return unexpectedError(err),
- },
- else => return error.Unexpected,
- }
-}
-
pub const CreateIoCompletionPortError = error{Unexpected};
pub fn CreateIoCompletionPort(
@@ -2878,21 +2670,6 @@ pub fn CloseHandle(hObject: HANDLE) void {
assert(ntdll.NtClose(hObject) == .SUCCESS);
}
-pub const GetStdHandleError = error{
- NoStandardHandleAttached,
- Unexpected,
-};
-
-pub fn GetStdHandle(handle_id: DWORD) GetStdHandleError!HANDLE {
- const handle = kernel32.GetStdHandle(handle_id) orelse return error.NoStandardHandleAttached;
- if (handle == INVALID_HANDLE_VALUE) {
- switch (GetLastError()) {
- else => |err| return unexpectedError(err),
- }
- }
- return handle;
-}
-
pub const QueryObjectNameError = error{
AccessDenied,
InvalidHandle,
@@ -3545,6 +3322,12 @@ pub fn nanoSecondsToFileTime(ns: Io.Timestamp) FILETIME {
};
}
+/// Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a
+/// redundant copy of the uppercase data.
+pub inline fn toUpperWtf16(c: u16) u16 {
+ return (if (builtin.os.tag != .windows or @inComptime()) nls.upcaseW else ntdll.RtlUpcaseUnicodeChar)(c);
+}
+
/// Compares two WTF16 strings using the equivalent functionality of
/// `RtlEqualUnicodeString` (with case insensitive comparison enabled).
/// This function can be called on any target.
@@ -3598,19 +3381,12 @@ pub fn eqlIgnoreCaseWtf8(a: []const u8, b: []const u8) bool {
var a_wtf8_it = std.unicode.Wtf8View.initUnchecked(a).iterator();
var b_wtf8_it = std.unicode.Wtf8View.initUnchecked(b).iterator();
- // Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a
- // redundant copy of the uppercase data.
- const upcaseImpl = switch (builtin.os.tag) {
- .windows => if (@inComptime()) nls.upcaseW else ntdll.RtlUpcaseUnicodeChar,
- else => nls.upcaseW,
- };
-
while (true) {
const a_cp = a_wtf8_it.nextCodepoint() orelse break;
const b_cp = b_wtf8_it.nextCodepoint() orelse return false;
if (a_cp <= maxInt(u16) and b_cp <= maxInt(u16)) {
- if (a_cp != b_cp and upcaseImpl(@intCast(a_cp)) != upcaseImpl(@intCast(b_cp))) {
+ if (a_cp != b_cp and toUpperWtf16(@intCast(a_cp)) != toUpperWtf16(@intCast(b_cp))) {
return false;
}
} else if (a_cp != b_cp) {
@@ -4098,15 +3874,6 @@ pub const Win32Error = @import("windows/win32error.zig").Win32Error;
pub const LANG = @import("windows/lang.zig");
pub const SUBLANG = @import("windows/sublang.zig");
-/// The standard input device. Initially, this is the console input buffer, CONIN$.
-pub const STD_INPUT_HANDLE = maxInt(DWORD) - 10 + 1;
-
-/// The standard output device. Initially, this is the active console screen buffer, CONOUT$.
-pub const STD_OUTPUT_HANDLE = maxInt(DWORD) - 11 + 1;
-
-/// The standard error device. Initially, this is the active console screen buffer, CONOUT$.
-pub const STD_ERROR_HANDLE = maxInt(DWORD) - 12 + 1;
-
pub const BOOL = c_int;
pub const BOOLEAN = BYTE;
pub const BYTE = u8;
@@ -5244,6 +5011,8 @@ pub const UNICODE_STRING = extern struct {
Length: c_ushort,
MaximumLength: c_ushort,
Buffer: ?[*]WCHAR,
+
+ pub const empty: UNICODE_STRING = .{ .Length = 0, .MaximumLength = 0, .Buffer = null };
};
pub const ACTIVATION_CONTEXT_DATA = opaque {};
diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig
@@ -12,8 +12,6 @@ const FILETIME = windows.FILETIME;
const HANDLE = windows.HANDLE;
const HANDLER_ROUTINE = windows.HANDLER_ROUTINE;
const HMODULE = windows.HMODULE;
-const INIT_ONCE = windows.INIT_ONCE;
-const INIT_ONCE_FN = windows.INIT_ONCE_FN;
const LARGE_INTEGER = windows.LARGE_INTEGER;
const LPCSTR = windows.LPCSTR;
const LPCVOID = windows.LPCVOID;
@@ -24,7 +22,6 @@ const LPWSTR = windows.LPWSTR;
const MODULEENTRY32 = windows.MODULEENTRY32;
const OVERLAPPED = windows.OVERLAPPED;
const OVERLAPPED_ENTRY = windows.OVERLAPPED_ENTRY;
-const PMEMORY_BASIC_INFORMATION = windows.PMEMORY_BASIC_INFORMATION;
const PROCESS_INFORMATION = windows.PROCESS_INFORMATION;
const SECURITY_ATTRIBUTES = windows.SECURITY_ATTRIBUTES;
const SIZE_T = windows.SIZE_T;
@@ -37,7 +34,6 @@ const ULONG = windows.ULONG;
const ULONG_PTR = windows.ULONG_PTR;
const va_list = windows.va_list;
const WCHAR = windows.WCHAR;
-const WIN32_FIND_DATAW = windows.WIN32_FIND_DATAW;
const Win32Error = windows.Win32Error;
const WORD = windows.WORD;
@@ -59,39 +55,6 @@ pub extern "kernel32" fn CancelIo(
hFile: HANDLE,
) callconv(.winapi) BOOL;
-// TODO: Wrapper around NtCancelIoFileEx.
-pub extern "kernel32" fn CancelIoEx(
- hFile: HANDLE,
- lpOverlapped: ?*OVERLAPPED,
-) callconv(.winapi) BOOL;
-
-pub extern "kernel32" fn CreateFileW(
- lpFileName: LPCWSTR,
- dwDesiredAccess: ACCESS_MASK,
- dwShareMode: DWORD,
- lpSecurityAttributes: ?*SECURITY_ATTRIBUTES,
- dwCreationDisposition: DWORD,
- dwFlagsAndAttributes: DWORD,
- hTemplateFile: ?HANDLE,
-) callconv(.winapi) HANDLE;
-
-// TODO A bunch of logic around NtCreateNamedPipe
-pub extern "kernel32" fn CreateNamedPipeW(
- lpName: LPCWSTR,
- dwOpenMode: DWORD,
- dwPipeMode: DWORD,
- nMaxInstances: DWORD,
- nOutBufferSize: DWORD,
- nInBufferSize: DWORD,
- nDefaultTimeOut: DWORD,
- lpSecurityAttributes: ?*const SECURITY_ATTRIBUTES,
-) callconv(.winapi) HANDLE;
-
-// TODO: Matches `STD_*_HANDLE` to peb().ProcessParameters.Standard*
-pub extern "kernel32" fn GetStdHandle(
- nStdHandle: DWORD,
-) callconv(.winapi) ?HANDLE;
-
// TODO: Wrapper around NtSetInformationFile + `FILE_POSITION_INFORMATION`.
// `FILE_STANDARD_INFORMATION` is also used if dwMoveMethod is `FILE_END`
pub extern "kernel32" fn SetFilePointerEx(
@@ -117,11 +80,6 @@ pub extern "kernel32" fn WriteFile(
in_out_lpOverlapped: ?*OVERLAPPED,
) callconv(.winapi) BOOL;
-// TODO: Wrapper around GetStdHandle + NtFlushBuffersFile.
-pub extern "kernel32" fn FlushFileBuffers(
- hFile: HANDLE,
-) callconv(.winapi) BOOL;
-
// TODO: Wrapper around NtSetInformationFile + `FILE_IO_COMPLETION_NOTIFICATION_INFORMATION`.
pub extern "kernel32" fn SetFileCompletionNotificationModes(
FileHandle: HANDLE,
@@ -143,24 +101,6 @@ pub extern "kernel32" fn GetSystemDirectoryW(
// I/O - Kernel Objects
-// TODO: Wrapper around GetStdHandle + NtDuplicateObject.
-pub extern "kernel32" fn DuplicateHandle(
- hSourceProcessHandle: HANDLE,
- hSourceHandle: HANDLE,
- hTargetProcessHandle: HANDLE,
- lpTargetHandle: *HANDLE,
- dwDesiredAccess: ACCESS_MASK,
- bInheritHandle: BOOL,
- dwOptions: DWORD,
-) callconv(.winapi) BOOL;
-
-// TODO: Wrapper around GetStdHandle + NtQueryObject + NtSetInformationObject with .ObjectHandleFlagInformation.
-pub extern "kernel32" fn SetHandleInformation(
- hObject: HANDLE,
- dwMask: DWORD,
- dwFlags: DWORD,
-) callconv(.winapi) BOOL;
-
// TODO: Wrapper around NtRemoveIoCompletion.
pub extern "kernel32" fn GetQueuedCompletionStatus(
CompletionPort: HANDLE,
@@ -210,37 +150,6 @@ pub extern "kernel32" fn TerminateProcess(
uExitCode: UINT,
) callconv(.winapi) BOOL;
-// TODO: WaitForSingleObjectEx with bAlertable=false.
-pub extern "kernel32" fn WaitForSingleObject(
- hHandle: HANDLE,
- dwMilliseconds: DWORD,
-) callconv(.winapi) DWORD;
-
-// TODO: Wrapper for GetStdHandle + NtWaitForSingleObject.
-// Sets up an activation context before calling NtWaitForSingleObject.
-pub extern "kernel32" fn WaitForSingleObjectEx(
- hHandle: HANDLE,
- dwMilliseconds: DWORD,
- bAlertable: BOOL,
-) callconv(.winapi) DWORD;
-
-// TODO: WaitForMultipleObjectsEx with alertable=false
-pub extern "kernel32" fn WaitForMultipleObjects(
- nCount: DWORD,
- lpHandle: [*]const HANDLE,
- bWaitAll: BOOL,
- dwMilliseconds: DWORD,
-) callconv(.winapi) DWORD;
-
-// TODO: Wrapper around NtWaitForMultipleObjects.
-pub extern "kernel32" fn WaitForMultipleObjectsEx(
- nCount: DWORD,
- lpHandle: [*]const HANDLE,
- bWaitAll: BOOL,
- dwMilliseconds: DWORD,
- bAlertable: BOOL,
-) callconv(.winapi) DWORD;
-
// Process Management
pub extern "kernel32" fn CreateProcessW(
@@ -256,12 +165,6 @@ pub extern "kernel32" fn CreateProcessW(
lpProcessInformation: *PROCESS_INFORMATION,
) callconv(.winapi) BOOL;
-// TODO: implement via ntdll instead
-pub extern "kernel32" fn SleepEx(
- dwMilliseconds: DWORD,
- bAlertable: BOOL,
-) callconv(.winapi) DWORD;
-
// TODO: Wrapper around NtQueryInformationProcess with `PROCESS_BASIC_INFORMATION`.
pub extern "kernel32" fn GetExitCodeProcess(
hProcess: HANDLE,
@@ -436,14 +339,3 @@ pub extern "kernel32" fn FormatMessageW(
// TODO: Getter for teb().LastErrorValue.
pub extern "kernel32" fn GetLastError() callconv(.winapi) Win32Error;
-
-// TODO: Wrapper around RtlSetLastWin32Error.
-pub extern "kernel32" fn SetLastError(
- dwErrCode: Win32Error,
-) callconv(.winapi) void;
-
-// Everything Else
-
-pub extern "kernel32" fn GetSystemInfo(
- lpSystemInfo: *SYSTEM_INFO,
-) callconv(.winapi) void;
diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig
@@ -407,6 +407,11 @@ pub extern "ntdll" fn NtCreateNamedPipeFile(
DefaultTimeout: ?*const LARGE_INTEGER,
) callconv(.winapi) NTSTATUS;
+pub extern "ntdll" fn NtFlushBuffersFile(
+ FileHandle: HANDLE,
+ IoStatusBlock: *IO_STATUS_BLOCK,
+) callconv(.winapi) NTSTATUS;
+
pub extern "ntdll" fn NtMapViewOfSection(
SectionHandle: HANDLE,
ProcessHandle: HANDLE,
@@ -590,7 +595,7 @@ pub extern "ntdll" fn NtOpenThread(
pub extern "ntdll" fn NtCancelSynchronousIoFile(
ThreadHandle: HANDLE,
- RequestToCancel: ?*IO_STATUS_BLOCK,
+ IoRequestToCancel: ?*IO_STATUS_BLOCK,
IoStatusBlock: *IO_STATUS_BLOCK,
) callconv(.winapi) NTSTATUS;
@@ -606,13 +611,13 @@ pub extern "ntdll" fn NtDelayExecution(
DelayInterval: *const LARGE_INTEGER,
) callconv(.winapi) NTSTATUS;
-pub extern "ntdll" fn NtCancelIoFileEx(
+pub extern "ntdll" fn NtCancelIoFile(
FileHandle: HANDLE,
- IoRequestToCancel: *const IO_STATUS_BLOCK,
IoStatusBlock: *IO_STATUS_BLOCK,
) callconv(.winapi) NTSTATUS;
-pub extern "ntdll" fn NtCancelIoFile(
+pub extern "ntdll" fn NtCancelIoFileEx(
FileHandle: HANDLE,
+ IoRequestToCancel: *const IO_STATUS_BLOCK,
IoStatusBlock: *IO_STATUS_BLOCK,
) callconv(.winapi) NTSTATUS;
diff --git a/lib/std/process/Environ.zig b/lib/std/process/Environ.zig
@@ -4,7 +4,7 @@ const builtin = @import("builtin");
const native_os = builtin.os.tag;
const std = @import("../std.zig");
-const Allocator = std.mem.Allocator;
+const Allocator = mem.Allocator;
const assert = std.debug.assert;
const testing = std.testing;
const unicode = std.unicode;
@@ -14,12 +14,7 @@ const mem = std.mem;
/// Unmodified, unprocessed data provided by the operating system.
block: Block,
-pub const empty: Environ = .{
- .block = switch (Block) {
- void => {},
- else => &.{},
- },
-};
+pub const empty: Environ = .{ .block = .empty };
/// On WASI without libc, this is `void` because the environment has to be
/// queried and heap-allocated at runtime.
@@ -28,13 +23,65 @@ pub const empty: Environ = .{
/// is modified, so a long-lived pointer cannot be used. Therefore, on this
/// operating system `void` is also used.
pub const Block = switch (native_os) {
- .windows => void,
+ .windows => GlobalBlock,
.wasi => switch (builtin.link_libc) {
- false => void,
- true => [:null]const ?[*:0]const u8,
+ false => GlobalBlock,
+ true => PosixBlock,
},
- .freestanding, .other => void,
- else => [:null]const ?[*:0]const u8,
+ .freestanding, .other => GlobalBlock,
+ else => PosixBlock,
+};
+
+pub const GlobalBlock = struct {
+ use_global: bool,
+
+ pub const empty: GlobalBlock = .{ .use_global = false };
+ pub const global: GlobalBlock = .{ .use_global = true };
+
+ pub fn deinit(_: GlobalBlock, _: Allocator) void {}
+};
+
+pub const PosixBlock = struct {
+ slice: [:null]const ?[*:0]const u8,
+
+ pub const empty: PosixBlock = .{ .slice = &.{} };
+
+ pub fn deinit(block: PosixBlock, gpa: Allocator) void {
+ for (block.slice) |entry| gpa.free(mem.span(entry.?));
+ gpa.free(block.slice);
+ }
+
+ pub const View = struct {
+ slice: []const [*:0]const u8,
+
+ pub fn isEmpty(v: View) bool {
+ return v.slice.len == 0;
+ }
+ };
+ pub fn view(block: PosixBlock) View {
+ return .{ .slice = @ptrCast(block.slice) };
+ }
+};
+
+pub const WindowsBlock = struct {
+ slice: [:0]const u16,
+
+ pub const empty: WindowsBlock = .{ .slice = &.{0} };
+
+ pub fn deinit(block: WindowsBlock, gpa: Allocator) void {
+ gpa.free(block.slice);
+ }
+
+ pub const View = struct {
+ ptr: [*:0]const u16,
+
+ pub fn isEmpty(v: View) bool {
+ return v.ptr[0] == 0;
+ }
+ };
+ pub fn view(block: WindowsBlock) View {
+ return .{ .ptr = block.slice.ptr };
+ }
};
pub const Map = struct {
@@ -46,47 +93,60 @@ pub const Map = struct {
pub const Size = usize;
pub const EnvNameHashContext = struct {
- fn upcase(c: u21) u21 {
- if (c <= std.math.maxInt(u16))
- return std.os.windows.ntdll.RtlUpcaseUnicodeChar(@as(u16, @intCast(c)));
- return c;
- }
-
pub fn hash(self: @This(), s: []const u8) u32 {
_ = self;
- if (native_os == .windows) {
- var h = std.hash.Wyhash.init(0);
- var it = unicode.Wtf8View.initUnchecked(s).iterator();
- while (it.nextCodepoint()) |cp| {
- const cp_upper = upcase(cp);
- h.update(&[_]u8{
- @as(u8, @intCast((cp_upper >> 16) & 0xff)),
- @as(u8, @intCast((cp_upper >> 8) & 0xff)),
- @as(u8, @intCast((cp_upper >> 0) & 0xff)),
- });
- }
- return @truncate(h.final());
+ switch (native_os) {
+ else => return std.array_hash_map.hashString(s),
+ .windows => {
+ var h = std.hash.Wyhash.init(0);
+ var it = unicode.Wtf8View.initUnchecked(s).iterator();
+ while (it.nextCodepoint()) |cp| {
+ const cp_upper = if (std.math.cast(u16, cp)) |wtf16|
+ std.os.windows.toUpperWtf16(wtf16)
+ else
+ cp;
+ h.update(&[_]u8{
+ @truncate(cp_upper >> 0),
+ @truncate(cp_upper >> 8),
+ @truncate(cp_upper >> 16),
+ });
+ }
+ return @truncate(h.final());
+ },
}
- return std.array_hash_map.hashString(s);
}
pub fn eql(self: @This(), a: []const u8, b: []const u8, b_index: usize) bool {
_ = self;
_ = b_index;
- if (native_os == .windows) {
- var it_a = unicode.Wtf8View.initUnchecked(a).iterator();
- var it_b = unicode.Wtf8View.initUnchecked(b).iterator();
- while (true) {
- const c_a = it_a.nextCodepoint() orelse break;
- const c_b = it_b.nextCodepoint() orelse return false;
- if (upcase(c_a) != upcase(c_b))
- return false;
- }
- return if (it_b.nextCodepoint()) |_| false else true;
- }
- return std.array_hash_map.eqlString(a, b);
+ return eqlKeys(a, b);
}
};
+ fn eqlKeys(a: []const u8, b: []const u8) bool {
+ return switch (native_os) {
+ else => std.array_hash_map.eqlString(a, b),
+ .windows => std.os.windows.eqlIgnoreCaseWtf8(a, b),
+ };
+ }
+
+ pub fn validateKey(key: []const u8) bool {
+ switch (native_os) {
+ else => return key.len > 0 and mem.findAny(u8, key, &.{ 0, '=' }) == null,
+ .windows => {
+ if (!unicode.wtf8ValidateSlice(key)) return false;
+ var it = unicode.Wtf8View.initUnchecked(key).iterator();
+ switch (it.nextCodepoint() orelse return false) {
+ 0 => return false,
+ else => {},
+ }
+ while (it.nextCodepoint()) |cp| switch (cp) {
+ 0, '=' => return false,
+ else => {},
+ };
+ return true;
+ },
+ }
+ }
/// Create a Map backed by a specific allocator.
/// That allocator will be used for both backing allocations
@@ -99,30 +159,71 @@ pub const Map = struct {
/// of the stored keys and values.
pub fn deinit(self: *Map) void {
const gpa = self.allocator;
- var it = self.array_hash_map.iterator();
- while (it.next()) |entry| {
- gpa.free(entry.key_ptr.*);
- gpa.free(entry.value_ptr.*);
- }
+ for (self.keys()) |key| gpa.free(key);
+ for (self.values()) |value| gpa.free(value);
self.array_hash_map.deinit(gpa);
self.* = undefined;
}
- pub fn keys(m: *const Map) [][]const u8 {
- return m.array_hash_map.keys();
+ pub fn keys(map: *const Map) [][]const u8 {
+ return map.array_hash_map.keys();
+ }
+
+ pub fn values(map: *const Map) [][]const u8 {
+ return map.array_hash_map.values();
+ }
+
+ pub fn putPosixBlock(map: *Map, view: PosixBlock.View) Allocator.Error!void {
+ for (view.slice) |entry| {
+ var entry_i: usize = 0;
+ while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {}
+ const key = entry[0..entry_i];
+
+ var end_i: usize = entry_i;
+ while (entry[end_i] != 0) : (end_i += 1) {}
+ const value = entry[entry_i + 1 .. end_i];
+
+ try map.put(key, value);
+ }
}
- pub fn values(m: *const Map) [][]const u8 {
- return m.array_hash_map.values();
+ pub fn putWindowsBlock(map: *Map, view: WindowsBlock.View) Allocator.Error!void {
+ var i: usize = 0;
+ while (view.ptr[i] != 0) {
+ const key_start = i;
+
+ // There are some special environment variables that start with =,
+ // so we need a special case to not treat = as a key/value separator
+ // if it's the first character.
+ // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
+ if (view.ptr[key_start] == '=') i += 1;
+
+ while (view.ptr[i] != 0 and view.ptr[i] != '=') : (i += 1) {}
+ const key_w = view.ptr[key_start..i];
+ const key = try unicode.wtf16LeToWtf8Alloc(map.allocator, key_w);
+ errdefer map.allocator.free(key);
+
+ if (view.ptr[i] == '=') i += 1;
+
+ const value_start = i;
+ while (view.ptr[i] != 0) : (i += 1) {}
+ const value_w = view.ptr[value_start..i];
+ const value = try unicode.wtf16LeToWtf8Alloc(map.allocator, value_w);
+ errdefer map.allocator.free(value);
+
+ i += 1; // skip over null byte
+
+ try map.putMove(key, value);
+ }
}
/// Same as `put` but the key and value become owned by the Map rather
/// than being copied.
/// If `putMove` fails, the ownership of key and value does not transfer.
/// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
- pub fn putMove(self: *Map, key: []u8, value: []u8) !void {
+ pub fn putMove(self: *Map, key: []u8, value: []u8) Allocator.Error!void {
+ assert(validateKey(key));
const gpa = self.allocator;
- assert(unicode.wtf8ValidateSlice(key));
const get_or_put = try self.array_hash_map.getOrPut(gpa, key);
if (get_or_put.found_existing) {
gpa.free(get_or_put.key_ptr.*);
@@ -134,8 +235,8 @@ pub const Map = struct {
/// `key` and `value` are copied into the Map.
/// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
- pub fn put(self: *Map, key: []const u8, value: []const u8) !void {
- assert(unicode.wtf8ValidateSlice(key));
+ pub fn put(self: *Map, key: []const u8, value: []const u8) Allocator.Error!void {
+ assert(validateKey(key));
const gpa = self.allocator;
const value_copy = try gpa.dupe(u8, value);
errdefer gpa.free(value_copy);
@@ -155,7 +256,7 @@ pub const Map = struct {
/// The returned pointer is invalidated if the map resizes.
/// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
pub fn getPtr(self: Map, key: []const u8) ?*[]const u8 {
- assert(unicode.wtf8ValidateSlice(key));
+ assert(validateKey(key));
return self.array_hash_map.getPtr(key);
}
@@ -164,11 +265,12 @@ pub const Map = struct {
/// key is removed from the map.
/// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
pub fn get(self: Map, key: []const u8) ?[]const u8 {
- assert(unicode.wtf8ValidateSlice(key));
+ assert(validateKey(key));
return self.array_hash_map.get(key);
}
pub fn contains(m: *const Map, key: []const u8) bool {
+ assert(validateKey(key));
return m.array_hash_map.contains(key);
}
@@ -181,7 +283,7 @@ pub const Map = struct {
/// This invalidates the value returned by get() for this key.
/// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
pub fn swapRemove(self: *Map, key: []const u8) bool {
- assert(unicode.wtf8ValidateSlice(key));
+ assert(validateKey(key));
const kv = self.array_hash_map.fetchSwapRemove(key) orelse return false;
const gpa = self.allocator;
gpa.free(kv.key);
@@ -198,7 +300,7 @@ pub const Map = struct {
/// This invalidates the value returned by get() for this key.
/// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
pub fn orderedRemove(self: *Map, key: []const u8) bool {
- assert(unicode.wtf8ValidateSlice(key));
+ assert(validateKey(key));
const kv = self.array_hash_map.fetchOrderedRemove(key) orelse return false;
const gpa = self.allocator;
gpa.free(kv.key);
@@ -233,105 +335,120 @@ pub const Map = struct {
/// Creates a null-delimited environment variable block in the format
/// expected by POSIX, from a hash map plus options.
- pub fn createBlockPosix(
+ pub fn createPosixBlock(
map: *const Map,
- arena: Allocator,
- options: CreateBlockPosixOptions,
- ) Allocator.Error![:null]?[*:0]u8 {
+ gpa: Allocator,
+ options: CreatePosixBlockOptions,
+ ) Allocator.Error!PosixBlock {
const ZigProgressAction = enum { nothing, edit, delete, add };
- const zig_progress_action: ZigProgressAction = a: {
- const fd = options.zig_progress_fd orelse break :a .nothing;
- const exists = map.get("ZIG_PROGRESS") != null;
+ const zig_progress_action: ZigProgressAction = action: {
+ const fd = options.zig_progress_fd orelse break :action .nothing;
+ const exists = map.contains("ZIG_PROGRESS");
if (fd >= 0) {
- break :a if (exists) .edit else .add;
+ break :action if (exists) .edit else .add;
} else {
- if (exists) break :a .delete;
+ if (exists) break :action .delete;
}
- break :a .nothing;
+ break :action .nothing;
};
- const envp_count: usize = c: {
- var c: usize = map.count();
+ const envp = try gpa.allocSentinel(?[*:0]u8, len: {
+ var len: usize = map.count();
switch (zig_progress_action) {
- .add => c += 1,
- .delete => c -= 1,
+ .add => len += 1,
+ .delete => len -= 1,
.nothing, .edit => {},
}
- break :c c;
- };
-
- const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
- var i: usize = 0;
+ break :len len;
+ }, null);
+ var envp_len: usize = 0;
+ errdefer {
+ envp[envp_len] = null;
+ PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa);
+ }
if (zig_progress_action == .add) {
- envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
- i += 1;
+ envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
+ envp_len += 1;
}
- {
- var it = map.iterator();
- while (it.next()) |pair| {
- if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) {
- .add => unreachable,
- .delete => continue,
- .edit => {
- envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{
- pair.key_ptr.*, options.zig_progress_fd.?,
- }, 0);
- i += 1;
- continue;
- },
- .nothing => {},
- };
-
- envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0);
- i += 1;
- }
+ for (map.keys(), map.values()) |key, value| {
+ if (mem.eql(u8, key, "ZIG_PROGRESS")) switch (zig_progress_action) {
+ .add => unreachable,
+ .delete => continue,
+ .edit => {
+ envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={d}", .{
+ key, options.zig_progress_fd.?,
+ }, 0);
+ envp_len += 1;
+ continue;
+ },
+ .nothing => {},
+ };
+
+ envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={s}", .{ key, value }, 0);
+ envp_len += 1;
}
- assert(i == envp_count);
- return envp_buf;
+ assert(envp_len == envp.len);
+ return .{ .slice = envp };
}
/// Caller owns result.
- pub fn createBlockWindows(map: *const Map, gpa: Allocator) error{ OutOfMemory, InvalidWtf8 }![:0]u16 {
+ pub fn createWindowsBlock(
+ map: *const Map,
+ gpa: Allocator,
+ options: CreateWindowsBlockOptions,
+ ) error{ OutOfMemory, InvalidWtf8 }!WindowsBlock {
// count bytes needed
- const max_chars_needed = x: {
- // Only need 2 trailing NUL code units for an empty environment
- var max_chars_needed: usize = if (map.count() == 0) 2 else 1;
- var it = map.iterator();
- while (it.next()) |pair| {
- // +1 for '='
- // +1 for null byte
- max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2;
+ const max_chars_needed = max_chars_needed: {
+ var max_chars_needed: usize = "\x00".len;
+ if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
+ max_chars_needed += std.fmt.count("ZIG_PROGRESS={d}\x00", .{@intFromPtr(handle)});
+ };
+ for (map.keys(), map.values()) |key, value| {
+ if (options.zig_progress_handle != null and eqlKeys(key, "ZIG_PROGRESS")) continue;
+ max_chars_needed += key.len + "=".len + value.len + "\x00".len;
}
- break :x max_chars_needed;
+ break :max_chars_needed @max("\x00\x00".len, max_chars_needed);
};
- const result = try gpa.alloc(u16, max_chars_needed);
- errdefer gpa.free(result);
+ const block = try gpa.alloc(u16, max_chars_needed);
+ errdefer gpa.free(block);
- var it = map.iterator();
var i: usize = 0;
- while (it.next()) |pair| {
- i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
- result[i] = '=';
+ if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
+ @memcpy(
+ block[i..][0.."ZIG_PROGRESS=".len],
+ &[_]u16{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S', '=' },
+ );
+ i += "ZIG_PROGRESS=".len;
+ var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined;
+ const value = std.fmt.bufPrint(&value_buf, "{d}", .{@intFromPtr(handle)}) catch unreachable;
+ for (block[i..][0..value.len], value) |*r, v| r.* = v;
+ i += value.len;
+ block[i] = 0;
i += 1;
- i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
- result[i] = 0;
+ };
+ for (map.keys(), map.values()) |key, value| {
+ if (options.zig_progress_handle != null and eqlKeys(key, "ZIG_PROGRESS")) continue;
+ i += try unicode.wtf8ToWtf16Le(block[i..], key);
+ block[i] = '=';
+ i += 1;
+ i += try unicode.wtf8ToWtf16Le(block[i..], value);
+ block[i] = 0;
i += 1;
}
- result[i] = 0;
- i += 1;
// An empty environment is a special case that requires a redundant
// NUL terminator. CreateProcess will read the second code unit even
// though theoretically the first should be enough to recognize that the
// environment is empty (see https://nullprogram.com/blog/2023/08/23/)
- if (map.count() == 0) {
- result[i] = 0;
+ for (0..2) |_| {
+ block[i] = 0;
i += 1;
- }
- const reallocated = try gpa.realloc(result, i);
- return reallocated[0 .. i - 1 :0];
+ if (i >= 2) break;
+ } else unreachable;
+ const reallocated = try gpa.realloc(block, i);
+ return .{ .slice = reallocated[0 .. i - 1 :0] };
}
};
@@ -344,13 +461,18 @@ pub const CreateMapError = error{
/// Allocates a `Map` and copies environment block into it.
pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
- if (native_os == .windows)
- return createMapWide(std.os.windows.peb().ProcessParameters.Environment, allocator);
-
- var result = Map.init(allocator);
- errdefer result.deinit();
+ var map = Map.init(allocator);
+ errdefer map.deinit();
+ if (native_os == .windows) empty: {
+ if (!env.block.use_global) break :empty;
+
+ const peb = std.os.windows.peb();
+ assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
+ defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
+ try map.putWindowsBlock(.{ .ptr = peb.ProcessParameters.Environment });
+ } else if (native_os == .wasi and !builtin.link_libc) empty: {
+ if (!env.block.use_global) break :empty;
- if (native_os == .wasi and !builtin.link_libc) {
var environ_count: usize = undefined;
var environ_buf_size: usize = undefined;
@@ -360,7 +482,7 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
}
if (environ_count == 0) {
- return result;
+ return map;
}
const environ = try allocator.alloc([*:0]u8, environ_count);
@@ -373,63 +495,9 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
return posix.unexpectedErrno(environ_get_ret);
}
- for (environ) |line| {
- const pair = mem.sliceTo(line, 0);
- var parts = mem.splitScalar(u8, pair, '=');
- const key = parts.first();
- const value = parts.rest();
- try result.put(key, value);
- }
- return result;
- } else {
- for (env.block) |opt_line| {
- const line = opt_line.?;
- var line_i: usize = 0;
- while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
- const key = line[0..line_i];
-
- var end_i: usize = line_i;
- while (line[end_i] != 0) : (end_i += 1) {}
- const value = line[line_i + 1 .. end_i];
-
- try result.put(key, value);
- }
- return result;
- }
-}
-
-pub fn createMapWide(ptr: [*:0]u16, gpa: Allocator) CreateMapError!Map {
- var result = Map.init(gpa);
- errdefer result.deinit();
-
- var i: usize = 0;
- while (ptr[i] != 0) {
- const key_start = i;
-
- // There are some special environment variables that start with =,
- // so we need a special case to not treat = as a key/value separator
- // if it's the first character.
- // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
- if (ptr[key_start] == '=') i += 1;
-
- while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
- const key_w = ptr[key_start..i];
- const key = try unicode.wtf16LeToWtf8Alloc(gpa, key_w);
- errdefer gpa.free(key);
-
- if (ptr[i] == '=') i += 1;
-
- const value_start = i;
- while (ptr[i] != 0) : (i += 1) {}
- const value_w = ptr[value_start..i];
- const value = try unicode.wtf16LeToWtf8Alloc(gpa, value_w);
- errdefer gpa.free(value);
-
- i += 1; // skip over null byte
-
- try result.putMove(key, value);
- }
- return result;
+ try map.putPosixBlock(.{ .slice = environ });
+ } else try map.putPosixBlock(env.block.view());
+ return map;
}
pub const ContainsError = error{
@@ -451,6 +519,7 @@ pub const ContainsError = error{
/// * `containsConstant`
/// * `containsUnempty`
pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool {
+ if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8;
var map = try createMap(environ, gpa);
defer map.deinit();
return map.contains(key);
@@ -464,6 +533,7 @@ pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError
/// * `containsUnemptyConstant`
/// * `contains`
pub fn containsUnempty(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool {
+ if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8;
var map = try createMap(environ, gpa);
defer map.deinit();
const value = map.get(key) orelse return false;
@@ -516,16 +586,15 @@ pub inline fn containsUnemptyConstant(environ: Environ, comptime key: []const u8
/// * `createMap`
pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 {
if (mem.findScalar(u8, key, '=') != null) return null;
- for (environ.block) |opt_line| {
- const line = opt_line.?;
- var line_i: usize = 0;
- while (line[line_i] != 0) : (line_i += 1) {
- if (line_i == key.len) break;
- if (line[line_i] != key[line_i]) break;
+ for (environ.block.view().slice) |entry| {
+ var entry_i: usize = 0;
+ while (entry[entry_i] != 0) : (entry_i += 1) {
+ if (entry_i == key.len) break;
+ if (entry[entry_i] != key[entry_i]) break;
}
- if ((line_i != key.len) or (line[line_i] != '=')) continue;
+ if ((entry_i != key.len) or (entry[entry_i] != '=')) continue;
- return mem.sliceTo(line + line_i + 1, 0);
+ return mem.sliceTo(entry + entry_i + 1, 0);
}
return null;
}
@@ -541,14 +610,16 @@ pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 {
/// * `containsConstant`
/// * `contains`
pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 {
- comptime assert(native_os == .windows);
- comptime assert(@TypeOf(environ.block) == void);
-
// '=' anywhere but the start makes this an invalid environment variable name.
const key_slice = mem.sliceTo(key, 0);
- if (key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') != null) return null;
+ assert(key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') == null);
- const ptr = std.os.windows.peb().ProcessParameters.Environment;
+ if (!environ.block.use_global) return null;
+
+ const peb = std.os.windows.peb();
+ assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
+ defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
+ const ptr = peb.ProcessParameters.Environment;
var i: usize = 0;
while (ptr[i] != 0) {
@@ -558,8 +629,7 @@ pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 {
// so we need a special case to not treat = as a key/value separator
// if it's the first character.
// https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
- const equal_search_start: usize = if (key_value[0] == '=') 1 else 0;
- const equal_index = mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse {
+ const equal_index = mem.findScalarPos(u16, key_value, 1, '=') orelse {
// This is enforced by CreateProcess.
// If violated, CreateProcess will fail with INVALID_PARAMETER.
unreachable; // must contain a =
@@ -598,13 +668,14 @@ pub const GetAllocError = error{
/// See also:
/// * `createMap`
pub fn getAlloc(environ: Environ, gpa: Allocator, key: []const u8) GetAllocError![]u8 {
+ if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8;
var map = createMap(environ, gpa) catch return error.OutOfMemory;
defer map.deinit();
const val = map.get(key) orelse return error.EnvironmentVariableMissing;
return gpa.dupe(u8, val);
}
-pub const CreateBlockPosixOptions = struct {
+pub const CreatePosixBlockOptions = struct {
/// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
/// If non-null, negative means to remove the environment variable, and >= 0
/// means to provide it with the given integer.
@@ -613,67 +684,147 @@ pub const CreateBlockPosixOptions = struct {
/// Creates a null-delimited environment variable block in the format expected
/// by POSIX, from a different one.
-pub fn createBlockPosix(
+pub fn createPosixBlock(
existing: Environ,
- arena: Allocator,
- options: CreateBlockPosixOptions,
-) Allocator.Error![:null]?[*:0]u8 {
- const contains_zig_progress = for (existing.block) |opt_line| {
- if (mem.eql(u8, mem.sliceTo(opt_line.?, '='), "ZIG_PROGRESS")) break true;
+ gpa: Allocator,
+ options: CreatePosixBlockOptions,
+) Allocator.Error!PosixBlock {
+ const contains_zig_progress = for (existing.block.view().slice) |entry| {
+ if (mem.eql(u8, mem.sliceTo(entry, '='), "ZIG_PROGRESS")) break true;
} else false;
const ZigProgressAction = enum { nothing, edit, delete, add };
- const zig_progress_action: ZigProgressAction = a: {
- const fd = options.zig_progress_fd orelse break :a .nothing;
+ const zig_progress_action: ZigProgressAction = action: {
+ const fd = options.zig_progress_fd orelse break :action .nothing;
if (fd >= 0) {
- break :a if (contains_zig_progress) .edit else .add;
+ break :action if (contains_zig_progress) .edit else .add;
} else {
- if (contains_zig_progress) break :a .delete;
+ if (contains_zig_progress) break :action .delete;
}
- break :a .nothing;
+ break :action .nothing;
};
- const envp_count: usize = c: {
- var count: usize = existing.block.len;
+ const envp = try gpa.allocSentinel(?[*:0]u8, len: {
+ var len: usize = existing.block.slice.len;
switch (zig_progress_action) {
- .add => count += 1,
- .delete => count -= 1,
+ .add => len += 1,
+ .delete => len -= 1,
.nothing, .edit => {},
}
- break :c count;
- };
-
- const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
- var i: usize = 0;
- var existing_index: usize = 0;
-
+ break :len len;
+ }, null);
+ var envp_len: usize = 0;
+ errdefer {
+ envp[envp_len] = null;
+ PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa);
+ }
if (zig_progress_action == .add) {
- envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
- i += 1;
+ envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
+ envp_len += 1;
}
- while (existing.block[existing_index]) |line| : (existing_index += 1) {
- if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
+ var existing_index: usize = 0;
+ while (existing.block.slice[existing_index]) |entry| : (existing_index += 1) {
+ if (mem.eql(u8, mem.sliceTo(entry, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
.add => unreachable,
.delete => continue,
.edit => {
- envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
- i += 1;
+ envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
+ envp_len += 1;
continue;
},
.nothing => {},
};
- envp_buf[i] = try arena.dupeZ(u8, mem.span(line));
- i += 1;
+ envp[envp_len] = try gpa.dupeZ(u8, mem.span(entry));
+ envp_len += 1;
}
- assert(i == envp_count);
- return envp_buf;
+ assert(envp_len == envp.len);
+ return .{ .slice = envp };
}
-test "Map.createBlock" {
- const allocator = testing.allocator;
- var envmap = Map.init(allocator);
+pub const CreateWindowsBlockOptions = struct {
+ /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
+ /// If non-null, `std.os.windows.INVALID_HANDLE_VALUE` means to remove the
+ /// environment variable, otherwise provide it with the given handle as an integer.
+ zig_progress_handle: ?std.os.windows.HANDLE = null,
+};
+
+/// Creates a null-delimited environment variable block in the format expected
+/// by POSIX, from a different one.
+pub fn createWindowsBlock(
+ existing: Environ,
+ gpa: Allocator,
+ options: CreateWindowsBlockOptions,
+) Allocator.Error!WindowsBlock {
+ if (!existing.block.use_global) return .{
+ .slice = try gpa.dupeSentinel(u16, WindowsBlock.empty.slice, 0),
+ };
+ const peb = std.os.windows.peb();
+ assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
+ defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
+ const existing_block = peb.ProcessParameters.Environment;
+ var ranges: [2]struct { start: usize, end: usize } = undefined;
+ var ranges_len: usize = 0;
+ ranges[ranges_len].start = 0;
+ const zig_progress_key = [_]u16{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S', '=' };
+ const needed_len = needed_len: {
+ var needed_len: usize = "\x00".len;
+ if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
+ needed_len += std.fmt.count("ZIG_PROGRESS={d}\x00", .{@intFromPtr(handle)});
+ };
+ var i: usize = 0;
+ while (existing_block[i] != 0) {
+ const start = i;
+ const entry = mem.sliceTo(existing_block[start..], 0);
+ i += entry.len + "\x00".len;
+ if (options.zig_progress_handle != null and entry.len >= zig_progress_key.len and
+ std.os.windows.eqlIgnoreCaseWtf16(entry[0..zig_progress_key.len], &zig_progress_key))
+ {
+ ranges[ranges_len].end = start;
+ ranges_len += 1;
+ ranges[ranges_len].start = i;
+ } else needed_len += entry.len + "\x00".len;
+ }
+ ranges[ranges_len].end = i;
+ ranges_len += 1;
+ break :needed_len @max("\x00\x00".len, needed_len);
+ };
+ const block = try gpa.alloc(u16, needed_len);
+ errdefer gpa.free(block);
+ var i: usize = 0;
+ if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
+ @memcpy(block[i..][0..zig_progress_key.len], &zig_progress_key);
+ i += zig_progress_key.len;
+ var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined;
+ const value = std.fmt.bufPrint(&value_buf, "{d}", .{@intFromPtr(handle)}) catch unreachable;
+ for (block[i..][0..value.len], value) |*r, v| r.* = v;
+ i += value.len;
+ block[i] = 0;
+ i += 1;
+ };
+ for (ranges[0..ranges_len]) |range| {
+ const range_len = range.end - range.start;
+ @memcpy(block[i..][0..range_len], existing_block[range.start..range.end]);
+ i += range_len;
+ }
+ // An empty environment is a special case that requires a redundant
+ // NUL terminator. CreateProcess will read the second code unit even
+ // though theoretically the first should be enough to recognize that the
+ // environment is empty (see https://nullprogram.com/blog/2023/08/23/)
+ for (0..2) |_| {
+ block[i] = 0;
+ i += 1;
+ if (i >= 2) break;
+ } else unreachable;
+ assert(i == block.len);
+ return .{ .slice = block[0 .. i - 1 :0] };
+}
+
+test "Map.createPosixBlock" {
+ const gpa = testing.allocator;
+
+ var envmap = Map.init(gpa);
defer envmap.deinit();
try envmap.put("HOME", "/home/ifreund");
@@ -682,29 +833,24 @@ test "Map.createBlock" {
try envmap.put("DEBUGINFOD_URLS", " ");
try envmap.put("XCURSOR_SIZE", "24");
- var arena = std.heap.ArenaAllocator.init(allocator);
- defer arena.deinit();
- const environ = try envmap.createBlockPosix(arena.allocator(), .{});
+ const block = try envmap.createPosixBlock(gpa, .{});
+ defer block.deinit(gpa);
- try testing.expectEqual(@as(usize, 5), environ.len);
+ try testing.expectEqual(@as(usize, 5), block.slice.len);
- inline for (.{
+ for (&[_][]const u8{
"HOME=/home/ifreund",
"WAYLAND_DISPLAY=wayland-1",
"DISPLAY=:1",
"DEBUGINFOD_URLS= ",
"XCURSOR_SIZE=24",
- }) |target| {
- for (environ) |variable| {
- if (mem.eql(u8, mem.span(variable orelse continue), target)) break;
- } else {
- try testing.expect(false); // Environment variable not found
- }
- }
+ }, block.slice) |expected, actual| try testing.expectEqualStrings(expected, mem.span(actual.?));
}
test Map {
- var env = Map.init(testing.allocator);
+ const gpa = testing.allocator;
+
+ var env: Map = .init(gpa);
defer env.deinit();
try env.put("SOMETHING_NEW", "hello");
@@ -740,6 +886,7 @@ test Map {
try testing.expect(env.swapRemove("SOMETHING_NEW"));
try testing.expect(!env.swapRemove("SOMETHING_NEW"));
try testing.expect(env.get("SOMETHING_NEW") == null);
+ try testing.expect(!env.contains("SOMETHING_NEW"));
try testing.expectEqual(@as(Map.Size, 1), env.count());
@@ -749,10 +896,10 @@ test Map {
try testing.expectEqualStrings("something else", env.get("кириллица").?);
// and WTF-8 that's not valid UTF-8
- const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{
+ const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(gpa, &[_]u16{
mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate
});
- defer testing.allocator.free(wtf8_with_surrogate_pair);
+ defer gpa.free(wtf8_with_surrogate_pair);
try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair);
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" {
defer map.deinit();
try map.put("FOO", "BAR");
try map.put("A", "");
- try map.put("", "B");
-
- var arena_allocator = std.heap.ArenaAllocator.init(gpa);
- defer arena_allocator.deinit();
- const arena = arena_allocator.allocator();
- const environ: Environ = .{ .block = try map.createBlockPosix(arena, .{}) };
+ const environ: Environ = .{ .block = try map.createPosixBlock(gpa, .{}) };
+ defer environ.block.deinit(gpa);
try testing.expectEqual(true, environ.contains(gpa, "FOO"));
try testing.expectEqual(false, environ.contains(gpa, "BAR"));
@@ -783,7 +926,6 @@ test "convert from Environ to Map and back again" {
try testing.expectEqual(true, environ.containsConstant("A"));
try testing.expectEqual(false, environ.containsUnempty(gpa, "A"));
try testing.expectEqual(false, environ.containsUnemptyConstant("A"));
- try testing.expectEqual(true, environ.contains(gpa, ""));
try testing.expectEqual(false, environ.contains(gpa, "B"));
try testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(gpa, "BOGUS"));
@@ -800,23 +942,47 @@ test "convert from Environ to Map and back again" {
try testing.expectEqualDeep(map.values(), map2.values());
}
-test createMapWide {
- if (builtin.cpu.arch.endian() == .big) return error.SkipZigTest; // TODO
+test "Map.putPosixBlock" {
+ const gpa = testing.allocator;
+
+ var map: Map = .init(gpa);
+ defer map.deinit();
+
+ try map.put("FOO", "BAR");
+ try map.put("A", "");
+ try map.put("ZIG_PROGRESS", "unchanged");
+
+ const block = try map.createPosixBlock(gpa, .{});
+ defer block.deinit(gpa);
+
+ var map2: Map = .init(gpa);
+ defer map2.deinit();
+ try map2.putPosixBlock(block.view());
+
+ try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "ZIG_PROGRESS" }, map2.keys());
+ try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "unchanged" }, map2.values());
+}
+
+test "Map.putWindowsBlock" {
+ if (native_os != .windows) return;
const gpa = testing.allocator;
var map: Map = .init(gpa);
defer map.deinit();
+
try map.put("FOO", "BAR");
try map.put("A", "");
- try map.put("", "B");
+ try map.put("=B", "");
+ try map.put("ZIG_PROGRESS", "unchanged");
- const environ: [:0]u16 = try map.createBlockWindows(gpa);
- defer gpa.free(environ);
+ const block = try map.createWindowsBlock(gpa, .{});
+ defer block.deinit(gpa);
- var map2 = try createMapWide(environ, gpa);
+ var map2: Map = .init(gpa);
defer map2.deinit();
+ try map2.putWindowsBlock(block.view());
- try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B" }, map2.keys());
- try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "" }, map2.values());
+ try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B", "ZIG_PROGRESS" }, map2.keys());
+ try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "", "unchanged" }, map2.values());
}
diff --git a/lib/std/start.zig b/lib/std/start.zig
@@ -90,15 +90,15 @@ fn _DllMainCRTStartup(
fn wasm_freestanding_start() callconv(.c) void {
// This is marked inline because for some reason LLVM in
// release mode fails to inline it, and we want fewer call frames in stack traces.
- _ = @call(.always_inline, callMain, .{ {}, {} });
+ _ = @call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global });
}
fn startWasi() callconv(.c) void {
// The function call is marked inline because for some reason LLVM in
// release mode fails to inline it, and we want fewer call frames in stack traces.
switch (builtin.wasi_exec_model) {
- .reactor => _ = @call(.always_inline, callMain, .{ {}, {} }),
- .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{ {}, {} })),
+ .reactor => _ = @call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global }),
+ .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global })),
}
}
@@ -476,7 +476,7 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn {
const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)];
- std.os.windows.ntdll.RtlExitUserProcess(callMain(cmd_line_w, {}));
+ std.os.windows.ntdll.RtlExitUserProcess(callMain(cmd_line_w, .global));
}
fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn {
@@ -620,13 +620,14 @@ fn expandStackSize(phdrs: []elf.Phdr) void {
}
inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) u8 {
+ const env_block: std.process.Environ.Block = .{ .slice = envp };
if (std.Options.debug_threaded_io) |t| {
if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0];
- t.environ = .{ .process_environ = .{ .block = envp } };
+ t.environ = .{ .process_environ = .{ .block = env_block } };
}
std.Thread.maybeAttachSignalStack();
std.debug.maybeEnableSegfaultHandler();
- return callMain(argv[0..argc], envp);
+ return callMain(argv[0..argc], env_block);
}
fn 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
std.debug.maybeEnableSegfaultHandler();
const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)];
- return callMain(cmd_line_w, {});
+ return callMain(cmd_line_w, .global);
},
else => {},
}
@@ -661,7 +662,7 @@ fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int {
if (@sizeOf(std.Io.Threaded.Argv0) != 0) {
if (std.Options.debug_threaded_io) |t| t.argv0.value = argv[0];
}
- return callMain(argv, &.{});
+ return callMain(argv, .empty);
}
/// General error message for a malformed return type
diff --git a/test/standalone/env_vars/main.zig b/test/standalone/env_vars/main.zig
@@ -12,14 +12,10 @@ pub fn main(init: std.process.Init) !void {
// containsUnempty
{
try std.testing.expect(try environ.containsUnempty(allocator, "FOO"));
- try std.testing.expect(!(try environ.containsUnempty(allocator, "FOO=")));
- try std.testing.expect(!(try environ.containsUnempty(allocator, "FO")));
- try std.testing.expect(!(try environ.containsUnempty(allocator, "FOOO")));
if (builtin.os.tag == .windows) {
try std.testing.expect(try environ.containsUnempty(allocator, "foo"));
}
try std.testing.expect(try environ.containsUnempty(allocator, "EQUALS"));
- try std.testing.expect(!(try environ.containsUnempty(allocator, "EQUALS=ABC")));
try std.testing.expect(try environ.containsUnempty(allocator, "КИРиллИЦА"));
if (builtin.os.tag == .windows) {
try std.testing.expect(try environ.containsUnempty(allocator, "кирИЛЛица"));
@@ -35,14 +31,10 @@ pub fn main(init: std.process.Init) !void {
// containsUnemptyConstant
{
try std.testing.expect(environ.containsUnemptyConstant("FOO"));
- try std.testing.expect(!environ.containsUnemptyConstant("FOO="));
- try std.testing.expect(!environ.containsUnemptyConstant("FO"));
- try std.testing.expect(!environ.containsUnemptyConstant("FOOO"));
if (builtin.os.tag == .windows) {
try std.testing.expect(environ.containsUnemptyConstant("foo"));
}
try std.testing.expect(environ.containsUnemptyConstant("EQUALS"));
- try std.testing.expect(!environ.containsUnemptyConstant("EQUALS=ABC"));
try std.testing.expect(environ.containsUnemptyConstant("КИРиллИЦА"));
if (builtin.os.tag == .windows) {
try std.testing.expect(environ.containsUnemptyConstant("кирИЛЛица"));
@@ -58,14 +50,10 @@ pub fn main(init: std.process.Init) !void {
// contains
{
try std.testing.expect(try environ.contains(allocator, "FOO"));
- try std.testing.expect(!(try environ.contains(allocator, "FOO=")));
- try std.testing.expect(!(try environ.contains(allocator, "FO")));
- try std.testing.expect(!(try environ.contains(allocator, "FOOO")));
if (builtin.os.tag == .windows) {
try std.testing.expect(try environ.contains(allocator, "foo"));
}
try std.testing.expect(try environ.contains(allocator, "EQUALS"));
- try std.testing.expect(!(try environ.contains(allocator, "EQUALS=ABC")));
try std.testing.expect(try environ.contains(allocator, "КИРиллИЦА"));
if (builtin.os.tag == .windows) {
try std.testing.expect(try environ.contains(allocator, "кирИЛЛица"));
@@ -81,14 +69,10 @@ pub fn main(init: std.process.Init) !void {
// containsConstant
{
try std.testing.expect(environ.containsConstant("FOO"));
- try std.testing.expect(!environ.containsConstant("FOO="));
- try std.testing.expect(!environ.containsConstant("FO"));
- try std.testing.expect(!environ.containsConstant("FOOO"));
if (builtin.os.tag == .windows) {
try std.testing.expect(environ.containsConstant("foo"));
}
try std.testing.expect(environ.containsConstant("EQUALS"));
- try std.testing.expect(!environ.containsConstant("EQUALS=ABC"));
try std.testing.expect(environ.containsConstant("КИРиллИЦА"));
if (builtin.os.tag == .windows) {
try std.testing.expect(environ.containsConstant("кирИЛЛица"));
@@ -104,14 +88,10 @@ pub fn main(init: std.process.Init) !void {
// getAlloc
{
try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "FOO"));
- try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOO="));
- try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FO"));
- try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOOO"));
if (builtin.os.tag == .windows) {
try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "foo"));
}
try std.testing.expectEqualSlices(u8, "ABC=123", try environ.getAlloc(arena, "EQUALS"));
- try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "EQUALS=ABC"));
try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "КИРиллИЦА"));
if (builtin.os.tag == .windows) {
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 {
defer environ_map.deinit();
try std.testing.expectEqualSlices(u8, "123", environ_map.get("FOO").?);
- try std.testing.expectEqual(null, environ_map.get("FO"));
- try std.testing.expectEqual(null, environ_map.get("FOOO"));
if (builtin.os.tag == .windows) {
try std.testing.expectEqualSlices(u8, "123", environ_map.get("foo").?);
}
try std.testing.expectEqualSlices(u8, "ABC=123", environ_map.get("EQUALS").?);
- try std.testing.expectEqual(null, environ_map.get("EQUALS=ABC"));
try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("КИРиллИЦА").?);
if (builtin.os.tag == .windows) {
try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("кирИЛЛица").?);
diff --git a/test/standalone/windows_argv/fuzz.zig b/test/standalone/windows_argv/fuzz.zig
@@ -125,7 +125,7 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO
.lpReserved2 = null,
.hStdInput = null,
.hStdOutput = null,
- .hStdError = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null,
+ .hStdError = windows.peb().ProcessParameters.hStdError,
};
var proc_info: windows.PROCESS_INFORMATION = undefined;
@@ -149,7 +149,12 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO
break :spawn proc_info.hProcess;
};
defer windows.CloseHandle(child_proc);
- try windows.WaitForSingleObjectEx(child_proc, windows.INFINITE, false);
+ const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
+ switch (windows.ntdll.NtWaitForSingleObject(child_proc, windows.FALSE, &infinite_timeout)) {
+ windows.NTSTATUS.WAIT_0 => {},
+ .TIMEOUT => return error.WaitTimeOut,
+ else => |status| return windows.unexpectedStatus(status),
+ }
var exit_code: windows.DWORD = undefined;
if (windows.kernel32.GetExitCodeProcess(child_proc, &exit_code) == 0) {
diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig
@@ -233,12 +233,13 @@ fn testExecWithCwdInner(gpa: Allocator, io: Io, command: []const u8, cwd: std.pr
}
fn renameExe(dir: Io.Dir, io: Io, old_sub_path: []const u8, new_sub_path: []const u8) !void {
- var attempt: u5 = 0;
+ var attempt: u5 = 10;
while (true) break dir.rename(old_sub_path, dir, new_sub_path, io) catch |err| switch (err) {
error.AccessDenied => {
- if (attempt == 13) return error.AccessDenied;
+ if (attempt == 26) return error.AccessDenied;
// give the kernel a chance to finish closing the executable handle
- _ = std.os.windows.kernel32.SleepEx(@as(u32, 1) << attempt >> 1, std.os.windows.FALSE);
+ const interval = @as(std.os.windows.LARGE_INTEGER, -1) << attempt;
+ _ = std.os.windows.ntdll.NtDelayExecution(std.os.windows.FALSE, &interval);
attempt += 1;
continue;
},