Skip to content

Commit d324240

Browse files
committed
spawnWindows: If an exe is found but fails to exec, retry with PATHEXT values appended
This matches `cmd.exe` behavior. For example, if there is only a file named `mycommand` in the cwd but it is a Linux executable, then running the command `mycommand` will result in: 'mycommand' is not recognized as an internal or external command, operable program or batch file. However, if there is *both* a `mycommand` (that is a Linux executable) and a `mycommand.exe` that is a valid Windows exe, then running the command `mycommand` will successfully run `mycommand.exe`.
1 parent 5843b79 commit d324240

File tree

1 file changed

+76
-42
lines changed

1 file changed

+76
-42
lines changed

lib/std/child_process.zig

Lines changed: 76 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -965,56 +965,90 @@ pub const ChildProcess = struct {
965965
const cmd_line_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, cmd_line);
966966
defer self.allocator.free(cmd_line_w);
967967

968-
windowsCreateProcess(app_path_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| {
969-
if (no_path_err != error.FileNotFound and no_path_err != error.InvalidExe) return no_path_err;
970-
971-
const PATH: [:0]const u16 = std.os.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse &[_:0]u16{};
972-
const PATHEXT: [:0]const u16 = std.os.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATHEXT")) orelse &[_:0]u16{};
973-
974-
var path_buf = std.ArrayListUnmanaged(u16){};
975-
defer path_buf.deinit(self.allocator);
976-
977-
const app_name = self.argv[0];
978-
const app_name_trimmed_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, mem.trimLeft(u8, app_name, "\\/"));
979-
defer self.allocator.free(app_name_trimmed_w);
980-
981-
var it = mem.tokenize(u16, PATH, &[_]u16{';'});
982-
retry: while (it.next()) |search_path| {
983-
path_buf.clearRetainingCapacity();
984-
const search_path_trimmed = mem.trimRight(u16, search_path, &[_]u16{ '\\', '/' });
985-
try path_buf.appendSlice(self.allocator, search_path_trimmed);
986-
try path_buf.append(self.allocator, fs.path.sep);
987-
try path_buf.appendSlice(self.allocator, app_name_trimmed_w);
988-
try path_buf.append(self.allocator, 0);
989-
const path_no_ext = path_buf.items[0 .. path_buf.items.len - 1 :0];
990-
991-
if (windowsCreateProcess(path_no_ext.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) |_| {
992-
break :retry;
993-
} else |err| switch (err) {
994-
error.FileNotFound, error.AccessDenied, error.InvalidExe => {},
995-
else => return err,
968+
exec: {
969+
windowsCreateProcess(app_path_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| {
970+
switch (no_path_err) {
971+
error.FileNotFound, error.InvalidExe => {},
972+
else => |e| return e,
996973
}
997974

998-
var ext_it = mem.tokenize(u16, PATHEXT, &[_]u16{';'});
999-
while (ext_it.next()) |ext| {
1000-
path_buf.shrinkRetainingCapacity(path_no_ext.len);
1001-
try path_buf.appendSlice(self.allocator, ext);
975+
const PATH: [:0]const u16 = std.os.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse &[_:0]u16{};
976+
const PATHEXT: [:0]const u16 = std.os.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATHEXT")) orelse &[_:0]u16{};
977+
978+
var path_buf = std.ArrayListUnmanaged(u16){};
979+
defer path_buf.deinit(self.allocator);
980+
981+
// Try again with PATHEXT's extensions appended
982+
{
983+
try path_buf.appendSlice(self.allocator, app_path_w);
984+
var ext_it = mem.tokenize(u16, PATHEXT, &[_]u16{';'});
985+
while (ext_it.next()) |ext| {
986+
path_buf.shrinkRetainingCapacity(app_path_w.len);
987+
try path_buf.appendSlice(self.allocator, ext);
988+
try path_buf.append(self.allocator, 0);
989+
const path_with_ext = path_buf.items[0 .. path_buf.items.len - 1 :0];
990+
991+
if (windowsCreateProcess(path_with_ext.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) |_| {
992+
break :exec;
993+
} else |err| switch (err) {
994+
error.FileNotFound, error.AccessDenied, error.InvalidExe => {},
995+
else => return err,
996+
}
997+
}
998+
}
999+
1000+
// app_path_w has the cwd prepended to it if cwd is non-null, so when
1001+
// searching the PATH we should make sure we use the app_name verbatim.
1002+
var app_name_w_needs_free = false;
1003+
const app_name_w = x: {
1004+
if (self.cwd) |_| {
1005+
app_name_w_needs_free = true;
1006+
break :x try unicode.utf8ToUtf16LeWithNull(self.allocator, self.argv[0]);
1007+
} else {
1008+
break :x app_path_w;
1009+
}
1010+
};
1011+
defer if (app_name_w_needs_free) self.allocator.free(app_name_w);
1012+
1013+
var it = mem.tokenize(u16, PATH, &[_]u16{';'});
1014+
while (it.next()) |search_path| {
1015+
path_buf.clearRetainingCapacity();
1016+
const search_path_trimmed = mem.trimRight(u16, search_path, &[_]u16{ '\\', '/' });
1017+
try path_buf.appendSlice(self.allocator, search_path_trimmed);
1018+
try path_buf.append(self.allocator, fs.path.sep);
1019+
const app_name_trimmed = mem.trimLeft(u16, app_name_w, &[_]u16{ '\\', '/' });
1020+
try path_buf.appendSlice(self.allocator, app_name_trimmed);
10021021
try path_buf.append(self.allocator, 0);
1003-
const joined_path = path_buf.items[0 .. path_buf.items.len - 1 :0];
1022+
const path_no_ext = path_buf.items[0 .. path_buf.items.len - 1 :0];
10041023

1005-
if (windowsCreateProcess(joined_path.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) |_| {
1006-
break :retry;
1024+
if (windowsCreateProcess(path_no_ext.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) |_| {
1025+
break :exec;
10071026
} else |err| switch (err) {
1008-
error.FileNotFound => continue,
1009-
error.AccessDenied => continue,
1010-
error.InvalidExe => continue,
1027+
error.FileNotFound, error.AccessDenied, error.InvalidExe => {},
10111028
else => return err,
10121029
}
1030+
1031+
var ext_it = mem.tokenize(u16, PATHEXT, &[_]u16{';'});
1032+
while (ext_it.next()) |ext| {
1033+
path_buf.shrinkRetainingCapacity(path_no_ext.len);
1034+
try path_buf.appendSlice(self.allocator, ext);
1035+
try path_buf.append(self.allocator, 0);
1036+
const joined_path = path_buf.items[0 .. path_buf.items.len - 1 :0];
1037+
1038+
if (windowsCreateProcess(joined_path.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) |_| {
1039+
break :exec;
1040+
} else |err| switch (err) {
1041+
error.FileNotFound => continue,
1042+
error.AccessDenied => continue,
1043+
error.InvalidExe => continue,
1044+
else => return err,
1045+
}
1046+
}
1047+
} else {
1048+
return no_path_err; // return the original error
10131049
}
1014-
} else {
1015-
return no_path_err; // return the original error
1016-
}
1017-
};
1050+
};
1051+
}
10181052

10191053
if (g_hChildStd_IN_Wr) |h| {
10201054
self.stdin = File{ .handle = h };

0 commit comments

Comments
 (0)