diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index b706f0aecb..956ee2adaf 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -965,56 +965,93 @@ pub const ChildProcess = struct { const cmd_line_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, cmd_line); defer self.allocator.free(cmd_line_w); - windowsCreateProcess(app_path_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| { - if (no_path_err != error.FileNotFound) return no_path_err; + exec: { + windowsCreateProcess(app_path_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| { + switch (no_path_err) { + error.FileNotFound, error.InvalidExe => {}, + else => |e| return e, + } - var free_path = true; - const PATH = process.getEnvVarOwned(self.allocator, "PATH") catch |err| switch (err) { - error.EnvironmentVariableNotFound => blk: { - free_path = false; - break :blk ""; - }, - else => |e| return e, - }; - defer if (free_path) self.allocator.free(PATH); + const PATH: [:0]const u16 = std.os.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse &[_:0]u16{}; + const PATHEXT: [:0]const u16 = std.os.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATHEXT")) orelse &[_:0]u16{}; - var free_path_ext = true; - const PATHEXT = process.getEnvVarOwned(self.allocator, "PATHEXT") catch |err| switch (err) { - error.EnvironmentVariableNotFound => blk: { - free_path_ext = false; - break :blk ""; - }, - else => |e| return e, - }; - defer if (free_path_ext) self.allocator.free(PATHEXT); + var path_buf = std.ArrayListUnmanaged(u16){}; + defer path_buf.deinit(self.allocator); - const app_name = self.argv[0]; + // Try again with PATHEXT's extensions appended + { + try path_buf.appendSlice(self.allocator, app_path_w); + var ext_it = mem.tokenize(u16, PATHEXT, &[_]u16{';'}); + while (ext_it.next()) |ext| { + path_buf.shrinkRetainingCapacity(app_path_w.len); + try path_buf.appendSlice(self.allocator, ext); + try path_buf.append(self.allocator, 0); + const path_with_ext = path_buf.items[0 .. path_buf.items.len - 1 :0]; - var it = mem.tokenize(u8, PATH, ";"); - retry: while (it.next()) |search_path| { - const path_no_ext = try fs.path.join(self.allocator, &[_][]const u8{ search_path, app_name }); - defer self.allocator.free(path_no_ext); - - var ext_it = mem.tokenize(u8, PATHEXT, ";"); - while (ext_it.next()) |app_ext| { - const joined_path = try mem.concat(self.allocator, u8, &[_][]const u8{ path_no_ext, app_ext }); - defer self.allocator.free(joined_path); - - const joined_path_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, joined_path); - defer self.allocator.free(joined_path_w); - - if (windowsCreateProcess(joined_path_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) |_| { - break :retry; - } else |err| switch (err) { - error.FileNotFound => continue, - error.AccessDenied => continue, - else => return err, + if (windowsCreateProcess(path_with_ext.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) |_| { + break :exec; + } else |err| switch (err) { + error.FileNotFound, error.AccessDenied, error.InvalidExe => {}, + else => return err, + } } } - } else { - return no_path_err; // return the original error - } - }; + + // No need to search the PATH if the app path is absolute + if (fs.path.isAbsoluteWindowsWTF16(app_path_w)) return no_path_err; + + // app_path_w has the cwd prepended to it if cwd is non-null, so when + // searching the PATH we should make sure we use the app_name verbatim. + var app_name_w_needs_free = false; + const app_name_w = x: { + if (self.cwd) |_| { + app_name_w_needs_free = true; + break :x try unicode.utf8ToUtf16LeWithNull(self.allocator, self.argv[0]); + } else { + break :x app_path_w; + } + }; + defer if (app_name_w_needs_free) self.allocator.free(app_name_w); + + var it = mem.tokenize(u16, PATH, &[_]u16{';'}); + while (it.next()) |search_path| { + path_buf.clearRetainingCapacity(); + const search_path_trimmed = mem.trimRight(u16, search_path, &[_]u16{ '\\', '/' }); + try path_buf.appendSlice(self.allocator, search_path_trimmed); + try path_buf.append(self.allocator, fs.path.sep); + const app_name_trimmed = mem.trimLeft(u16, app_name_w, &[_]u16{ '\\', '/' }); + try path_buf.appendSlice(self.allocator, app_name_trimmed); + try path_buf.append(self.allocator, 0); + const path_no_ext = path_buf.items[0 .. path_buf.items.len - 1 :0]; + + if (windowsCreateProcess(path_no_ext.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) |_| { + break :exec; + } else |err| switch (err) { + error.FileNotFound, error.AccessDenied, error.InvalidExe => {}, + else => return err, + } + + var ext_it = mem.tokenize(u16, PATHEXT, &[_]u16{';'}); + while (ext_it.next()) |ext| { + path_buf.shrinkRetainingCapacity(path_no_ext.len); + try path_buf.appendSlice(self.allocator, ext); + try path_buf.append(self.allocator, 0); + const joined_path = path_buf.items[0 .. path_buf.items.len - 1 :0]; + + if (windowsCreateProcess(joined_path.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) |_| { + break :exec; + } else |err| switch (err) { + error.FileNotFound => continue, + error.AccessDenied => continue, + error.InvalidExe => continue, + else => return err, + } + } + } else { + return no_path_err; // return the original error + } + }; + } if (g_hChildStd_IN_Wr) |h| { self.stdin = File{ .handle = h }; diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 0bd695a029..d654a60bd9 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -1569,6 +1569,7 @@ pub const CreateProcessError = error{ AccessDenied, InvalidName, NameTooLong, + InvalidExe, Unexpected, }; @@ -1603,6 +1604,27 @@ pub fn CreateProcessW( .INVALID_PARAMETER => unreachable, .INVALID_NAME => return error.InvalidName, .FILENAME_EXCED_RANGE => return error.NameTooLong, + // These are all the system errors that are mapped to ENOEXEC by + // the undocumented _dosmaperr (old CRT) or __acrt_errno_map_os_error + // (newer CRT) functions. Their code can be found in crt/src/dosmap.c (old SDK) + // or urt/misc/errno.cpp (newer SDK) in the Windows SDK. + .BAD_FORMAT, + .INVALID_STARTING_CODESEG, // MIN_EXEC_ERROR in errno.cpp + .INVALID_STACKSEG, + .INVALID_MODULETYPE, + .INVALID_EXE_SIGNATURE, + .EXE_MARKED_INVALID, + .BAD_EXE_FORMAT, + .ITERATED_DATA_EXCEEDS_64k, + .INVALID_MINALLOCSIZE, + .DYNLINK_FROM_INVALID_RING, + .IOPL_NOT_ENABLED, + .INVALID_SEGDPL, + .AUTODATASEG_EXCEEDS_64k, + .RING2SEG_MUST_BE_MOVABLE, + .RELOC_CHAIN_XEEDS_SEGLIM, + .INFLOOP_IN_RELOC_CHAIN, // MAX_EXEC_ERROR in errno.cpp + => return error.InvalidExe, else => |err| return unexpectedError(err), } }