Skip to content

Commit 8d7b364

Browse files
authored
Rollup merge of rust-lang#92517 - ChrisDenton:explicit-path, r=dtolnay
Explicitly pass `PATH` to the Windows exe resolver This allows for testing different `PATH`s without using the actual environment.
2 parents 9debf22 + 4145877 commit 8d7b364

File tree

2 files changed

+37
-33
lines changed

2 files changed

+37
-33
lines changed

library/std/src/sys/windows/process.rs

+20-11
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ impl Command {
268268
} else {
269269
None
270270
};
271-
let program = resolve_exe(&self.program, child_paths)?;
271+
let program = resolve_exe(&self.program, || env::var_os("PATH"), child_paths)?;
272272
let mut cmd_str =
273273
make_command_line(program.as_os_str(), &self.args, self.force_quotes_enabled)?;
274274
cmd_str.push(0); // add null terminator
@@ -362,7 +362,11 @@ impl fmt::Debug for Command {
362362
// Therefore this functions first assumes `.exe` was intended.
363363
// It falls back to the plain file name if a full path is given and the extension is omitted
364364
// or if only a file name is given and it already contains an extension.
365-
fn resolve_exe<'a>(exe_path: &'a OsStr, child_paths: Option<&OsStr>) -> io::Result<PathBuf> {
365+
fn resolve_exe<'a>(
366+
exe_path: &'a OsStr,
367+
parent_paths: impl FnOnce() -> Option<OsString>,
368+
child_paths: Option<&OsStr>,
369+
) -> io::Result<PathBuf> {
366370
// Early return if there is no filename.
367371
if exe_path.is_empty() || path::has_trailing_slash(exe_path) {
368372
return Err(io::Error::new_const(
@@ -406,7 +410,7 @@ fn resolve_exe<'a>(exe_path: &'a OsStr, child_paths: Option<&OsStr>) -> io::Resu
406410
let has_extension = exe_path.bytes().contains(&b'.');
407411

408412
// Search the directories given by `search_paths`.
409-
let result = search_paths(child_paths, |mut path| {
413+
let result = search_paths(parent_paths, child_paths, |mut path| {
410414
path.push(&exe_path);
411415
if !has_extension {
412416
path.set_extension(EXE_EXTENSION);
@@ -423,15 +427,20 @@ fn resolve_exe<'a>(exe_path: &'a OsStr, child_paths: Option<&OsStr>) -> io::Resu
423427

424428
// Calls `f` for every path that should be used to find an executable.
425429
// Returns once `f` returns the path to an executable or all paths have been searched.
426-
fn search_paths<F>(child_paths: Option<&OsStr>, mut f: F) -> Option<PathBuf>
430+
fn search_paths<Paths, Exists>(
431+
parent_paths: Paths,
432+
child_paths: Option<&OsStr>,
433+
mut exists: Exists,
434+
) -> Option<PathBuf>
427435
where
428-
F: FnMut(PathBuf) -> Option<PathBuf>,
436+
Paths: FnOnce() -> Option<OsString>,
437+
Exists: FnMut(PathBuf) -> Option<PathBuf>,
429438
{
430439
// 1. Child paths
431440
// This is for consistency with Rust's historic behaviour.
432441
if let Some(paths) = child_paths {
433442
for path in env::split_paths(paths).filter(|p| !p.as_os_str().is_empty()) {
434-
if let Some(path) = f(path) {
443+
if let Some(path) = exists(path) {
435444
return Some(path);
436445
}
437446
}
@@ -440,7 +449,7 @@ where
440449
// 2. Application path
441450
if let Ok(mut app_path) = env::current_exe() {
442451
app_path.pop();
443-
if let Some(path) = f(app_path) {
452+
if let Some(path) = exists(app_path) {
444453
return Some(path);
445454
}
446455
}
@@ -450,25 +459,25 @@ where
450459
unsafe {
451460
if let Ok(Some(path)) = super::fill_utf16_buf(
452461
|buf, size| c::GetSystemDirectoryW(buf, size),
453-
|buf| f(PathBuf::from(OsString::from_wide(buf))),
462+
|buf| exists(PathBuf::from(OsString::from_wide(buf))),
454463
) {
455464
return Some(path);
456465
}
457466
#[cfg(not(target_vendor = "uwp"))]
458467
{
459468
if let Ok(Some(path)) = super::fill_utf16_buf(
460469
|buf, size| c::GetWindowsDirectoryW(buf, size),
461-
|buf| f(PathBuf::from(OsString::from_wide(buf))),
470+
|buf| exists(PathBuf::from(OsString::from_wide(buf))),
462471
) {
463472
return Some(path);
464473
}
465474
}
466475
}
467476

468477
// 5. Parent paths
469-
if let Some(parent_paths) = env::var_os("PATH") {
478+
if let Some(parent_paths) = parent_paths() {
470479
for path in env::split_paths(&parent_paths).filter(|p| !p.as_os_str().is_empty()) {
471-
if let Some(path) = f(path) {
480+
if let Some(path) = exists(path) {
472481
return Some(path);
473482
}
474483
}

library/std/src/sys/windows/process/tests.rs

+17-22
Original file line numberDiff line numberDiff line change
@@ -136,51 +136,46 @@ fn windows_exe_resolver() {
136136
use super::resolve_exe;
137137
use crate::io;
138138

139+
let env_paths = || env::var_os("PATH");
140+
139141
// Test a full path, with and without the `exe` extension.
140142
let mut current_exe = env::current_exe().unwrap();
141-
assert!(resolve_exe(current_exe.as_ref(), None).is_ok());
143+
assert!(resolve_exe(current_exe.as_ref(), env_paths, None).is_ok());
142144
current_exe.set_extension("");
143-
assert!(resolve_exe(current_exe.as_ref(), None).is_ok());
145+
assert!(resolve_exe(current_exe.as_ref(), env_paths, None).is_ok());
144146

145147
// Test lone file names.
146-
assert!(resolve_exe(OsStr::new("cmd"), None).is_ok());
147-
assert!(resolve_exe(OsStr::new("cmd.exe"), None).is_ok());
148-
assert!(resolve_exe(OsStr::new("cmd.EXE"), None).is_ok());
149-
assert!(resolve_exe(OsStr::new("fc"), None).is_ok());
148+
assert!(resolve_exe(OsStr::new("cmd"), env_paths, None).is_ok());
149+
assert!(resolve_exe(OsStr::new("cmd.exe"), env_paths, None).is_ok());
150+
assert!(resolve_exe(OsStr::new("cmd.EXE"), env_paths, None).is_ok());
151+
assert!(resolve_exe(OsStr::new("fc"), env_paths, None).is_ok());
150152

151153
// Invalid file names should return InvalidInput.
152-
assert_eq!(resolve_exe(OsStr::new(""), None).unwrap_err().kind(), io::ErrorKind::InvalidInput);
153154
assert_eq!(
154-
resolve_exe(OsStr::new("\0"), None).unwrap_err().kind(),
155+
resolve_exe(OsStr::new(""), env_paths, None).unwrap_err().kind(),
156+
io::ErrorKind::InvalidInput
157+
);
158+
assert_eq!(
159+
resolve_exe(OsStr::new("\0"), env_paths, None).unwrap_err().kind(),
155160
io::ErrorKind::InvalidInput
156161
);
157162
// Trailing slash, therefore there's no file name component.
158163
assert_eq!(
159-
resolve_exe(OsStr::new(r"C:\Path\to\"), None).unwrap_err().kind(),
164+
resolve_exe(OsStr::new(r"C:\Path\to\"), env_paths, None).unwrap_err().kind(),
160165
io::ErrorKind::InvalidInput
161166
);
162167

163-
/* FIXME: fix and re-enable these tests before making changes to the resolver.
164-
165168
/*
166169
Some of the following tests may need to be changed if you are deliberately
167170
changing the behaviour of `resolve_exe`.
168171
*/
169172

170-
let paths = env::var_os("PATH").unwrap();
171-
env::set_var("PATH", "");
172-
173-
assert_eq!(resolve_exe(OsStr::new("rustc"), None).unwrap_err().kind(), io::ErrorKind::NotFound);
174-
175-
let child_paths = Some(paths.as_os_str());
176-
assert!(resolve_exe(OsStr::new("rustc"), child_paths).is_ok());
173+
let empty_paths = || None;
177174

178175
// The resolver looks in system directories even when `PATH` is empty.
179-
assert!(resolve_exe(OsStr::new("cmd.exe"), None).is_ok());
176+
assert!(resolve_exe(OsStr::new("cmd.exe"), empty_paths, None).is_ok());
180177

181178
// The application's directory is also searched.
182179
let current_exe = env::current_exe().unwrap();
183-
assert!(resolve_exe(current_exe.file_name().unwrap().as_ref(), None).is_ok());
184-
185-
*/
180+
assert!(resolve_exe(current_exe.file_name().unwrap().as_ref(), empty_paths, None).is_ok());
186181
}

0 commit comments

Comments
 (0)