Skip to content

Commit f2e490f

Browse files
fix(procmgr): resolve PATHEXT executables case-insensitively on Windows
PATHEXT defaults use uppercase extensions while created files often use lowercase; match existing files via read_dir when Path::is_file fails.
1 parent c26d96f commit f2e490f

1 file changed

Lines changed: 27 additions & 3 deletions

File tree

pkg/procmgr/rust/src/platform/windows/resolve_executable.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub(crate) fn resolve_executable_in_env(
2626
}
2727

2828
if contains_path_separator(command) {
29-
if Path::new(command).is_file() {
29+
if path_is_existing_file(Path::new(command)) {
3030
return Ok(command.to_string());
3131
}
3232
bail!("secretBackendCommand '{command}' does not exist");
@@ -46,7 +46,7 @@ pub(crate) fn resolve_executable_in_env(
4646
for ext in &extensions {
4747
let candidate_name = format!("{command}{ext}");
4848
let candidate = dir.join(&candidate_name);
49-
if candidate.is_file() {
49+
if path_is_existing_file(&candidate) {
5050
return Ok(candidate.to_string_lossy().into_owned());
5151
}
5252
}
@@ -55,6 +55,30 @@ pub(crate) fn resolve_executable_in_env(
5555
bail!("secretBackendCommand '{command}' does not exist");
5656
}
5757

58+
fn path_is_existing_file(path: &Path) -> bool {
59+
if path.is_file() {
60+
return true;
61+
}
62+
#[cfg(windows)]
63+
{
64+
let Some(name) = path.file_name() else {
65+
return false;
66+
};
67+
let Some(parent) = path.parent() else {
68+
return false;
69+
};
70+
let Ok(entries) = std::fs::read_dir(parent) else {
71+
return false;
72+
};
73+
return entries.flatten().any(|entry| {
74+
entry.file_name().eq_ignore_ascii_case(name)
75+
&& entry.file_type().is_ok_and(|t| t.is_file())
76+
});
77+
}
78+
#[cfg(not(windows))]
79+
false
80+
}
81+
5882
fn contains_path_separator(command: &str) -> bool {
5983
command.contains('\\') || command.contains('/')
6084
}
@@ -100,7 +124,7 @@ fn pathext_extensions(env: &HashMap<String, String>) -> Vec<String> {
100124
fn find_on_path(command: &str, path_dirs: &[PathBuf]) -> Option<String> {
101125
for dir in path_dirs {
102126
let candidate = dir.join(command);
103-
if candidate.is_file() {
127+
if path_is_existing_file(&candidate) {
104128
return Some(candidate.to_string_lossy().into_owned());
105129
}
106130
}

0 commit comments

Comments
 (0)