Skip to content

Commit

Permalink
Merge pull request #13983 from squeek502/windows-childprocess-exec-retry
Browse files Browse the repository at this point in the history
`ChildProcess.spawnWindows`: `PATH` search fixes + optimizations
  • Loading branch information
andrewrk authored Dec 17, 2022
2 parents 90477e5 + 9e8ac2b commit 11b5747
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 44 deletions.
125 changes: 81 additions & 44 deletions lib/std/child_process.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;

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);

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);

const app_name = self.argv[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);
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,
}

const joined_path_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, joined_path);
defer self.allocator.free(joined_path_w);
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 path_buf = std.ArrayListUnmanaged(u16){};
defer path_buf.deinit(self.allocator);

// 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];

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,
}
}
}

if (windowsCreateProcess(joined_path_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) |_| {
break :retry;
// 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 => continue,
error.AccessDenied => continue,
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
}
} else {
return no_path_err; // return the original error
}
};
};
}

if (g_hChildStd_IN_Wr) |h| {
self.stdin = File{ .handle = h };
Expand Down
22 changes: 22 additions & 0 deletions lib/std/os/windows.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1569,6 +1569,7 @@ pub const CreateProcessError = error{
AccessDenied,
InvalidName,
NameTooLong,
InvalidExe,
Unexpected,
};

Expand Down Expand Up @@ -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),
}
}
Expand Down

0 comments on commit 11b5747

Please sign in to comment.