authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-01 17:53:02-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-04 00:27:08-08:00
log60447ea97cd86e38e566c23d40c123e19d50eb1a
tree49766d483d7acbce49239ba4a1d8055af068ff1e
parent08447ca47ed2ece816de9b0c5735be3f17edc6ca

std: fix windows compilation errors


7 files changed, 336 insertions(+), 216 deletions(-)

lib/std/Io/Threaded.zig+256-146
...@@ -5654,7 +5654,7 @@ fn dirSymLinkWindows(...@@ -5654,7 +5654,7 @@ fn dirSymLinkWindows(
5654 // Already an NT path, no need to do anything to it5654 // Already an NT path, no need to do anything to it
5655 break :target_path target_path_w.span();5655 break :target_path target_path_w.span();
5656 } else {5656 } else {
5657 switch (w.getWin32PathType(u16, target_path_w.span())) {5657 switch (Dir.path.getWin32PathType(u16, target_path_w.span())) {
5658 // Rooted paths need to avoid getting put through wToPrefixedFileW5658 // Rooted paths need to avoid getting put through wToPrefixedFileW
5659 // (and they are treated as relative in this context)5659 // (and they are treated as relative in this context)
5660 // Note: It seems that rooted paths in symbolic links are relative to5660 // Note: It seems that rooted paths in symbolic links are relative to
...@@ -12617,6 +12617,45 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void {...@@ -12617,6 +12617,45 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void {
1261712617
12618fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {}12618fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {}
1261912619
12620const WindowsEnvironStrings = struct {
12621 PATH: ?[:0]const u16 = null,
12622 PATHEXT: ?[:0]const u16 = null,
12623
12624 fn scan() WindowsEnvironStrings {
12625 const ptr = windows.peb().ProcessParameters.Environment;
12626
12627 var result: WindowsEnvironStrings = .{};
12628 var i: usize = 0;
12629 while (ptr[i] != 0) {
12630 const key_start = i;
12631
12632 // There are some special environment variables that start with =,
12633 // so we need a special case to not treat = as a key/value separator
12634 // if it's the first character.
12635 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
12636 if (ptr[key_start] == '=') i += 1;
12637
12638 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
12639 const key_w = ptr[key_start..i];
12640
12641 if (ptr[i] == '=') i += 1;
12642
12643 const value_start = i;
12644 while (ptr[i] != 0) : (i += 1) {}
12645 const value_w = ptr[value_start..i :0];
12646
12647 i += 1; // skip over null byte
12648
12649 inline for (@typeInfo(WindowsEnvironStrings).@"struct".fields) |field| {
12650 const field_name_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(field.name);
12651 if (std.mem.eql(u16, key_w, field_name_w)) @field(result, field.name) = value_w;
12652 }
12653 }
12654
12655 return result;
12656 }
12657};
12658
12620fn scanEnviron(t: *Threaded) void {12659fn scanEnviron(t: *Threaded) void {
12621 t.mutex.lock();12660 t.mutex.lock();
12622 defer t.mutex.unlock();12661 defer t.mutex.unlock();
...@@ -12625,6 +12664,9 @@ fn scanEnviron(t: *Threaded) void {...@@ -12625,6 +12664,9 @@ fn scanEnviron(t: *Threaded) void {
12625 t.environ.initialized = true;12664 t.environ.initialized = true;
1262612665
12627 if (is_windows) {12666 if (is_windows) {
12667 // This value expires with any call that modifies the environment,
12668 // which is outside of this Io implementation's control, so references
12669 // must be short-lived.
12628 const ptr = windows.peb().ProcessParameters.Environment;12670 const ptr = windows.peb().ProcessParameters.Environment;
1262912671
12630 var i: usize = 0;12672 var i: usize = 0;
...@@ -12779,6 +12821,7 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce...@@ -12779,6 +12821,7 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce
12779 };12821 };
1278012822
12781 const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore);12823 const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore);
12824 // TODO: cache file handle of /dev/null!
12782 const dev_null_fd = if (any_ignore)12825 const dev_null_fd = if (any_ignore)
12783 posix.openZ("/dev/null", .{ .ACCMODE = .RDWR }, 0) catch |err| switch (err) {12826 posix.openZ("/dev/null", .{ .ACCMODE = .RDWR }, 0) catch |err| switch (err) {
12784 error.PathAlreadyExists => unreachable,12827 error.PathAlreadyExists => unreachable,
...@@ -12962,55 +13005,88 @@ fn childWait(userdata: ?*anyopaque, child: *std.process.Child) process.Child.Wai...@@ -12962,55 +13005,88 @@ fn childWait(userdata: ?*anyopaque, child: *std.process.Child) process.Child.Wai
12962fn childKill(userdata: ?*anyopaque, child: *std.process.Child) void {13005fn childKill(userdata: ?*anyopaque, child: *std.process.Child) void {
12963 const t: *Threaded = @ptrCast(@alignCast(userdata));13006 const t: *Threaded = @ptrCast(@alignCast(userdata));
12964 if (is_windows) {13007 if (is_windows) {
12965 childKillWindows(t, child, 1) catch {13008 childKillWindows(t, child, 1) catch childCleanupWindows(child);
12966 childCleanupStreams(child);
12967 };
12968 } else {13009 } else {
12969 childKillPosix(t, child) catch {13010 childKillPosix(t, child) catch childCleanupPosix(child);
12970 childCleanupStreams(child);
12971 };
12972 }13011 }
12973}13012}
1297413013
12975fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT) !void {13014fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT) !void {
12976 windows.TerminateProcess(child.id, exit_code) catch |err| switch (err) {13015 _ = t; // TODO cancelation
12977 error.AccessDenied => {13016 const handle = child.id.?;
12978 // Usually when TerminateProcess triggers a ACCESS_DENIED error, it13017 if (windows.kernel32.TerminateProcess(handle, exit_code) == 0) {
12979 // indicates that the process has already exited, but there may be13018 switch (windows.GetLastError()) {
12980 // some rare edge cases where our process handle no longer has the13019 .ACCESS_DENIED => {
12981 // PROCESS_TERMINATE access right, so let's do another check to make13020 // Usually when TerminateProcess triggers a ACCESS_DENIED error, it
12982 // sure the process is really no longer running:13021 // indicates that the process has already exited, but there may be
12983 windows.WaitForSingleObjectEx(child.id, 0, false) catch return err;13022 // some rare edge cases where our process handle no longer has the
12984 return error.AlreadyTerminated;13023 // PROCESS_TERMINATE access right, so let's do another check to make
12985 },13024 // sure the process is really no longer running:
12986 else => return err,13025 windows.WaitForSingleObjectEx(handle, 0, false) catch return error.AccessDenied;
12987 };13026 return error.AlreadyTerminated;
12988 try childWaitWindows(t, child);13027 },
13028 else => |err| return windows.unexpectedError(err),
13029 }
13030 }
13031 _ = windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE);
13032 childCleanupWindows(child);
12989}13033}
1299013034
12991fn childWaitWindows(t: *Threaded, child: *process.Child) process.Child.WaitError!process.Child.Term {13035fn childWaitWindows(t: *Threaded, child: *process.Child) process.Child.WaitError!process.Child.Term {
12992 _ = t; // TODO cancelation13036 const current_thread = Thread.getCurrent(t);
12993 windows.WaitForSingleObjectEx(child.id, windows.INFINITE, false);13037 const handle = child.id.?;
13038
13039 while (true) {
13040 try current_thread.checkCancel();
13041 switch (windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE)) {
13042 windows.WAIT_OBJECT_0 => break,
13043 windows.WAIT_ABANDONED, windows.WAIT_TIMEOUT => continue,
13044 windows.WAIT_FAILED => switch (windows.GetLastError()) {
13045 else => |err| return windows.unexpectedError(err),
13046 },
13047 else => return error.Unexpected,
13048 }
13049 }
1299413050
12995 const term: process.Child.Term = x: {13051 const term: process.Child.Term = x: {
12996 var exit_code: windows.DWORD = undefined;13052 var exit_code: windows.DWORD = undefined;
12997 if (windows.kernel32.GetExitCodeProcess(child.id, &exit_code) == 0) {13053 if (windows.kernel32.GetExitCodeProcess(handle, &exit_code) == 0) {
12998 break :x .{ .unknown = 0 };13054 break :x .{ .unknown = 0 };
12999 } else {13055 } else {
13000 break :x .{ .exited = @as(u8, @truncate(exit_code)) };13056 break :x .{ .exited = @as(u8, @truncate(exit_code)) };
13001 }13057 }
13002 };13058 };
1300313059
13004 if (child.request_resource_usage_statistics) {13060 childCleanupWindows(child);
13005 child.resource_usage_statistics.rusage = try windows.GetProcessMemoryInfo(child.id);
13006 }
13007
13008 posix.close(child.id);
13009 posix.close(child.thread_handle);
13010 childCleanupStreams(child);
13011 return term;13061 return term;
13012}13062}
1301313063
13064fn childCleanupWindows(child: *process.Child) void {
13065 const handle = child.id orelse return;
13066
13067 if (child.request_resource_usage_statistics)
13068 child.resource_usage_statistics.rusage = windows.GetProcessMemoryInfo(handle) catch null;
13069
13070 windows.CloseHandle(handle);
13071 child.id = null;
13072
13073 windows.CloseHandle(child.thread_handle);
13074 child.thread_handle = undefined;
13075
13076 if (child.stdin) |*stdin| {
13077 windows.CloseHandle(stdin.handle);
13078 child.stdin = null;
13079 }
13080 if (child.stdout) |*stdout| {
13081 windows.CloseHandle(stdout.handle);
13082 child.stdout = null;
13083 }
13084 if (child.stderr) |*stderr| {
13085 windows.CloseHandle(stderr.handle);
13086 child.stderr = null;
13087 }
13088}
13089
13014fn childWaitPosix(t: *Threaded, child: *process.Child) process.Child.WaitError!process.Child.Term {13090fn childWaitPosix(t: *Threaded, child: *process.Child) process.Child.WaitError!process.Child.Term {
13015 _ = t; // TODO cancelation13091 _ = t; // TODO cancelation
13016 const pid = child.id.?;13092 const pid = child.id.?;
...@@ -13023,7 +13099,7 @@ fn childWaitPosix(t: *Threaded, child: *process.Child) process.Child.WaitError!p...@@ -13023,7 +13099,7 @@ fn childWaitPosix(t: *Threaded, child: *process.Child) process.Child.WaitError!p
13023 }13099 }
13024 break :res posix.waitpid(pid, 0);13100 break :res posix.waitpid(pid, 0);
13025 };13101 };
13026 childCleanupStreams(child);13102 childCleanupPosix(child);
13027 return statusToTerm(res.status);13103 return statusToTerm(res.status);
13028}13104}
1302913105
...@@ -13050,7 +13126,7 @@ fn childKillPosix(t: *Threaded, child: *process.Child) !void {...@@ -13050,7 +13126,7 @@ fn childKillPosix(t: *Threaded, child: *process.Child) !void {
13050 _ = try childWaitPosix(t, child);13126 _ = try childWaitPosix(t, child);
13051}13127}
1305213128
13053fn childCleanupStreams(child: *process.Child) void {13129fn childCleanupPosix(child: *process.Child) void {
13054 if (child.stdin) |*stdin| {13130 if (child.stdin) |*stdin| {
13055 posix.close(stdin.handle);13131 posix.close(stdin.handle);
13056 child.stdin = null;13132 child.stdin = null;
...@@ -13140,9 +13216,8 @@ fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32...@@ -13140,9 +13216,8 @@ fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32
13140 }13216 }
13141}13217}
1314213218
13143fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.SpawnError!void {13219fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
13144 const t: *Threaded = @ptrCast(@alignCast(userdata));13220 const t: *Threaded = @ptrCast(@alignCast(userdata));
13145 _ = t;
1314613221
13147 var saAttr: windows.SECURITY_ATTRIBUTES = .{13222 var saAttr: windows.SECURITY_ATTRIBUTES = .{
13148 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),13223 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
...@@ -13151,10 +13226,11 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa...@@ -13151,10 +13226,11 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa
13151 };13226 };
1315213227
13153 const any_ignore =13228 const any_ignore =
13154 child.stdin_behavior == .ignore or13229 options.stdin == .ignore or
13155 child.stdout_behavior == .ignore or13230 options.stdout == .ignore or
13156 child.stderr_behavior == .ignore;13231 options.stderr == .ignore;
1315713232
13233 // TODO: cache the handle to null file!
13158 const nul_handle = if (any_ignore)13234 const nul_handle = if (any_ignore)
13159 // "\Device\Null" or "\??\NUL"13235 // "\Device\Null" or "\??\NUL"
13160 windows.OpenFile(&[_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }, .{13236 windows.OpenFile(&[_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }, .{
...@@ -13185,7 +13261,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa...@@ -13185,7 +13261,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa
1318513261
13186 var g_hChildStd_IN_Rd: ?windows.HANDLE = null;13262 var g_hChildStd_IN_Rd: ?windows.HANDLE = null;
13187 var g_hChildStd_IN_Wr: ?windows.HANDLE = null;13263 var g_hChildStd_IN_Wr: ?windows.HANDLE = null;
13188 switch (child.stdin_behavior) {13264 switch (options.stdin) {
13189 .pipe => {13265 .pipe => {
13190 try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr);13266 try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr);
13191 },13267 },
...@@ -13198,14 +13274,15 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa...@@ -13198,14 +13274,15 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa
13198 .close => {13274 .close => {
13199 g_hChildStd_IN_Rd = null;13275 g_hChildStd_IN_Rd = null;
13200 },13276 },
13277 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
13201 }13278 }
13202 errdefer if (child.stdin_behavior == .pipe) {13279 errdefer if (options.stdin == .pipe) {
13203 windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr);13280 windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr);
13204 };13281 };
1320513282
13206 var g_hChildStd_OUT_Rd: ?windows.HANDLE = null;13283 var g_hChildStd_OUT_Rd: ?windows.HANDLE = null;
13207 var g_hChildStd_OUT_Wr: ?windows.HANDLE = null;13284 var g_hChildStd_OUT_Wr: ?windows.HANDLE = null;
13208 switch (child.stdout_behavior) {13285 switch (options.stdout) {
13209 .pipe => {13286 .pipe => {
13210 try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr);13287 try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr);
13211 },13288 },
...@@ -13218,14 +13295,15 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa...@@ -13218,14 +13295,15 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa
13218 .close => {13295 .close => {
13219 g_hChildStd_OUT_Wr = null;13296 g_hChildStd_OUT_Wr = null;
13220 },13297 },
13298 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
13221 }13299 }
13222 errdefer if (child.stdout_behavior == .pipe) {13300 errdefer if (options.stdout == .pipe) {
13223 windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr);13301 windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr);
13224 };13302 };
1322513303
13226 var g_hChildStd_ERR_Rd: ?windows.HANDLE = null;13304 var g_hChildStd_ERR_Rd: ?windows.HANDLE = null;
13227 var g_hChildStd_ERR_Wr: ?windows.HANDLE = null;13305 var g_hChildStd_ERR_Wr: ?windows.HANDLE = null;
13228 switch (child.stderr_behavior) {13306 switch (options.stderr) {
13229 .pipe => {13307 .pipe => {
13230 try windowsMakeAsyncPipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr);13308 try windowsMakeAsyncPipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr);
13231 },13309 },
...@@ -13238,12 +13316,13 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa...@@ -13238,12 +13316,13 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa
13238 .close => {13316 .close => {
13239 g_hChildStd_ERR_Wr = null;13317 g_hChildStd_ERR_Wr = null;
13240 },13318 },
13319 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
13241 }13320 }
13242 errdefer if (child.stderr_behavior == .pipe) {13321 errdefer if (options.stderr == .pipe) {
13243 windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr);13322 windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr);
13244 };13323 };
1324513324
13246 var siStartInfo = windows.STARTUPINFOW{13325 var siStartInfo: windows.STARTUPINFOW = .{
13247 .cb = @sizeOf(windows.STARTUPINFOW),13326 .cb = @sizeOf(windows.STARTUPINFOW),
13248 .hStdError = g_hChildStd_ERR_Wr,13327 .hStdError = g_hChildStd_ERR_Wr,
13249 .hStdOutput = g_hChildStd_OUT_Wr,13328 .hStdOutput = g_hChildStd_OUT_Wr,
...@@ -13266,63 +13345,63 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa...@@ -13266,63 +13345,63 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa
13266 };13345 };
13267 var piProcInfo: windows.PROCESS_INFORMATION = undefined;13346 var piProcInfo: windows.PROCESS_INFORMATION = undefined;
1326813347
13269 const cwd_w = if (child.cwd) |cwd| try std.unicode.wtf8ToWtf16LeAllocZ(child.allocator, cwd) else null;13348 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
13270 defer if (cwd_w) |cwd| child.allocator.free(cwd);13349 defer arena_allocator.deinit();
13350 const arena = arena_allocator.allocator();
13351
13352 const cwd_w = if (options.cwd) |cwd| try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd) else null;
13271 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;13353 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
1327213354
13273 const maybe_envp_buf = if (child.env_map) |env_map| try process.createWindowsEnvBlock(child.allocator, env_map) else null;13355 const maybe_envp_buf = if (options.env_map) |env_map| try env_map.createBlockWindows(arena) else null;
13274 defer if (maybe_envp_buf) |envp_buf| child.allocator.free(envp_buf);
13275 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;13356 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
1327613357
13277 const app_name_wtf8 = child.argv[0];13358 const app_name_wtf8 = options.argv[0];
13278 const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8);13359 const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8);
1327913360
13280 // the cwd set in Child is in effect when choosing the executable path13361 // The cwd provided by options is in effect when choosing the executable
13281 // to match posix semantics13362 // path to match POSIX semantics.
13282 var cwd_path_w_needs_free = false;13363 var cwd_path_w_needs_free = false;
13283 const cwd_path_w = x: {13364 const cwd_path_w = x: {
13284 // If the app name is absolute, then we need to use its dirname as the cwd13365 // If the app name is absolute, then we need to use its dirname as the cwd
13285 if (app_name_is_absolute) {13366 if (app_name_is_absolute) {
13286 cwd_path_w_needs_free = true;13367 cwd_path_w_needs_free = true;
13287 const dir = Dir.path.dirname(app_name_wtf8).?;13368 const dir = Dir.path.dirname(app_name_wtf8).?;
13288 break :x try std.unicode.wtf8ToWtf16LeAllocZ(child.allocator, dir);13369 break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, dir);
13289 } else if (child.cwd) |cwd| {13370 } else if (options.cwd) |cwd| {
13290 cwd_path_w_needs_free = true;13371 cwd_path_w_needs_free = true;
13291 break :x try std.unicode.wtf8ToWtf16LeAllocZ(child.allocator, cwd);13372 break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd);
13292 } else {13373 } else {
13293 break :x &[_:0]u16{}; // empty for cwd13374 break :x &[_:0]u16{}; // empty for cwd
13294 }13375 }
13295 };13376 };
13296 defer if (cwd_path_w_needs_free) child.allocator.free(cwd_path_w);
1329713377
13298 // If the app name has more than just a filename, then we need to separate that13378 // If the app name has more than just a filename, then we need to separate
13299 // into the basename and dirname and use the dirname as an addition to the cwd13379 // that into the basename and dirname and use the dirname as an addition to
13300 // path. This is because NtQueryDirectoryFile cannot accept FileName params with13380 // the cwd path. This is because NtQueryDirectoryFile cannot accept
13301 // path separators.13381 // FileName params with path separators.
13302 const app_basename_wtf8 = Dir.path.basename(app_name_wtf8);13382 const app_basename_wtf8 = Dir.path.basename(app_name_wtf8);
13303 // If the app name is absolute, then the cwd will already have the app's dirname in it,13383 // If the app name is absolute, then the cwd will already have the app's dirname in it,
13304 // so only populate app_dirname if app name is a relative path with > 0 path separators.13384 // so only populate app_dirname if app name is a relative path with > 0 path separators.
13305 const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) Dir.path.dirname(app_name_wtf8) else null;13385 const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) Dir.path.dirname(app_name_wtf8) else null;
13306 const app_dirname_w: ?[:0]u16 = x: {13386 const app_dirname_w: ?[:0]u16 = x: {
13307 if (maybe_app_dirname_wtf8) |app_dirname_wtf8| {13387 if (maybe_app_dirname_wtf8) |app_dirname_wtf8| {
13308 break :x try std.unicode.wtf8ToWtf16LeAllocZ(child.allocator, app_dirname_wtf8);13388 break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_dirname_wtf8);
13309 }13389 }
13310 break :x null;13390 break :x null;
13311 };13391 };
13312 defer if (app_dirname_w != null) child.allocator.free(app_dirname_w.?);13392 const app_name_w = try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_basename_wtf8);
13313
13314 const app_name_w = try std.unicode.wtf8ToWtf16LeAllocZ(child.allocator, app_basename_wtf8);
13315 defer child.allocator.free(app_name_w);
1331613393
13317 const flags: windows.CreateProcessFlags = .{13394 const flags: windows.CreateProcessFlags = .{
13318 .create_suspended = child.start_suspended,13395 .create_suspended = options.start_suspended,
13319 .create_unicode_environment = true,13396 .create_unicode_environment = true,
13320 .create_no_window = child.create_no_window,13397 .create_no_window = options.create_no_window,
13321 };13398 };
1332213399
13323 run: {13400 run: {
13324 const PATH: [:0]const u16 = process.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse &[_:0]u16{};13401 // We have to scan each time because the PEB environment pointer is not stable.
13325 const PATHEXT: [:0]const u16 = process.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("PATHEXT")) orelse &[_:0]u16{};13402 const env_strings: WindowsEnvironStrings = .scan();
13403 const PATH = env_strings.PATH orelse &[_:0]u16{};
13404 const PATHEXT = env_strings.PATHEXT orelse &[_:0]u16{};
1332613405
13327 // In case the command ends up being a .bat/.cmd script, we need to escape things using the cmd.exe rules13406 // In case the command ends up being a .bat/.cmd script, we need to escape things using the cmd.exe rules
13328 // and invoke cmd.exe ourselves in order to mitigate arbitrary command execution from maliciously13407 // and invoke cmd.exe ourselves in order to mitigate arbitrary command execution from maliciously
...@@ -13331,26 +13410,34 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa...@@ -13331,26 +13410,34 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa
13331 // We'll need to wait until we're actually trying to run the command to know for sure13410 // We'll need to wait until we're actually trying to run the command to know for sure
13332 // if the resolved command has the `.bat` or `.cmd` extension, so we defer actually13411 // if the resolved command has the `.bat` or `.cmd` extension, so we defer actually
13333 // serializing the command line until we determine how it should be serialized.13412 // serializing the command line until we determine how it should be serialized.
13334 var cmd_line_cache = WindowsCommandLineCache.init(child.allocator, child.argv);13413 var cmd_line_cache = WindowsCommandLineCache.init(arena, options.argv);
13335 defer cmd_line_cache.deinit();
1333613414
13337 var app_buf: std.ArrayList(u16) = .empty;13415 var app_buf: std.ArrayList(u16) = .empty;
13338 defer app_buf.deinit(child.allocator);13416 try app_buf.appendSlice(arena, app_name_w);
13339
13340 try app_buf.appendSlice(child.allocator, app_name_w);
1334113417
13342 var dir_buf: std.ArrayList(u16) = .empty;13418 var dir_buf: std.ArrayList(u16) = .empty;
13343 defer dir_buf.deinit(child.allocator);
1334413419
13345 if (cwd_path_w.len > 0) {13420 if (cwd_path_w.len > 0) {
13346 try dir_buf.appendSlice(child.allocator, cwd_path_w);13421 try dir_buf.appendSlice(arena, cwd_path_w);
13347 }13422 }
13348 if (app_dirname_w) |app_dir| {13423 if (app_dirname_w) |app_dir| {
13349 if (dir_buf.items.len > 0) try dir_buf.append(child.allocator, Dir.path.sep);13424 if (dir_buf.items.len > 0) try dir_buf.append(arena, Dir.path.sep);
13350 try dir_buf.appendSlice(child.allocator, app_dir);13425 try dir_buf.appendSlice(arena, app_dir);
13351 }13426 }
1335213427
13353 windowsCreateProcessPathExt(child.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo) catch |no_path_err| {13428 windowsCreateProcessPathExt(
13429 t,
13430 arena,
13431 &dir_buf,
13432 &app_buf,
13433 PATHEXT,
13434 &cmd_line_cache,
13435 envp_ptr,
13436 cwd_w_ptr,
13437 flags,
13438 &siStartInfo,
13439 &piProcInfo,
13440 ) catch |no_path_err| {
13354 const original_err = switch (no_path_err) {13441 const original_err = switch (no_path_err) {
13355 // argv[0] contains unsupported characters that will never resolve to a valid exe.13442 // argv[0] contains unsupported characters that will never resolve to a valid exe.
13356 error.InvalidArg0 => return error.FileNotFound,13443 error.InvalidArg0 => return error.FileNotFound,
...@@ -13362,7 +13449,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa...@@ -13362,7 +13449,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa
13362 // If the app name had path separators, that disallows PATH searching,13449 // If the app name had path separators, that disallows PATH searching,
13363 // and there's no need to search the PATH if the app name is absolute.13450 // and there's no need to search the PATH if the app name is absolute.
13364 // We still search the path if the cwd is absolute because of the13451 // We still search the path if the cwd is absolute because of the
13365 // "cwd set in Child is in effect when choosing the executable path13452 // "cwd provided by options is in effect when choosing the executable path
13366 // to match posix semantics" behavior--we don't want to skip searching13453 // to match posix semantics" behavior--we don't want to skip searching
13367 // the PATH just because we were trying to set the cwd of the child process.13454 // the PATH just because we were trying to set the cwd of the child process.
13368 if (app_dirname_w != null or app_name_is_absolute) {13455 if (app_dirname_w != null or app_name_is_absolute) {
...@@ -13372,9 +13459,21 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa...@@ -13372,9 +13459,21 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa
13372 var it = std.mem.tokenizeScalar(u16, PATH, ';');13459 var it = std.mem.tokenizeScalar(u16, PATH, ';');
13373 while (it.next()) |search_path| {13460 while (it.next()) |search_path| {
13374 dir_buf.clearRetainingCapacity();13461 dir_buf.clearRetainingCapacity();
13375 try dir_buf.appendSlice(child.allocator, search_path);13462 try dir_buf.appendSlice(arena, search_path);
1337613463
13377 if (windowsCreateProcessPathExt(child.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) {13464 if (windowsCreateProcessPathExt(
13465 t,
13466 arena,
13467 &dir_buf,
13468 &app_buf,
13469 PATHEXT,
13470 &cmd_line_cache,
13471 envp_ptr,
13472 cwd_w_ptr,
13473 flags,
13474 &siStartInfo,
13475 &piProcInfo,
13476 )) {
13378 break :run;13477 break :run;
13379 } else |err| switch (err) {13478 } else |err| switch (err) {
13380 // argv[0] contains unsupported characters that will never resolve to a valid exe.13479 // argv[0] contains unsupported characters that will never resolve to a valid exe.
...@@ -13389,35 +13488,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa...@@ -13389,35 +13488,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa
13389 };13488 };
13390 }13489 }
1339113490
13392 if (g_hChildStd_IN_Wr) |h| {13491 if (options.stdin == .pipe) windows.CloseHandle(g_hChildStd_IN_Rd.?);
13393 child.stdin = File{ .handle = h };13492 if (options.stderr == .pipe) windows.CloseHandle(g_hChildStd_ERR_Wr.?);
13394 } else {13493 if (options.stdout == .pipe) windows.CloseHandle(g_hChildStd_OUT_Wr.?);
13395 child.stdin = null;
13396 }
13397 if (g_hChildStd_OUT_Rd) |h| {
13398 child.stdout = File{ .handle = h };
13399 } else {
13400 child.stdout = null;
13401 }
13402 if (g_hChildStd_ERR_Rd) |h| {
13403 child.stderr = File{ .handle = h };
13404 } else {
13405 child.stderr = null;
13406 }
13407
13408 child.id = piProcInfo.hProcess;
13409 child.thread_handle = piProcInfo.hThread;
13410 child.term = null;
1341113494
13412 if (child.stdin_behavior == .pipe) {13495 return .{
13413 posix.close(g_hChildStd_IN_Rd.?);13496 .id = piProcInfo.hProcess,
13414 }13497 .thread_handle = piProcInfo.hThread,
13415 if (child.stderr_behavior == .pipe) {13498 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h } else null,
13416 posix.close(g_hChildStd_ERR_Wr.?);13499 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h } else null,
13417 }13500 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h } else null,
13418 if (child.stdout_behavior == .pipe) {13501 .request_resource_usage_statistics = options.request_resource_usage_statistics,
13419 posix.close(g_hChildStd_OUT_Wr.?);13502 };
13420 }
13421}13503}
1342213504
13423/// Expects `app_buf` to contain exactly the app name, and `dir_buf` to contain exactly the dir path.13505/// Expects `app_buf` to contain exactly the app name, and `dir_buf` to contain exactly the dir path.
...@@ -13425,12 +13507,12 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa...@@ -13425,12 +13507,12 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa
13425/// Note: `app_buf` should not contain any leading path separators.13507/// Note: `app_buf` should not contain any leading path separators.
13426/// Note: If the dir is the cwd, dir_buf should be empty (len = 0).13508/// Note: If the dir is the cwd, dir_buf should be empty (len = 0).
13427fn windowsCreateProcessPathExt(13509fn windowsCreateProcessPathExt(
13428 allocator: Allocator,13510 arena: Allocator,
13429 dir_buf: *std.ArrayList(u16),13511 dir_buf: *std.ArrayList(u16),
13430 app_buf: *std.ArrayList(u16),13512 app_buf: *std.ArrayList(u16),
13431 pathext: [:0]const u16,13513 pathext: [:0]const u16,
13432 cmd_line_cache: *WindowsCommandLineCache,13514 cmd_line_cache: *WindowsCommandLineCache,
13433 envp_ptr: ?[*]u16,13515 envp_ptr: ?[*:0]const u16,
13434 cwd_ptr: ?[*:0]u16,13516 cwd_ptr: ?[*:0]u16,
13435 flags: windows.CreateProcessFlags,13517 flags: windows.CreateProcessFlags,
13436 lpStartupInfo: *windows.STARTUPINFOW,13518 lpStartupInfo: *windows.STARTUPINFOW,
...@@ -13471,7 +13553,7 @@ fn windowsCreateProcessPathExt(...@@ -13471,7 +13553,7 @@ fn windowsCreateProcessPathExt(
13471 // that scenario.13553 // that scenario.
13472 var dir = dir: {13554 var dir = dir: {
13473 // needs to be null-terminated13555 // needs to be null-terminated
13474 try dir_buf.append(allocator, 0);13556 try dir_buf.append(arena, 0);
13475 defer dir_buf.shrinkRetainingCapacity(dir_path_len);13557 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
13476 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];13558 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
13477 const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);13559 const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);
...@@ -13482,8 +13564,8 @@ fn windowsCreateProcessPathExt(...@@ -13482,8 +13564,8 @@ fn windowsCreateProcessPathExt(
13482 defer windows.CloseHandle(dir.handle);13564 defer windows.CloseHandle(dir.handle);
1348313565
13484 // Add wildcard and null-terminator13566 // Add wildcard and null-terminator
13485 try app_buf.append(allocator, '*');13567 try app_buf.append(arena, '*');
13486 try app_buf.append(allocator, 0);13568 try app_buf.append(arena, 0);
13487 const app_name_wildcard = app_buf.items[0 .. app_buf.items.len - 1 :0];13569 const app_name_wildcard = app_buf.items[0 .. app_buf.items.len - 1 :0];
1348813570
13489 // This 2048 is arbitrary, we just want it to be large enough to get multiple FILE_DIRECTORY_INFORMATION entries13571 // This 2048 is arbitrary, we just want it to be large enough to get multiple FILE_DIRECTORY_INFORMATION entries
...@@ -13563,10 +13645,10 @@ fn windowsCreateProcessPathExt(...@@ -13563,10 +13645,10 @@ fn windowsCreateProcessPathExt(
13563 if (unappended_exists) {13645 if (unappended_exists) {
13564 if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) {13646 if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) {
13565 '/', '\\' => {},13647 '/', '\\' => {},
13566 else => try dir_buf.append(allocator, Dir.path.sep),13648 else => try dir_buf.append(arena, Dir.path.sep),
13567 };13649 };
13568 try dir_buf.appendSlice(allocator, app_buf.items[0..app_name_len]);13650 try dir_buf.appendSlice(arena, app_buf.items[0..app_name_len]);
13569 try dir_buf.append(allocator, 0);13651 try dir_buf.append(arena, 0);
13570 const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0];13652 const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
1357113653
13572 const is_bat_or_cmd = bat_or_cmd: {13654 const is_bat_or_cmd = bat_or_cmd: {
...@@ -13588,7 +13670,15 @@ fn windowsCreateProcessPathExt(...@@ -13588,7 +13670,15 @@ fn windowsCreateProcessPathExt(
13588 else13670 else
13589 full_app_name;13671 full_app_name;
1359013672
13591 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {13673 if (windowsCreateProcess(
13674 app_name_w.ptr,
13675 cmd_line_w.ptr,
13676 envp_ptr,
13677 cwd_ptr,
13678 flags,
13679 lpStartupInfo,
13680 lpProcessInformation,
13681 )) |_| {
13592 return;13682 return;
13593 } else |err| switch (err) {13683 } else |err| switch (err) {
13594 error.FileNotFound,13684 error.FileNotFound,
...@@ -13623,11 +13713,11 @@ fn windowsCreateProcessPathExt(...@@ -13623,11 +13713,11 @@ fn windowsCreateProcessPathExt(
13623 dir_buf.shrinkRetainingCapacity(dir_path_len);13713 dir_buf.shrinkRetainingCapacity(dir_path_len);
13624 if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) {13714 if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) {
13625 '/', '\\' => {},13715 '/', '\\' => {},
13626 else => try dir_buf.append(allocator, Dir.path.sep),13716 else => try dir_buf.append(arena, Dir.path.sep),
13627 };13717 };
13628 try dir_buf.appendSlice(allocator, app_buf.items[0..app_name_len]);13718 try dir_buf.appendSlice(arena, app_buf.items[0..app_name_len]);
13629 try dir_buf.appendSlice(allocator, ext);13719 try dir_buf.appendSlice(arena, ext);
13630 try dir_buf.append(allocator, 0);13720 try dir_buf.append(arena, 0);
13631 const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0];13721 const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
1363213722
13633 const is_bat_or_cmd = switch (ext_enum) {13723 const is_bat_or_cmd = switch (ext_enum) {
...@@ -13667,41 +13757,61 @@ fn windowsCreateProcessPathExt(...@@ -13667,41 +13757,61 @@ fn windowsCreateProcessPathExt(
13667fn windowsCreateProcess(13757fn windowsCreateProcess(
13668 app_name: [*:0]u16,13758 app_name: [*:0]u16,
13669 cmd_line: [*:0]u16,13759 cmd_line: [*:0]u16,
13670 envp_ptr: ?[*]u16,13760 env_ptr: ?[*:0]const u16,
13671 cwd_ptr: ?[*:0]u16,13761 cwd_ptr: ?[*:0]u16,
13672 flags: windows.CreateProcessFlags,13762 flags: windows.CreateProcessFlags,
13673 lpStartupInfo: *windows.STARTUPINFOW,13763 lpStartupInfo: *windows.STARTUPINFOW,
13674 lpProcessInformation: *windows.PROCESS_INFORMATION,13764 lpProcessInformation: *windows.PROCESS_INFORMATION,
13675) !void {13765) !void {
13676 // TODO the docs for environment pointer say:13766 if (windows.kernel32.CreateProcessW(
13677 // > A pointer to the environment block for the new process. If this parameter
13678 // > is NULL, the new process uses the environment of the calling process.
13679 // > ...
13680 // > An environment block can contain either Unicode or ANSI characters. If
13681 // > the environment block pointed to by lpEnvironment contains Unicode
13682 // > characters, be sure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT.
13683 // > If this parameter is NULL and the environment block of the parent process
13684 // > contains Unicode characters, you must also ensure that dwCreationFlags
13685 // > includes CREATE_UNICODE_ENVIRONMENT.
13686 // This seems to imply that we have to somehow know whether our process parent passed
13687 // CREATE_UNICODE_ENVIRONMENT if we want to pass NULL for the environment parameter.
13688 // Since we do not know this information that would imply that we must not pass NULL
13689 // for the parameter.
13690 // However this would imply that programs compiled with -DUNICODE could not pass
13691 // environment variables to programs that were not, which seems unlikely.
13692 // More investigation is needed.
13693 return windows.CreateProcessW(
13694 app_name,13767 app_name,
13695 cmd_line,13768 cmd_line,
13696 null,13769 null,
13697 null,13770 null,
13698 windows.TRUE,13771 windows.TRUE,
13699 flags,13772 flags,
13700 @as(?*anyopaque, @ptrCast(envp_ptr)),13773 env_ptr,
13701 cwd_ptr,13774 cwd_ptr,
13702 lpStartupInfo,13775 lpStartupInfo,
13703 lpProcessInformation,13776 lpProcessInformation,
13704 );13777 ) == 0) switch (windows.GetLastError()) {
13778 .FILE_NOT_FOUND => return error.FileNotFound,
13779 .PATH_NOT_FOUND => return error.FileNotFound,
13780 .DIRECTORY => return error.FileNotFound,
13781 .ACCESS_DENIED => return error.AccessDenied,
13782 .INVALID_PARAMETER => unreachable,
13783 .INVALID_NAME => return error.InvalidName,
13784 .FILENAME_EXCED_RANGE => return error.NameTooLong,
13785 .SHARING_VIOLATION => return error.FileBusy,
13786
13787 // These are all the system errors that are mapped to ENOEXEC by
13788 // the undocumented _dosmaperr (old CRT) or __acrt_errno_map_os_error
13789 // (newer CRT) functions. Their code can be found in crt/src/dosmap.c (old SDK)
13790 // or urt/misc/errno.cpp (newer SDK) in the Windows SDK.
13791 .BAD_FORMAT,
13792 .INVALID_STARTING_CODESEG, // MIN_EXEC_ERROR in errno.cpp
13793 .INVALID_STACKSEG,
13794 .INVALID_MODULETYPE,
13795 .INVALID_EXE_SIGNATURE,
13796 .EXE_MARKED_INVALID,
13797 .BAD_EXE_FORMAT,
13798 .ITERATED_DATA_EXCEEDS_64k,
13799 .INVALID_MINALLOCSIZE,
13800 .DYNLINK_FROM_INVALID_RING,
13801 .IOPL_NOT_ENABLED,
13802 .INVALID_SEGDPL,
13803 .AUTODATASEG_EXCEEDS_64k,
13804 .RING2SEG_MUST_BE_MOVABLE,
13805 .RELOC_CHAIN_XEEDS_SEGLIM,
13806 .INFLOOP_IN_RELOC_CHAIN, // MAX_EXEC_ERROR in errno.cpp
13807 // This one is not mapped to ENOEXEC but it is possible, for example
13808 // when calling CreateProcessW on a plain text file with a .exe extension
13809 .EXE_MACHINE_TYPE_MISMATCH,
13810 => return error.InvalidExe,
13811
13812 .COMMITMENT_LIMIT => return error.SystemResources,
13813 else => |err| return windows.unexpectedError(err),
13814 };
13705}13815}
1370613816
13707/// Case-insensitive WTF-16 lookup13817/// Case-insensitive WTF-16 lookup
lib/std/fs/test.zig+1-1
...@@ -79,7 +79,7 @@ const PathType = enum {...@@ -79,7 +79,7 @@ const PathType = enum {
79 // using '127.0.0.1' as the server name and '<drive letter>$' as the share name.79 // using '127.0.0.1' as the server name and '<drive letter>$' as the share name.
80 var fd_path_buf: [Dir.max_path_bytes]u8 = undefined;80 var fd_path_buf: [Dir.max_path_bytes]u8 = undefined;
81 const dir_path = fd_path_buf[0..try dir.realPath(io, &fd_path_buf)];81 const dir_path = fd_path_buf[0..try dir.realPath(io, &fd_path_buf)];
82 const windows_path_type = windows.getWin32PathType(u8, dir_path);82 const windows_path_type = Dir.path.getWin32PathType(u8, dir_path);
83 switch (windows_path_type) {83 switch (windows_path_type) {
84 .unc_absolute => return Dir.path.joinZ(allocator, &.{ dir_path, relative_path }),84 .unc_absolute => return Dir.path.joinZ(allocator, &.{ dir_path, relative_path }),
85 .drive_absolute => {85 .drive_absolute => {
lib/std/os/windows.zig+2-13
...@@ -3756,17 +3756,6 @@ pub fn GetModuleFileNameW(hModule: ?HMODULE, buf_ptr: [*]u16, buf_len: DWORD) Ge...@@ -3756,17 +3756,6 @@ pub fn GetModuleFileNameW(hModule: ?HMODULE, buf_ptr: [*]u16, buf_len: DWORD) Ge
3756 return buf_ptr[0..rc :0];3756 return buf_ptr[0..rc :0];
3757}3757}
37583758
3759pub const TerminateProcessError = error{ AccessDenied, Unexpected };
3760
3761pub fn TerminateProcess(hProcess: HANDLE, uExitCode: UINT) TerminateProcessError!void {
3762 if (kernel32.TerminateProcess(hProcess, uExitCode) == 0) {
3763 switch (GetLastError()) {
3764 Win32Error.ACCESS_DENIED => return error.AccessDenied,
3765 else => |err| return unexpectedError(err),
3766 }
3767 }
3768}
3769
3770pub const NtAllocateVirtualMemoryError = error{3759pub const NtAllocateVirtualMemoryError = error{
3771 AccessDenied,3760 AccessDenied,
3772 InvalidParameter,3761 InvalidParameter,
...@@ -3919,7 +3908,7 @@ pub fn CreateProcessW(...@@ -3919,7 +3908,7 @@ pub fn CreateProcessW(
3919 lpThreadAttributes: ?*SECURITY_ATTRIBUTES,3908 lpThreadAttributes: ?*SECURITY_ATTRIBUTES,
3920 bInheritHandles: BOOL,3909 bInheritHandles: BOOL,
3921 dwCreationFlags: CreateProcessFlags,3910 dwCreationFlags: CreateProcessFlags,
3922 lpEnvironment: ?*anyopaque,3911 lpEnvironment: ?[*:0]u16,
3923 lpCurrentDirectory: ?LPCWSTR,3912 lpCurrentDirectory: ?LPCWSTR,
3924 lpStartupInfo: *STARTUPINFOW,3913 lpStartupInfo: *STARTUPINFOW,
3925 lpProcessInformation: *PROCESS_INFORMATION,3914 lpProcessInformation: *PROCESS_INFORMATION,
...@@ -4539,7 +4528,7 @@ const LocalDevicePathType = enum {...@@ -4539,7 +4528,7 @@ const LocalDevicePathType = enum {
4539};4528};
45404529
4541/// Only relevant for Win32 -> NT path conversion.4530/// Only relevant for Win32 -> NT path conversion.
4542/// Asserts `path` is of type `Win32PathType.local_device`.4531/// Asserts `path` is of type `std.fs.path.Win32PathType.local_device`.
4543fn getLocalDevicePathType(comptime T: type, path: []const T) LocalDevicePathType {4532fn getLocalDevicePathType(comptime T: type, path: []const T) LocalDevicePathType {
4544 if (std.debug.runtime_safety) {4533 if (std.debug.runtime_safety) {
4545 assert(std.fs.path.getWin32PathType(T, path) == .local_device);4534 assert(std.fs.path.getWin32PathType(T, path) == .local_device);
lib/std/os/windows/kernel32.zig+1-1
...@@ -265,7 +265,7 @@ pub extern "kernel32" fn CreateProcessW(...@@ -265,7 +265,7 @@ pub extern "kernel32" fn CreateProcessW(
265 lpThreadAttributes: ?*SECURITY_ATTRIBUTES,265 lpThreadAttributes: ?*SECURITY_ATTRIBUTES,
266 bInheritHandles: BOOL,266 bInheritHandles: BOOL,
267 dwCreationFlags: windows.CreateProcessFlags,267 dwCreationFlags: windows.CreateProcessFlags,
268 lpEnvironment: ?LPVOID,268 lpEnvironment: ?[*:0]const u16,
269 lpCurrentDirectory: ?LPCWSTR,269 lpCurrentDirectory: ?LPCWSTR,
270 lpStartupInfo: *STARTUPINFOW,270 lpStartupInfo: *STARTUPINFOW,
271 lpProcessInformation: *PROCESS_INFORMATION,271 lpProcessInformation: *PROCESS_INFORMATION,
lib/std/os/windows/test.zig+3-3
...@@ -274,8 +274,8 @@ test "getWin32PathType vs RtlDetermineDosPathNameType_U" {...@@ -274,8 +274,8 @@ test "getWin32PathType vs RtlDetermineDosPathNameType_U" {
274 std.debug.assert(std.unicode.wtf16LeToWtf8(wtf8_buf.items, path) == wtf8_len);274 std.debug.assert(std.unicode.wtf16LeToWtf8(wtf8_buf.items, path) == wtf8_len);
275275
276 const windows_type = RtlDetermineDosPathNameType_U(path);276 const windows_type = RtlDetermineDosPathNameType_U(path);
277 const wtf16_type = windows.getWin32PathType(u16, path);277 const wtf16_type = std.fs.path.getWin32PathType(u16, path);
278 const wtf8_type = windows.getWin32PathType(u8, wtf8_buf.items);278 const wtf8_type = std.fs.path.getWin32PathType(u8, wtf8_buf.items);
279279
280 checkPathType(windows_type, wtf16_type) catch |err| {280 checkPathType(windows_type, wtf16_type) catch |err| {
281 std.debug.print("expected type {}, got {} for path: {f}\n", .{ windows_type, wtf16_type, std.unicode.fmtUtf16Le(path) });281 std.debug.print("expected type {}, got {} for path: {f}\n", .{ windows_type, wtf16_type, std.unicode.fmtUtf16Le(path) });
...@@ -295,7 +295,7 @@ test "getWin32PathType vs RtlDetermineDosPathNameType_U" {...@@ -295,7 +295,7 @@ test "getWin32PathType vs RtlDetermineDosPathNameType_U" {
295 }295 }
296}296}
297297
298fn checkPathType(windows_type: RTL_PATH_TYPE, zig_type: windows.Win32PathType) !void {298fn checkPathType(windows_type: RTL_PATH_TYPE, zig_type: std.fs.path.Win32PathType) !void {
299 const expected_windows_type: RTL_PATH_TYPE = switch (zig_type) {299 const expected_windows_type: RTL_PATH_TYPE = switch (zig_type) {
300 .unc_absolute => .UncAbsolute,300 .unc_absolute => .UncAbsolute,
301 .drive_absolute => .DriveAbsolute,301 .drive_absolute => .DriveAbsolute,
lib/std/process/Args.zig+8-13
...@@ -29,13 +29,8 @@ pub const Iterator = struct {...@@ -29,13 +29,8 @@ pub const Iterator = struct {
29 /// Initialize the args iterator. Consider using `initAllocator` instead29 /// Initialize the args iterator. Consider using `initAllocator` instead
30 /// for cross-platform compatibility.30 /// for cross-platform compatibility.
31 pub fn init(a: Args) Iterator {31 pub fn init(a: Args) Iterator {
32 if (native_os == .wasi) {32 if (native_os == .wasi) @compileError("In WASI, use initAllocator instead.");
33 @compileError("In WASI, use initAllocator instead.");33 if (native_os == .windows) @compileError("In Windows, use initAllocator instead.");
34 }
35 if (native_os == .windows) {
36 @compileError("In Windows, use initAllocator instead.");
37 }
38
39 return .{ .inner = .init(a) };34 return .{ .inner = .init(a) };
40 }35 }
4136
...@@ -44,10 +39,10 @@ pub const Iterator = struct {...@@ -44,10 +39,10 @@ pub const Iterator = struct {
44 /// You must deinitialize iterator's internal buffers by calling `deinit` when done.39 /// You must deinitialize iterator's internal buffers by calling `deinit` when done.
45 pub fn initAllocator(a: Args, gpa: Allocator) InitError!Iterator {40 pub fn initAllocator(a: Args, gpa: Allocator) InitError!Iterator {
46 if (native_os == .wasi and !builtin.link_libc) {41 if (native_os == .wasi and !builtin.link_libc) {
47 return .{ .inner = try .init(a, gpa) };42 return .{ .inner = try .init(gpa) };
48 }43 }
49 if (native_os == .windows) {44 if (native_os == .windows) {
50 return .{ .inner = try .init(a, gpa) };45 return .{ .inner = try .init(gpa, a.vector) };
51 }46 }
5247
53 return .{ .inner = .init(a) };48 return .{ .inner = .init(a) };
...@@ -111,7 +106,7 @@ pub const Iterator = struct {...@@ -111,7 +106,7 @@ pub const Iterator = struct {
111 ///106 ///
112 /// The iterator stores and uses `cmd_line_w`, so its memory must be valid for107 /// The iterator stores and uses `cmd_line_w`, so its memory must be valid for
113 /// at least as long as the returned Windows.108 /// at least as long as the returned Windows.
114 pub fn init(allocator: Allocator, cmd_line_w: []const u16) Windows.InitError!Windows {109 pub fn init(gpa: Allocator, cmd_line_w: []const u16) Windows.InitError!Windows {
115 const wtf8_len = std.unicode.calcWtf8Len(cmd_line_w);110 const wtf8_len = std.unicode.calcWtf8Len(cmd_line_w);
116111
117 // This buffer must be large enough to contain contiguous NUL-terminated slices112 // This buffer must be large enough to contain contiguous NUL-terminated slices
...@@ -121,11 +116,11 @@ pub const Iterator = struct {...@@ -121,11 +116,11 @@ pub const Iterator = struct {
121 // - The first argument needs one extra byte of space allocated for its NUL116 // - The first argument needs one extra byte of space allocated for its NUL
122 // terminator, but for each subsequent argument the necessary whitespace117 // terminator, but for each subsequent argument the necessary whitespace
123 // between arguments guarantees room for their NUL terminator(s).118 // between arguments guarantees room for their NUL terminator(s).
124 const buffer = try allocator.alloc(u8, wtf8_len + 1);119 const buffer = try gpa.alloc(u8, wtf8_len + 1);
125 errdefer allocator.free(buffer);120 errdefer gpa.free(buffer);
126121
127 return .{122 return .{
128 .allocator = allocator,123 .allocator = gpa,
129 .cmd_line = cmd_line_w,124 .cmd_line = cmd_line_w,
130 .buffer = buffer,125 .buffer = buffer,
131 };126 };
lib/std/process/Environ.zig+65-39
...@@ -15,7 +15,7 @@ const mem = std.mem;...@@ -15,7 +15,7 @@ const mem = std.mem;
15block: Block,15block: Block,
1616
17pub const empty: Environ = .{17pub const empty: Environ = .{
18 .block = switch (@TypeOf(Block)) {18 .block = switch (Block) {
19 void => {},19 void => {},
20 else => &.{},20 else => &.{},
21 },21 },
...@@ -65,7 +65,7 @@ pub const Map = struct {...@@ -65,7 +65,7 @@ pub const Map = struct {
65 @as(u8, @intCast((cp_upper >> 0) & 0xff)),65 @as(u8, @intCast((cp_upper >> 0) & 0xff)),
66 });66 });
67 }67 }
68 return h.final();68 return @truncate(h.final());
69 }69 }
70 return std.array_hash_map.hashString(s);70 return std.array_hash_map.hashString(s);
71 }71 }
...@@ -293,8 +293,8 @@ pub const Map = struct {...@@ -293,8 +293,8 @@ pub const Map = struct {
293 return envp_buf;293 return envp_buf;
294 }294 }
295295
296 /// Caller must free result.296 /// Caller owns result.
297 pub fn createBlockWindows(map: *const Map, gpa: Allocator) Allocator.Error![]u16 {297 pub fn createBlockWindows(map: *const Map, gpa: Allocator) error{ OutOfMemory, InvalidWtf8 }![:0]u16 {
298 // count bytes needed298 // count bytes needed
299 const max_chars_needed = x: {299 const max_chars_needed = x: {
300 // Only need 2 trailing NUL code units for an empty environment300 // Only need 2 trailing NUL code units for an empty environment
...@@ -330,54 +330,27 @@ pub const Map = struct {...@@ -330,54 +330,27 @@ pub const Map = struct {
330 result[i] = 0;330 result[i] = 0;
331 i += 1;331 i += 1;
332 }332 }
333 return try gpa.realloc(result, i);333 const reallocated = try gpa.realloc(result, i);
334 return reallocated[0 .. i - 1 :0];
334 }335 }
335};336};
336337
337pub const CreateMapError = error{338pub const CreateMapError = error{
338 OutOfMemory,339 OutOfMemory,
339 /// WASI-only. `environ_sizes_get` or `environ_get` failed for an340 /// WASI-only. `environ_sizes_get` or `environ_get` failed for an
340 /// unexpected reason.341 /// unanticipated, undocumented reason.
341 Unexpected,342 Unexpected,
342};343};
343344
344/// Allocates a `Map` and copies environment block into it.345/// Allocates a `Map` and copies environment block into it.
345pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {346pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
347 if (native_os == .windows)
348 return createMapWide(std.os.windows.peb().ProcessParameters.Environment, allocator);
349
346 var result = Map.init(allocator);350 var result = Map.init(allocator);
347 errdefer result.deinit();351 errdefer result.deinit();
348352
349 if (native_os == .windows) {353 if (native_os == .wasi and !builtin.link_libc) {
350 const ptr = std.os.windows.peb().ProcessParameters.Environment;
351
352 var i: usize = 0;
353 while (ptr[i] != 0) {
354 const key_start = i;
355
356 // There are some special environment variables that start with =,
357 // so we need a special case to not treat = as a key/value separator
358 // if it's the first character.
359 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
360 if (ptr[key_start] == '=') i += 1;
361
362 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
363 const key_w = ptr[key_start..i];
364 const key = try unicode.wtf16LeToWtf8Alloc(allocator, key_w);
365 errdefer allocator.free(key);
366
367 if (ptr[i] == '=') i += 1;
368
369 const value_start = i;
370 while (ptr[i] != 0) : (i += 1) {}
371 const value_w = ptr[value_start..i];
372 const value = try unicode.wtf16LeToWtf8Alloc(allocator, value_w);
373 errdefer allocator.free(value);
374
375 i += 1; // skip over null byte
376
377 try result.putMove(key, value);
378 }
379 return result;
380 } else if (native_os == .wasi and !builtin.link_libc) {
381 var environ_count: usize = undefined;354 var environ_count: usize = undefined;
382 var environ_buf_size: usize = undefined;355 var environ_buf_size: usize = undefined;
383356
...@@ -439,6 +412,40 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {...@@ -439,6 +412,40 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
439 }412 }
440}413}
441414
415pub fn createMapWide(ptr: [*:0]u16, gpa: Allocator) CreateMapError!Map {
416 var result = Map.init(gpa);
417 errdefer result.deinit();
418
419 var i: usize = 0;
420 while (ptr[i] != 0) {
421 const key_start = i;
422
423 // There are some special environment variables that start with =,
424 // so we need a special case to not treat = as a key/value separator
425 // if it's the first character.
426 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
427 if (ptr[key_start] == '=') i += 1;
428
429 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
430 const key_w = ptr[key_start..i];
431 const key = try unicode.wtf16LeToWtf8Alloc(gpa, key_w);
432 errdefer gpa.free(key);
433
434 if (ptr[i] == '=') i += 1;
435
436 const value_start = i;
437 while (ptr[i] != 0) : (i += 1) {}
438 const value_w = ptr[value_start..i];
439 const value = try unicode.wtf16LeToWtf8Alloc(gpa, value_w);
440 errdefer gpa.free(value);
441
442 i += 1; // skip over null byte
443
444 try result.putMove(key, value);
445 }
446 return result;
447}
448
442pub const ContainsError = error{449pub const ContainsError = error{
443 OutOfMemory,450 OutOfMemory,
444 /// On Windows, environment variable keys provided by the user must be451 /// On Windows, environment variable keys provided by the user must be
...@@ -777,7 +784,7 @@ test "convert from Environ to Map and back again" {...@@ -777,7 +784,7 @@ test "convert from Environ to Map and back again" {
777 const arena = arena_allocator.allocator();784 const arena = arena_allocator.allocator();
778785
779 const environ: Environ = switch (native_os) {786 const environ: Environ = switch (native_os) {
780 .windows => .{ .block = try map.createBlockWindows(arena) },787 .windows => return error.SkipZigTest,
781 .wasi => if (!builtin.libc) return error.SkipZigTest,788 .wasi => if (!builtin.libc) return error.SkipZigTest,
782 else => .{ .block = try map.createBlockPosix(arena, .{}) },789 else => .{ .block = try map.createBlockPosix(arena, .{}) },
783 };790 };
...@@ -804,3 +811,22 @@ test "convert from Environ to Map and back again" {...@@ -804,3 +811,22 @@ test "convert from Environ to Map and back again" {
804 try testing.expectEqualDeep(map.keys(), map2.keys());811 try testing.expectEqualDeep(map.keys(), map2.keys());
805 try testing.expectEqualDeep(map.values(), map2.values());812 try testing.expectEqualDeep(map.values(), map2.values());
806}813}
814
815test createMapWide {
816 const gpa = testing.allocator;
817
818 var map: Map = .init(gpa);
819 defer map.deinit();
820 try map.put("FOO", "BAR");
821 try map.put("A", "");
822 try map.put("", "B");
823
824 const environ: [:0]u16 = try map.createBlockWindows(gpa);
825 defer gpa.free(environ);
826
827 var map2 = try createMapWide(environ, gpa);
828 defer map2.deinit();
829
830 try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B" }, map2.keys());
831 try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "" }, map2.values());
832}