diff --git a/lib/std/Build/Watch.zig b/lib/std/Build/Watch.zig index c4ac62b216..26ea84f490 100644 --- 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,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, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 8de5e2692a..5bcd025473 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -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(); diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index ee2a993a4d..ccb2d2dad3 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -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; }, } diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 33fd4c0234..f0c6e33256 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -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(); diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index b9653b3ac9..9a2861fdc0 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -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 index b6785e4a33..d6af93cfc3 100644 --- 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 index d9e68e54f9..bda9fee828 100644 --- 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 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; diff --git a/lib/std/process/Environ.zig b/lib/std/process/Environ.zig index 5255e1af16..00c240fb6f 100644 --- 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,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()); } diff --git a/lib/std/start.zig b/lib/std/start.zig index a8c281ae16..e39465fe9b 100644 --- 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/windows_argv/fuzz.zig b/test/standalone/windows_argv/fuzz.zig index b955697d38..4b062a6b75 100644 --- a/test/standalone/windows_argv/fuzz.zig +++ b/test/standalone/windows_argv/fuzz.zig @@ -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;