std.Io.Threaded: implement and cleanup windows codepaths

This commit is contained in:
Jacob Young
2026-01-30 13:07:41 -05:00
committed by Andrew Kelley
parent e5454ff780
commit 753e71e2f5
10 changed files with 840 additions and 980 deletions
+6 -11
View File
@@ -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,8 +377,11 @@ 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,
+352 -334
View File
@@ -73,6 +73,7 @@ environ: Environ,
null_file: NullFile = .{},
random_file: RandomFile = .{},
pipe_file: PipeFile = .{},
csprng: Csprng = .{},
@@ -118,7 +119,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,
@@ -190,6 +191,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,
_,
@@ -1467,7 +1486,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,
};
@@ -1502,6 +1523,7 @@ pub fn deinit(t: *Threaded) void {
}
t.null_file.deinit();
t.random_file.deinit();
t.pipe_file.deinit();
t.* = undefined;
}
@@ -1544,14 +1566,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);
}
@@ -3288,12 +3303,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,
@@ -3975,13 +3986,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();
@@ -4197,14 +4204,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
@@ -4811,17 +4812,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.
@@ -4840,7 +4830,10 @@ pub fn dirOpenFileWtf16(
.WRITE = flags.isWrite(),
},
},
&attr,
&.{
.RootDirectory = dir_handle,
.ObjectName = &nt_name,
},
&io_status_block,
null,
.{ .NORMAL = true },
@@ -5202,12 +5195,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,
@@ -6417,12 +6406,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,
@@ -7242,14 +7227,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;
@@ -7800,24 +7779,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),
}
}
}
@@ -14364,15 +14338,15 @@ 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;
@@ -14402,19 +14376,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 {
@@ -14513,16 +14485,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`.
@@ -14601,7 +14571,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);
}
@@ -14615,7 +14585,6 @@ 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]);
return .{
@@ -14739,42 +14708,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)) {
.WAIT_0 => break alertable_syscall.finish(),
.ABANDONED_WAIT_0, .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);
@@ -15037,88 +15008,70 @@ 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 getNulDevice(t) else undefined;
const nul_handle = if (any_ignore) try getNulHandle(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;
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);
};
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);
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);
};
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);
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 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 = .SYNCHRONOUS_NONALERT } },
.inbound = true,
}) 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.stdin) {
.inherit => peb.ProcessParameters.hStdError,
.file => |file| file.handle,
.ignore => nul_handle,
.pipe => stderr_pipe[1],
.close => null,
},
.lpReserved = null,
.lpDesktop = null,
@@ -15143,8 +15096,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
const cwd_w = if (options.cwd) |cwd| try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd) else null;
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);
@@ -15222,7 +15185,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
&app_buf,
PATHEXT,
&cmd_line_cache,
envp_ptr,
env_block,
cwd_w_ptr,
flags,
&siStartInfo,
@@ -15257,7 +15220,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
&app_buf,
PATHEXT,
&cmd_line_cache,
envp_ptr,
env_block,
cwd_w_ptr,
flags,
&siStartInfo,
@@ -15277,21 +15240,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.setIpcFd(prog_pipe[0]);
}
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 {
{
t.mutex.lock();
defer t.mutex.unlock();
@@ -15299,12 +15281,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();
@@ -15315,12 +15291,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,
@@ -15347,7 +15322,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
};
}
fn getNulHandle(t: *Threaded) !windows.HANDLE {
fn getNulDevice(t: *Threaded) !windows.HANDLE {
{
t.mutex.lock();
defer t.mutex.unlock();
@@ -15355,44 +15330,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();
@@ -15406,18 +15363,64 @@ fn getNulHandle(t: *Threaded) !windows.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
// finished with the deletion operation, and so this CreateFile
// call has failed. There is not really a sane way to handle
// this other than retrying the creation after the OS finishes
// the deletion.
syscall.finish();
try parking_sleep.windowsRetrySleep(1);
syscall = try .start();
.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 {
{
t.mutex.lock();
defer t.mutex.unlock();
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();
t.mutex.lock(); // Another thread might have won the race.
defer t.mutex.unlock();
if (t.pipe_file.handle) |prev_handle| {
windows.CloseHandle(fresh_handle);
return prev_handle;
} else {
t.pipe_file.handle = fresh_handle;
return fresh_handle;
}
},
.CANCELLED => {
try syscall.checkCancel();
continue;
@@ -15449,7 +15452,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,
@@ -15626,7 +15629,7 @@ fn windowsCreateProcessPathExt(
if (windowsCreateProcess(
app_name_w.ptr,
cmd_line_w.ptr,
envp_ptr,
env_block,
cwd_ptr,
flags,
lpStartupInfo,
@@ -15686,7 +15689,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,
@@ -15710,7 +15713,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,
@@ -15725,7 +15728,7 @@ fn windowsCreateProcess(
null,
windows.TRUE,
flags,
env_ptr,
if (env_block) |block| block.slice.ptr else null,
cwd_ptr,
lpStartupInfo,
lpProcessInformation,
@@ -16246,11 +16249,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.
@@ -16278,7 +16281,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 => {},
@@ -16293,10 +16296,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,
@@ -16328,85 +16331,100 @@ 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;
// 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];
const PipeOptions = struct {
attributes: windows.OBJECT_ATTRIBUTES.ATTRIBUTES,
mode: windows.FILE.MODE,
};
pub const CreatePipeOptions = struct {
server: PipeOptions,
client: PipeOptions,
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,
};
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 => 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;
};
// 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;
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 => syscall.finish(),
.CANCELED => {
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));
@@ -16517,7 +16535,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();
+20 -17
View File
@@ -139,7 +139,7 @@ pub const Node = struct {
fn setIpcFd(s: *Storage, fd: Io.File.Handle) void {
const integer: u32 = switch (@typeInfo(Io.File.Handle)) {
.int => @bitCast(fd),
.pointer => @intFromPtr(fd),
.pointer => @intCast(@intFromPtr(fd)),
else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
};
// `estimated_total_count` max int indicates the special state that
@@ -342,10 +342,18 @@ pub const Node = struct {
/// Posix-only. Used by `std.process.Child`. Thread-safe.
pub fn setIpcFd(node: Node, fd: Io.File.Handle) 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);
switch (@typeInfo(Io.File.Handle)) {
.int => {
assert(fd >= 0);
assert(fd != posix.STDOUT_FILENO);
assert(fd != posix.STDIN_FILENO);
assert(fd != posix.STDERR_FILENO);
},
.pointer => {
assert(fd != windows.INVALID_HANDLE_VALUE);
},
else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
}
storageByIndex(index).setIpcFd(fd);
}
@@ -477,21 +485,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 +509,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 +533,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;
},
}
+5 -1
View File
@@ -726,7 +726,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)) {
.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();
+18 -249
View File
@@ -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 {};
-108
View File
@@ -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;
+11 -6
View File
@@ -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 NtCancelIoFile(
FileHandle: HANDLE,
IoStatusBlock: *IO_STATUS_BLOCK,
) callconv(.winapi) NTSTATUS;
pub extern "ntdll" fn NtCancelIoFileEx(
FileHandle: HANDLE,
IoRequestToCancel: *const IO_STATUS_BLOCK,
IoStatusBlock: *IO_STATUS_BLOCK,
) callconv(.winapi) NTSTATUS;
pub extern "ntdll" fn NtCancelIoFile(
FileHandle: HANDLE,
IoStatusBlock: *IO_STATUS_BLOCK,
) callconv(.winapi) NTSTATUS;
+412 -246
View File
@@ -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,13 +14,6 @@ const mem = std.mem;
/// Unmodified, unprocessed data provided by the operating system.
block: Block,
pub const empty: Environ = .{
.block = switch (Block) {
void => {},
else => &.{},
},
};
/// On WASI without libc, this is `void` because the environment has to be
/// queried and heap-allocated at runtime.
///
@@ -28,13 +21,62 @@ 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 {
pub const global: GlobalBlock = .{};
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 +88,64 @@ 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),
};
}
fn validateKey(key: []const u8) bool {
switch (builtin.mode) {
.Debug, .ReleaseSafe => {},
.ReleaseFast, .ReleaseSmall => return key.len > 0,
}
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
@@ -108,21 +167,65 @@ pub const Map = struct {
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(m: *const Map) [][]const u8 {
return m.array_hash_map.values();
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 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 +237,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 +258,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 +267,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 +285,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 +302,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,39 +337,41 @@ 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;
}
{
@@ -275,63 +381,81 @@ pub const Map = struct {
.add => unreachable,
.delete => continue,
.edit => {
envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{
envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={d}", .{
pair.key_ptr.*, options.zig_progress_fd.?,
}, 0);
i += 1;
envp_len += 1;
continue;
},
.nothing => {},
};
envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0);
i += 1;
envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 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;
const max_chars_needed = max_chars_needed: {
var max_chars_needed: usize = "\x00".len;
var it = map.iterator();
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)});
};
while (it.next()) |pair| {
// +1 for '='
// +1 for null byte
max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2;
if (options.zig_progress_handle != null and
eqlKeys(pair.key_ptr.*, "ZIG_PROGRESS")) continue;
max_chars_needed += pair.key_ptr.len + "=".len + pair.value_ptr.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;
};
var it = map.iterator();
while (it.next()) |pair| {
i += try unicode.wtf8ToWtf16Le(block[i..], pair.key_ptr.*);
block[i] = '=';
i += 1;
i += try unicode.wtf8ToWtf16Le(block[i..], pair.value_ptr.*);
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 +468,14 @@ 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();
if (native_os == .wasi and !builtin.link_libc) {
var map = Map.init(allocator);
errdefer map.deinit();
if (native_os == .windows) {
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) {
var environ_count: usize = undefined;
var environ_buf_size: usize = undefined;
@@ -360,7 +485,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 +498,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{
@@ -516,16 +587,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;
}
@@ -548,7 +618,10 @@ pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 {
const key_slice = mem.sliceTo(key, 0);
if (key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') != null) return null;
const ptr = std.os.windows.peb().ProcessParameters.Environment;
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) {
@@ -604,7 +677,7 @@ pub fn getAlloc(environ: Environ, gpa: Allocator, key: []const u8) GetAllocError
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 +686,145 @@ 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 {
_ = existing;
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());
}
+9 -8
View File
@@ -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
+7
View File
@@ -149,6 +149,13 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO
break :spawn proc_info.hProcess;
};
defer windows.CloseHandle(child_proc);
const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
switch (windows.ntdll.NtWaitForSingleObject(child_proc, windows.FALSE, &infinite_timeout)) {
.WAIT_0 => {},
.ABANDONED_WAIT_0 => return error.WaitAbandoned,
.TIMEOUT => return error.WaitTimeOut,
else => |status| return windows.unexpectedStatus(status),
}
try windows.WaitForSingleObjectEx(child_proc, windows.INFINITE, false);
var exit_code: windows.DWORD = undefined;