| ... | ... | @@ -5654,7 +5654,7 @@ fn dirSymLinkWindows( |
| 5654 | 5654 | // Already an NT path, no need to do anything to it |
| 5655 | 5655 | break :target_path target_path_w.span(); |
| 5656 | 5656 | } else { |
| 5657 | | switch (w.getWin32PathType(u16, target_path_w.span())) { |
| 5657 | switch (Dir.path.getWin32PathType(u16, target_path_w.span())) { |
| 5658 | 5658 | // Rooted paths need to avoid getting put through wToPrefixedFileW |
| 5659 | 5659 | // (and they are treated as relative in this context) |
| 5660 | 5660 | // 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 | 12617 | |
| 12618 | 12618 | fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {} |
| 12619 | 12619 | |
| 12620 | const 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 | |
| 12620 | 12659 | fn scanEnviron(t: *Threaded) void { |
| 12621 | 12660 | t.mutex.lock(); |
| 12622 | 12661 | defer t.mutex.unlock(); |
| ... | ... | @@ -12625,6 +12664,9 @@ fn scanEnviron(t: *Threaded) void { |
| 12625 | 12664 | t.environ.initialized = true; |
| 12626 | 12665 | |
| 12627 | 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 | 12670 | const ptr = windows.peb().ProcessParameters.Environment; |
| 12629 | 12671 | |
| 12630 | 12672 | var i: usize = 0; |
| ... | ... | @@ -12779,6 +12821,7 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce |
| 12779 | 12821 | }; |
| 12780 | 12822 | |
| 12781 | 12823 | const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore); |
| 12824 | // TODO: cache file handle of /dev/null! |
| 12782 | 12825 | const dev_null_fd = if (any_ignore) |
| 12783 | 12826 | posix.openZ("/dev/null", .{ .ACCMODE = .RDWR }, 0) catch |err| switch (err) { |
| 12784 | 12827 | error.PathAlreadyExists => unreachable, |
| ... | ... | @@ -12962,55 +13005,88 @@ fn childWait(userdata: ?*anyopaque, child: *std.process.Child) process.Child.Wai |
| 12962 | 13005 | fn childKill(userdata: ?*anyopaque, child: *std.process.Child) void { |
| 12963 | 13006 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 12964 | 13007 | if (is_windows) { |
| 12965 | | childKillWindows(t, child, 1) catch { |
| 12966 | | childCleanupStreams(child); |
| 12967 | | }; |
| 13008 | childKillWindows(t, child, 1) catch childCleanupWindows(child); |
| 12968 | 13009 | } else { |
| 12969 | | childKillPosix(t, child) catch { |
| 12970 | | childCleanupStreams(child); |
| 12971 | | }; |
| 13010 | childKillPosix(t, child) catch childCleanupPosix(child); |
| 12972 | 13011 | } |
| 12973 | 13012 | } |
| 12974 | 13013 | |
| 12975 | 13014 | fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT) !void { |
| 12976 | | windows.TerminateProcess(child.id, exit_code) catch |err| switch (err) { |
| 12977 | | error.AccessDenied => { |
| 12978 | | // Usually when TerminateProcess triggers a ACCESS_DENIED error, it |
| 12979 | | // indicates that the process has already exited, but there may be |
| 12980 | | // some rare edge cases where our process handle no longer has the |
| 12981 | | // PROCESS_TERMINATE access right, so let's do another check to make |
| 12982 | | // sure the process is really no longer running: |
| 12983 | | windows.WaitForSingleObjectEx(child.id, 0, false) catch return err; |
| 12984 | | return error.AlreadyTerminated; |
| 12985 | | }, |
| 12986 | | else => return err, |
| 12987 | | }; |
| 12988 | | try childWaitWindows(t, child); |
| 13015 | _ = t; // TODO cancelation |
| 13016 | const handle = child.id.?; |
| 13017 | if (windows.kernel32.TerminateProcess(handle, exit_code) == 0) { |
| 13018 | switch (windows.GetLastError()) { |
| 13019 | .ACCESS_DENIED => { |
| 13020 | // Usually when TerminateProcess triggers a ACCESS_DENIED error, it |
| 13021 | // indicates that the process has already exited, but there may be |
| 13022 | // some rare edge cases where our process handle no longer has the |
| 13023 | // PROCESS_TERMINATE access right, so let's do another check to make |
| 13024 | // sure the process is really no longer running: |
| 13025 | windows.WaitForSingleObjectEx(handle, 0, false) catch return error.AccessDenied; |
| 13026 | return error.AlreadyTerminated; |
| 13027 | }, |
| 13028 | else => |err| return windows.unexpectedError(err), |
| 13029 | } |
| 13030 | } |
| 13031 | _ = windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE); |
| 13032 | childCleanupWindows(child); |
| 12989 | 13033 | } |
| 12990 | 13034 | |
| 12991 | 13035 | fn childWaitWindows(t: *Threaded, child: *process.Child) process.Child.WaitError!process.Child.Term { |
| 12992 | | _ = t; // TODO cancelation |
| 12993 | | windows.WaitForSingleObjectEx(child.id, windows.INFINITE, false); |
| 13036 | const current_thread = Thread.getCurrent(t); |
| 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 | } |
| 12994 | 13050 | |
| 12995 | 13051 | const term: process.Child.Term = x: { |
| 12996 | 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 | 13054 | break :x .{ .unknown = 0 }; |
| 12999 | 13055 | } else { |
| 13000 | 13056 | break :x .{ .exited = @as(u8, @truncate(exit_code)) }; |
| 13001 | 13057 | } |
| 13002 | 13058 | }; |
| 13003 | 13059 | |
| 13004 | | if (child.request_resource_usage_statistics) { |
| 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); |
| 13060 | childCleanupWindows(child); |
| 13011 | 13061 | return term; |
| 13012 | 13062 | } |
| 13013 | 13063 | |
| 13064 | fn 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 | |
| 13014 | 13090 | fn childWaitPosix(t: *Threaded, child: *process.Child) process.Child.WaitError!process.Child.Term { |
| 13015 | 13091 | _ = t; // TODO cancelation |
| 13016 | 13092 | const pid = child.id.?; |
| ... | ... | @@ -13023,7 +13099,7 @@ fn childWaitPosix(t: *Threaded, child: *process.Child) process.Child.WaitError!p |
| 13023 | 13099 | } |
| 13024 | 13100 | break :res posix.waitpid(pid, 0); |
| 13025 | 13101 | }; |
| 13026 | | childCleanupStreams(child); |
| 13102 | childCleanupPosix(child); |
| 13027 | 13103 | return statusToTerm(res.status); |
| 13028 | 13104 | } |
| 13029 | 13105 | |
| ... | ... | @@ -13050,7 +13126,7 @@ fn childKillPosix(t: *Threaded, child: *process.Child) !void { |
| 13050 | 13126 | _ = try childWaitPosix(t, child); |
| 13051 | 13127 | } |
| 13052 | 13128 | |
| 13053 | | fn childCleanupStreams(child: *process.Child) void { |
| 13129 | fn childCleanupPosix(child: *process.Child) void { |
| 13054 | 13130 | if (child.stdin) |*stdin| { |
| 13055 | 13131 | posix.close(stdin.handle); |
| 13056 | 13132 | child.stdin = null; |
| ... | ... | @@ -13140,9 +13216,8 @@ fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32 |
| 13140 | 13216 | } |
| 13141 | 13217 | } |
| 13142 | 13218 | |
| 13143 | | fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.SpawnError!void { |
| 13219 | fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { |
| 13144 | 13220 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 13145 | | _ = t; |
| 13146 | 13221 | |
| 13147 | 13222 | var saAttr: windows.SECURITY_ATTRIBUTES = .{ |
| 13148 | 13223 | .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), |
| ... | ... | @@ -13151,10 +13226,11 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa |
| 13151 | 13226 | }; |
| 13152 | 13227 | |
| 13153 | 13228 | const any_ignore = |
| 13154 | | child.stdin_behavior == .ignore or |
| 13155 | | child.stdout_behavior == .ignore or |
| 13156 | | child.stderr_behavior == .ignore; |
| 13229 | options.stdin == .ignore or |
| 13230 | options.stdout == .ignore or |
| 13231 | options.stderr == .ignore; |
| 13157 | 13232 | |
| 13233 | // TODO: cache the handle to null file! |
| 13158 | 13234 | const nul_handle = if (any_ignore) |
| 13159 | 13235 | // "\Device\Null" or "\??\NUL" |
| 13160 | 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 | 13261 | |
| 13186 | 13262 | var g_hChildStd_IN_Rd: ?windows.HANDLE = null; |
| 13187 | 13263 | var g_hChildStd_IN_Wr: ?windows.HANDLE = null; |
| 13188 | | switch (child.stdin_behavior) { |
| 13264 | switch (options.stdin) { |
| 13189 | 13265 | .pipe => { |
| 13190 | 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 | 13274 | .close => { |
| 13199 | 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 | 13280 | windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr); |
| 13204 | 13281 | }; |
| 13205 | 13282 | |
| 13206 | 13283 | var g_hChildStd_OUT_Rd: ?windows.HANDLE = null; |
| 13207 | 13284 | var g_hChildStd_OUT_Wr: ?windows.HANDLE = null; |
| 13208 | | switch (child.stdout_behavior) { |
| 13285 | switch (options.stdout) { |
| 13209 | 13286 | .pipe => { |
| 13210 | 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 | 13295 | .close => { |
| 13219 | 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 | 13301 | windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr); |
| 13224 | 13302 | }; |
| 13225 | 13303 | |
| 13226 | 13304 | var g_hChildStd_ERR_Rd: ?windows.HANDLE = null; |
| 13227 | 13305 | var g_hChildStd_ERR_Wr: ?windows.HANDLE = null; |
| 13228 | | switch (child.stderr_behavior) { |
| 13306 | switch (options.stderr) { |
| 13229 | 13307 | .pipe => { |
| 13230 | 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 | 13316 | .close => { |
| 13239 | 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 | 13322 | windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr); |
| 13244 | 13323 | }; |
| 13245 | 13324 | |
| 13246 | | var siStartInfo = windows.STARTUPINFOW{ |
| 13325 | var siStartInfo: windows.STARTUPINFOW = .{ |
| 13247 | 13326 | .cb = @sizeOf(windows.STARTUPINFOW), |
| 13248 | 13327 | .hStdError = g_hChildStd_ERR_Wr, |
| 13249 | 13328 | .hStdOutput = g_hChildStd_OUT_Wr, |
| ... | ... | @@ -13266,63 +13345,63 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa |
| 13266 | 13345 | }; |
| 13267 | 13346 | var piProcInfo: windows.PROCESS_INFORMATION = undefined; |
| 13268 | 13347 | |
| 13269 | | const cwd_w = if (child.cwd) |cwd| try std.unicode.wtf8ToWtf16LeAllocZ(child.allocator, cwd) else null; |
| 13270 | | defer if (cwd_w) |cwd| child.allocator.free(cwd); |
| 13348 | var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); |
| 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 | 13353 | const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null; |
| 13272 | 13354 | |
| 13273 | | const maybe_envp_buf = if (child.env_map) |env_map| try process.createWindowsEnvBlock(child.allocator, env_map) else null; |
| 13274 | | defer if (maybe_envp_buf) |envp_buf| child.allocator.free(envp_buf); |
| 13355 | const maybe_envp_buf = if (options.env_map) |env_map| try env_map.createBlockWindows(arena) else null; |
| 13275 | 13356 | const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null; |
| 13276 | 13357 | |
| 13277 | | const app_name_wtf8 = child.argv[0]; |
| 13358 | const app_name_wtf8 = options.argv[0]; |
| 13278 | 13359 | const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8); |
| 13279 | 13360 | |
| 13280 | | // the cwd set in Child is in effect when choosing the executable path |
| 13281 | | // to match posix semantics |
| 13361 | // The cwd provided by options is in effect when choosing the executable |
| 13362 | // path to match POSIX semantics. |
| 13282 | 13363 | var cwd_path_w_needs_free = false; |
| 13283 | 13364 | const cwd_path_w = x: { |
| 13284 | 13365 | // If the app name is absolute, then we need to use its dirname as the cwd |
| 13285 | 13366 | if (app_name_is_absolute) { |
| 13286 | 13367 | cwd_path_w_needs_free = true; |
| 13287 | 13368 | const dir = Dir.path.dirname(app_name_wtf8).?; |
| 13288 | | break :x try std.unicode.wtf8ToWtf16LeAllocZ(child.allocator, dir); |
| 13289 | | } else if (child.cwd) |cwd| { |
| 13369 | break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, dir); |
| 13370 | } else if (options.cwd) |cwd| { |
| 13290 | 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 | 13373 | } else { |
| 13293 | 13374 | 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); |
| 13297 | 13377 | |
| 13298 | | // If the app name has more than just a filename, then we need to separate that |
| 13299 | | // into the basename and dirname and use the dirname as an addition to the cwd |
| 13300 | | // path. This is because NtQueryDirectoryFile cannot accept FileName params with |
| 13301 | | // path separators. |
| 13378 | // If the app name has more than just a filename, then we need to separate |
| 13379 | // that into the basename and dirname and use the dirname as an addition to |
| 13380 | // the cwd path. This is because NtQueryDirectoryFile cannot accept |
| 13381 | // FileName params with path separators. |
| 13302 | 13382 | const app_basename_wtf8 = Dir.path.basename(app_name_wtf8); |
| 13303 | 13383 | // If the app name is absolute, then the cwd will already have the app's dirname in it, |
| 13304 | 13384 | // so only populate app_dirname if app name is a relative path with > 0 path separators. |
| 13305 | 13385 | const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) Dir.path.dirname(app_name_wtf8) else null; |
| 13306 | 13386 | const app_dirname_w: ?[:0]u16 = x: { |
| 13307 | 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 | 13390 | break :x null; |
| 13311 | 13391 | }; |
| 13312 | | defer if (app_dirname_w != null) child.allocator.free(app_dirname_w.?); |
| 13313 | | |
| 13314 | | const app_name_w = try std.unicode.wtf8ToWtf16LeAllocZ(child.allocator, app_basename_wtf8); |
| 13315 | | defer child.allocator.free(app_name_w); |
| 13392 | const app_name_w = try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_basename_wtf8); |
| 13316 | 13393 | |
| 13317 | 13394 | const flags: windows.CreateProcessFlags = .{ |
| 13318 | | .create_suspended = child.start_suspended, |
| 13395 | .create_suspended = options.start_suspended, |
| 13319 | 13396 | .create_unicode_environment = true, |
| 13320 | | .create_no_window = child.create_no_window, |
| 13397 | .create_no_window = options.create_no_window, |
| 13321 | 13398 | }; |
| 13322 | 13399 | |
| 13323 | 13400 | run: { |
| 13324 | | const PATH: [:0]const u16 = process.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse &[_:0]u16{}; |
| 13325 | | const PATHEXT: [:0]const u16 = process.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("PATHEXT")) orelse &[_:0]u16{}; |
| 13401 | // We have to scan each time because the PEB environment pointer is not stable. |
| 13402 | const env_strings: WindowsEnvironStrings = .scan(); |
| 13403 | const PATH = env_strings.PATH orelse &[_:0]u16{}; |
| 13404 | const PATHEXT = env_strings.PATHEXT orelse &[_:0]u16{}; |
| 13326 | 13405 | |
| 13327 | 13406 | // In case the command ends up being a .bat/.cmd script, we need to escape things using the cmd.exe rules |
| 13328 | 13407 | // 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 | 13410 | // We'll need to wait until we're actually trying to run the command to know for sure |
| 13332 | 13411 | // if the resolved command has the `.bat` or `.cmd` extension, so we defer actually |
| 13333 | 13412 | // serializing the command line until we determine how it should be serialized. |
| 13334 | | var cmd_line_cache = WindowsCommandLineCache.init(child.allocator, child.argv); |
| 13335 | | defer cmd_line_cache.deinit(); |
| 13413 | var cmd_line_cache = WindowsCommandLineCache.init(arena, options.argv); |
| 13336 | 13414 | |
| 13337 | 13415 | var app_buf: std.ArrayList(u16) = .empty; |
| 13338 | | defer app_buf.deinit(child.allocator); |
| 13339 | | |
| 13340 | | try app_buf.appendSlice(child.allocator, app_name_w); |
| 13416 | try app_buf.appendSlice(arena, app_name_w); |
| 13341 | 13417 | |
| 13342 | 13418 | var dir_buf: std.ArrayList(u16) = .empty; |
| 13343 | | defer dir_buf.deinit(child.allocator); |
| 13344 | 13419 | |
| 13345 | 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 | 13423 | if (app_dirname_w) |app_dir| { |
| 13349 | | if (dir_buf.items.len > 0) try dir_buf.append(child.allocator, Dir.path.sep); |
| 13350 | | try dir_buf.appendSlice(child.allocator, app_dir); |
| 13351 | | } |
| 13352 | | |
| 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| { |
| 13424 | if (dir_buf.items.len > 0) try dir_buf.append(arena, Dir.path.sep); |
| 13425 | try dir_buf.appendSlice(arena, app_dir); |
| 13426 | } |
| 13427 | |
| 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 | 13441 | const original_err = switch (no_path_err) { |
| 13355 | 13442 | // argv[0] contains unsupported characters that will never resolve to a valid exe. |
| 13356 | 13443 | error.InvalidArg0 => return error.FileNotFound, |
| ... | ... | @@ -13362,7 +13449,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa |
| 13362 | 13449 | // If the app name had path separators, that disallows PATH searching, |
| 13363 | 13450 | // and there's no need to search the PATH if the app name is absolute. |
| 13364 | 13451 | // 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 path |
| 13452 | // "cwd provided by options is in effect when choosing the executable path |
| 13366 | 13453 | // to match posix semantics" behavior--we don't want to skip searching |
| 13367 | 13454 | // the PATH just because we were trying to set the cwd of the child process. |
| 13368 | 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 | 13459 | var it = std.mem.tokenizeScalar(u16, PATH, ';'); |
| 13373 | 13460 | while (it.next()) |search_path| { |
| 13374 | 13461 | dir_buf.clearRetainingCapacity(); |
| 13375 | | try dir_buf.appendSlice(child.allocator, search_path); |
| 13376 | | |
| 13377 | | if (windowsCreateProcessPathExt(child.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) { |
| 13462 | try dir_buf.appendSlice(arena, search_path); |
| 13463 | |
| 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 | 13477 | break :run; |
| 13379 | 13478 | } else |err| switch (err) { |
| 13380 | 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 | 13488 | }; |
| 13390 | 13489 | } |
| 13391 | 13490 | |
| 13392 | | if (g_hChildStd_IN_Wr) |h| { |
| 13393 | | child.stdin = File{ .handle = h }; |
| 13394 | | } else { |
| 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; |
| 13491 | if (options.stdin == .pipe) windows.CloseHandle(g_hChildStd_IN_Rd.?); |
| 13492 | if (options.stderr == .pipe) windows.CloseHandle(g_hChildStd_ERR_Wr.?); |
| 13493 | if (options.stdout == .pipe) windows.CloseHandle(g_hChildStd_OUT_Wr.?); |
| 13411 | 13494 | |
| 13412 | | if (child.stdin_behavior == .pipe) { |
| 13413 | | posix.close(g_hChildStd_IN_Rd.?); |
| 13414 | | } |
| 13415 | | if (child.stderr_behavior == .pipe) { |
| 13416 | | posix.close(g_hChildStd_ERR_Wr.?); |
| 13417 | | } |
| 13418 | | if (child.stdout_behavior == .pipe) { |
| 13419 | | posix.close(g_hChildStd_OUT_Wr.?); |
| 13420 | | } |
| 13495 | return .{ |
| 13496 | .id = piProcInfo.hProcess, |
| 13497 | .thread_handle = piProcInfo.hThread, |
| 13498 | .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h } else null, |
| 13499 | .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h } else null, |
| 13500 | .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h } else null, |
| 13501 | .request_resource_usage_statistics = options.request_resource_usage_statistics, |
| 13502 | }; |
| 13421 | 13503 | } |
| 13422 | 13504 | |
| 13423 | 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 | 13507 | /// Note: `app_buf` should not contain any leading path separators. |
| 13426 | 13508 | /// Note: If the dir is the cwd, dir_buf should be empty (len = 0). |
| 13427 | 13509 | fn windowsCreateProcessPathExt( |
| 13428 | | allocator: Allocator, |
| 13510 | arena: Allocator, |
| 13429 | 13511 | dir_buf: *std.ArrayList(u16), |
| 13430 | 13512 | app_buf: *std.ArrayList(u16), |
| 13431 | 13513 | pathext: [:0]const u16, |
| 13432 | 13514 | cmd_line_cache: *WindowsCommandLineCache, |
| 13433 | | envp_ptr: ?[*]u16, |
| 13515 | envp_ptr: ?[*:0]const u16, |
| 13434 | 13516 | cwd_ptr: ?[*:0]u16, |
| 13435 | 13517 | flags: windows.CreateProcessFlags, |
| 13436 | 13518 | lpStartupInfo: *windows.STARTUPINFOW, |
| ... | ... | @@ -13471,7 +13553,7 @@ fn windowsCreateProcessPathExt( |
| 13471 | 13553 | // that scenario. |
| 13472 | 13554 | var dir = dir: { |
| 13473 | 13555 | // needs to be null-terminated |
| 13474 | | try dir_buf.append(allocator, 0); |
| 13556 | try dir_buf.append(arena, 0); |
| 13475 | 13557 | defer dir_buf.shrinkRetainingCapacity(dir_path_len); |
| 13476 | 13558 | const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; |
| 13477 | 13559 | const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z); |
| ... | ... | @@ -13482,8 +13564,8 @@ fn windowsCreateProcessPathExt( |
| 13482 | 13564 | defer windows.CloseHandle(dir.handle); |
| 13483 | 13565 | |
| 13484 | 13566 | // Add wildcard and null-terminator |
| 13485 | | try app_buf.append(allocator, '*'); |
| 13486 | | try app_buf.append(allocator, 0); |
| 13567 | try app_buf.append(arena, '*'); |
| 13568 | try app_buf.append(arena, 0); |
| 13487 | 13569 | const app_name_wildcard = app_buf.items[0 .. app_buf.items.len - 1 :0]; |
| 13488 | 13570 | |
| 13489 | 13571 | // 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 | 13645 | if (unappended_exists) { |
| 13564 | 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]); |
| 13569 | | try dir_buf.append(allocator, 0); |
| 13650 | try dir_buf.appendSlice(arena, app_buf.items[0..app_name_len]); |
| 13651 | try dir_buf.append(arena, 0); |
| 13570 | 13652 | const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; |
| 13571 | 13653 | |
| 13572 | 13654 | const is_bat_or_cmd = bat_or_cmd: { |
| ... | ... | @@ -13588,7 +13670,15 @@ fn windowsCreateProcessPathExt( |
| 13588 | 13670 | else |
| 13589 | 13671 | full_app_name; |
| 13590 | 13672 | |
| 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 | 13682 | return; |
| 13593 | 13683 | } else |err| switch (err) { |
| 13594 | 13684 | error.FileNotFound, |
| ... | ... | @@ -13623,11 +13713,11 @@ fn windowsCreateProcessPathExt( |
| 13623 | 13713 | dir_buf.shrinkRetainingCapacity(dir_path_len); |
| 13624 | 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]); |
| 13629 | | try dir_buf.appendSlice(allocator, ext); |
| 13630 | | try dir_buf.append(allocator, 0); |
| 13718 | try dir_buf.appendSlice(arena, app_buf.items[0..app_name_len]); |
| 13719 | try dir_buf.appendSlice(arena, ext); |
| 13720 | try dir_buf.append(arena, 0); |
| 13631 | 13721 | const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; |
| 13632 | 13722 | |
| 13633 | 13723 | const is_bat_or_cmd = switch (ext_enum) { |
| ... | ... | @@ -13667,41 +13757,61 @@ fn windowsCreateProcessPathExt( |
| 13667 | 13757 | fn windowsCreateProcess( |
| 13668 | 13758 | app_name: [*:0]u16, |
| 13669 | 13759 | cmd_line: [*:0]u16, |
| 13670 | | envp_ptr: ?[*]u16, |
| 13760 | env_ptr: ?[*:0]const u16, |
| 13671 | 13761 | cwd_ptr: ?[*:0]u16, |
| 13672 | 13762 | flags: windows.CreateProcessFlags, |
| 13673 | 13763 | lpStartupInfo: *windows.STARTUPINFOW, |
| 13674 | 13764 | lpProcessInformation: *windows.PROCESS_INFORMATION, |
| 13675 | 13765 | ) !void { |
| 13676 | | // TODO the docs for environment pointer say: |
| 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( |
| 13766 | if (windows.kernel32.CreateProcessW( |
| 13694 | 13767 | app_name, |
| 13695 | 13768 | cmd_line, |
| 13696 | 13769 | null, |
| 13697 | 13770 | null, |
| 13698 | 13771 | windows.TRUE, |
| 13699 | 13772 | flags, |
| 13700 | | @as(?*anyopaque, @ptrCast(envp_ptr)), |
| 13773 | env_ptr, |
| 13701 | 13774 | cwd_ptr, |
| 13702 | 13775 | lpStartupInfo, |
| 13703 | 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 | } |
| 13706 | 13816 | |
| 13707 | 13817 | /// Case-insensitive WTF-16 lookup |