Skip to content

Commit 9a7a8b9

Browse files
committed
Maintain broken symlink behaviour for the Windows exe resolver
1 parent 902e590 commit 9a7a8b9

File tree

3 files changed

+30
-2
lines changed

3 files changed

+30
-2
lines changed

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

+2
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ pub const CSTR_GREATER_THAN: c_int = 3;
8383
pub const FILE_ATTRIBUTE_READONLY: DWORD = 0x1;
8484
pub const FILE_ATTRIBUTE_DIRECTORY: DWORD = 0x10;
8585
pub const FILE_ATTRIBUTE_REPARSE_POINT: DWORD = 0x400;
86+
pub const INVALID_FILE_ATTRIBUTES: DWORD = DWORD::MAX;
8687

8788
pub const FILE_SHARE_DELETE: DWORD = 0x4;
8889
pub const FILE_SHARE_READ: DWORD = 0x1;
@@ -1075,6 +1076,7 @@ extern "system" {
10751076
lpBuffer: LPWSTR,
10761077
lpFilePart: *mut LPWSTR,
10771078
) -> DWORD;
1079+
pub fn GetFileAttributesW(lpFileName: LPCWSTR) -> DWORD;
10781080
}
10791081

10801082
#[link(name = "ws2_32")]

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

+17-2
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ fn resolve_exe<'a>(
394394

395395
// Append `.exe` if not already there.
396396
path = path::append_suffix(path, EXE_SUFFIX.as_ref());
397-
if path.try_exists().unwrap_or(false) {
397+
if program_exists(&path) {
398398
return Ok(path);
399399
} else {
400400
// It's ok to use `set_extension` here because the intent is to
@@ -415,7 +415,7 @@ fn resolve_exe<'a>(
415415
if !has_extension {
416416
path.set_extension(EXE_EXTENSION);
417417
}
418-
if let Ok(true) = path.try_exists() { Some(path) } else { None }
418+
if program_exists(&path) { Some(path) } else { None }
419419
});
420420
if let Some(path) = result {
421421
return Ok(path);
@@ -485,6 +485,21 @@ where
485485
None
486486
}
487487

488+
/// Check if a file exists without following symlinks.
489+
fn program_exists(path: &Path) -> bool {
490+
unsafe {
491+
to_u16s(path)
492+
.map(|path| {
493+
// Getting attributes using `GetFileAttributesW` does not follow symlinks
494+
// and it will almost always be successful if the link exists.
495+
// There are some exceptions for special system files (e.g. the pagefile)
496+
// but these are not executable.
497+
c::GetFileAttributesW(path.as_ptr()) != c::INVALID_FILE_ATTRIBUTES
498+
})
499+
.unwrap_or(false)
500+
}
501+
}
502+
488503
impl Stdio {
489504
fn to_handle(&self, stdio_id: c::DWORD, pipe: &mut Option<AnonPipe>) -> io::Result<Handle> {
490505
match *self {

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

+11
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ fn windows_env_unicode_case() {
135135
fn windows_exe_resolver() {
136136
use super::resolve_exe;
137137
use crate::io;
138+
use crate::sys::fs::symlink;
139+
use crate::sys_common::io::test::tmpdir;
138140

139141
let env_paths = || env::var_os("PATH");
140142

@@ -178,4 +180,13 @@ fn windows_exe_resolver() {
178180
// The application's directory is also searched.
179181
let current_exe = env::current_exe().unwrap();
180182
assert!(resolve_exe(current_exe.file_name().unwrap().as_ref(), empty_paths, None).is_ok());
183+
184+
// Create a temporary path and add a broken symlink.
185+
let temp = tmpdir();
186+
let mut exe_path = temp.path().to_owned();
187+
exe_path.push("exists.exe");
188+
symlink("<DOES NOT EXIST>".as_ref(), &exe_path).unwrap();
189+
190+
// A broken symlink should still be resolved.
191+
assert!(resolve_exe(OsStr::new("exists.exe"), empty_paths, Some(temp.path().as_ref())).is_ok());
181192
}

0 commit comments

Comments
 (0)