| ... | @@ -5654,7 +5654,7 @@ fn dirSymLinkWindows( | ... | @@ -5654,7 +5654,7 @@ fn dirSymLinkWindows( |
| 5654 | // Already an NT path, no need to do anything to it | 5654 | // 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 wToPrefixedFileW | 5658 | // 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 to | 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,6 +12617,45 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void { |
| 12617 | | 12617 | |
| 12618 | fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {} | 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 | fn scanEnviron(t: *Threaded) void { | 12659 | fn 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; |
| 12626 | | 12665 | |
| 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; |
| 12629 | | 12671 | |
| 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 | }; |
| 12780 | | 12822 | |
| 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 |
| 12962 | fn childKill(userdata: ?*anyopaque, child: *std.process.Child) void { | 13005 | fn 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 | } |
| 12974 | | 13013 | |
| 12975 | fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT) !void { | 13014 | fn 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, it | 13017 | if (windows.kernel32.TerminateProcess(handle, exit_code) == 0) { |
| 12979 | // indicates that the process has already exited, but there may be | 13018 | switch (windows.GetLastError()) { |
| 12980 | // some rare edge cases where our process handle no longer has the | 13019 | .ACCESS_DENIED => { |
| 12981 | // PROCESS_TERMINATE access right, so let's do another check to make | 13020 | // 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 | } |
| 12990 | | 13034 | |
| 12991 | fn childWaitWindows(t: *Threaded, child: *process.Child) process.Child.WaitError!process.Child.Term { | 13035 | fn childWaitWindows(t: *Threaded, child: *process.Child) process.Child.WaitError!process.Child.Term { |
| 12992 | _ = t; // TODO cancelation | 13036 | 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 | } |
| 12994 | | 13050 | |
| 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 | }; |
| 13003 | | 13059 | |
| 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 | } |
| 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 | fn childWaitPosix(t: *Threaded, child: *process.Child) process.Child.WaitError!process.Child.Term { | 13090 | fn childWaitPosix(t: *Threaded, child: *process.Child) process.Child.WaitError!process.Child.Term { |
| 13015 | _ = t; // TODO cancelation | 13091 | _ = 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 | } |
| 13029 | | 13105 | |
| ... | @@ -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 | } |
| 13052 | | 13128 | |
| 13053 | fn childCleanupStreams(child: *process.Child) void { | 13129 | fn 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 | } |
| 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 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 13220 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 13145 | _ = t; | | |
| 13146 | | 13221 | |
| 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 | }; |
| 13152 | | 13227 | |
| 13153 | const any_ignore = | 13228 | const any_ignore = |
| 13154 | child.stdin_behavior == .ignore or | 13229 | options.stdin == .ignore or |
| 13155 | child.stdout_behavior == .ignore or | 13230 | options.stdout == .ignore or |
| 13156 | child.stderr_behavior == .ignore; | 13231 | options.stderr == .ignore; |
| 13157 | | 13232 | |
| | 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 |
| 13185 | | 13261 | |
| 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 | }; |
| 13205 | | 13282 | |
| 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 | }; |
| 13225 | | 13303 | |
| 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 | }; |
| 13245 | | 13324 | |
| 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; |
| 13268 | | 13347 | |
| 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; |
| 13272 | | 13354 | |
| 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; |
| 13276 | | 13357 | |
| 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); |
| 13279 | | 13360 | |
| 13280 | // the cwd set in Child is in effect when choosing the executable path | 13361 | // The cwd provided by options is in effect when choosing the executable |
| 13281 | // to match posix semantics | 13362 | // 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 cwd | 13365 | // 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 cwd | 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 | 13378 | // 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 cwd | 13379 | // that into the basename and dirname and use the dirname as an addition to |
| 13300 | // path. This is because NtQueryDirectoryFile cannot accept FileName params with | 13380 | // 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); | | |
| 13316 | | 13393 | |
| 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 | }; |
| 13322 | | 13399 | |
| 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{}; |
| 13326 | | 13405 | |
| 13327 | // In case the command ends up being a .bat/.cmd script, we need to escape things using the cmd.exe rules | 13406 | // 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 maliciously | 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,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 sure | 13410 | // 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 actually | 13411 | // 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(); | | |
| 13336 | | 13414 | |
| 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); | | |
| 13341 | | 13417 | |
| 13342 | var dir_buf: std.ArrayList(u16) = .empty; | 13418 | var dir_buf: std.ArrayList(u16) = .empty; |
| 13343 | defer dir_buf.deinit(child.allocator); | | |
| 13344 | | 13419 | |
| 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 | } |
| 13352 | | 13427 | |
| 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 the | 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 | // to match posix semantics" behavior--we don't want to skip searching | 13453 | // 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); |
| 13376 | | 13463 | |
| 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 | } |
| 13391 | | 13490 | |
| 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; | | |
| 13411 | | 13494 | |
| 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 | } |
| 13422 | | 13504 | |
| 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). |
| 13427 | fn windowsCreateProcessPathExt( | 13509 | fn 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-terminated | 13555 | // 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); |
| 13483 | | 13565 | |
| 13484 | // Add wildcard and null-terminator | 13566 | // 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]; |
| 13488 | | 13570 | |
| 13489 | // This 2048 is arbitrary, we just want it to be large enough to get multiple FILE_DIRECTORY_INFORMATION entries | 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,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]; |
| 13571 | | 13653 | |
| 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 | else | 13670 | else |
| 13589 | full_app_name; | 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 | 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]; |
| 13632 | | 13722 | |
| 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( |
| 13667 | fn windowsCreateProcess( | 13757 | fn 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 | } |
| 13706 | | 13816 | |
| 13707 | /// Case-insensitive WTF-16 lookup | 13817 | /// Case-insensitive WTF-16 lookup |