Skip to content

Commit 687fffa

Browse files
committed
do not look, cursed things inside
1 parent 9649706 commit 687fffa

File tree

4 files changed

+276
-1
lines changed

4 files changed

+276
-1
lines changed

src/bootstrap/Cargo.lock

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ version = "1.0.8"
1717
source = "registry+https://github.com/rust-lang/crates.io-index"
1818
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
1919

20+
[[package]]
21+
name = "anyhow"
22+
version = "1.0.86"
23+
source = "registry+https://github.com/rust-lang/crates.io-index"
24+
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
25+
2026
[[package]]
2127
name = "bitflags"
2228
version = "2.6.0"
@@ -36,6 +42,7 @@ dependencies = [
3642
name = "bootstrap"
3743
version = "0.0.0"
3844
dependencies = [
45+
"anyhow",
3946
"build_helper",
4047
"cc",
4148
"clap",

src/bootstrap/Cargo.toml

+11-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ build = "build.rs"
66
default-run = "bootstrap"
77

88
[features]
9+
default = ["sysinfo"]
910
build-metrics = ["sysinfo"]
1011
bootstrap-self-test = [] # enabled in the bootstrap unit tests
1112

@@ -41,7 +42,7 @@ cc = "=1.0.97"
4142
cmake = "=0.1.48"
4243

4344
build_helper = { path = "../tools/build_helper" }
44-
clap = { version = "4.4", default-features = false, features = ["std", "usage", "help", "derive", "error-context"] }
45+
clap = { version = "4.4", default-features = false, features = ["derive", "error-context", "help", "std", "usage"] }
4546
clap_complete = "4.4"
4647
fd-lock = "4.0"
4748
home = "0.5"
@@ -62,6 +63,9 @@ toml = "0.5"
6263
walkdir = "2.4"
6364
xz2 = "0.1"
6465

66+
# EXPERIMENTAL
67+
anyhow = "1"
68+
6569
# Dependencies needed by the build-metrics feature
6670
sysinfo = { version = "0.31.2", default-features = false, optional = true, features = ["system"] }
6771

@@ -71,13 +75,19 @@ version = "1.0.0"
7175
[target.'cfg(windows)'.dependencies.windows]
7276
version = "0.52"
7377
features = [
78+
"Wdk_Foundation",
79+
"Wdk_Storage_FileSystem",
80+
"Wdk_System_SystemServices",
7481
"Win32_Foundation",
7582
"Win32_Security",
83+
"Win32_Storage_FileSystem",
7684
"Win32_System_Diagnostics_Debug",
85+
"Win32_System_IO",
7786
"Win32_System_JobObjects",
7887
"Win32_System_ProcessStatus",
7988
"Win32_System_Threading",
8089
"Win32_System_Time",
90+
"Win32_System_WindowsProgramming",
8191
]
8292

8393
[dev-dependencies]

src/bootstrap/src/lib.rs

+108
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ use crate::utils::helpers::{
4343

4444
mod core;
4545
mod utils;
46+
#[cfg(windows)]
47+
mod windows_hacks;
4648

4749
pub use core::builder::PathSet;
4850
pub use core::config::flags::{Flags, Subcommand};
@@ -1663,12 +1665,118 @@ Executed at: {executed_at}"#,
16631665
if src == dst {
16641666
return;
16651667
}
1668+
1669+
#[cfg(windows)]
1670+
{
1671+
// HACK(jieyouxu): let's see what's holding up. Note that this is not robost to TOCTOU
1672+
// races where the process was holding on to the file when calling `remove_file` but
1673+
// released immediately after before gathering process IDs holding the file.
1674+
if fs::symlink_metadata(dst).is_ok() {
1675+
let mut process_ids = windows_hacks::process_ids_using_file(dst).unwrap();
1676+
process_ids.dedup();
1677+
process_ids.sort();
1678+
1679+
if !process_ids.is_empty() {
1680+
eprintln!(
1681+
"[DEBUG] copy_link_internal: pre-remove_file: pids holding dst=`{}`: {:?}",
1682+
dst.display(),
1683+
process_ids
1684+
);
1685+
}
1686+
1687+
use sysinfo::{ProcessRefreshKind, RefreshKind, System};
1688+
1689+
let sys = System::new_with_specifics(
1690+
RefreshKind::new().with_processes(ProcessRefreshKind::everything()),
1691+
);
1692+
1693+
let mut holdups = vec![];
1694+
for (pid, process) in sys.processes() {
1695+
if process_ids.contains(&(pid.as_u32() as usize)) {
1696+
holdups.push((pid.as_u32(), process.exe().unwrap_or(Path::new(""))));
1697+
}
1698+
}
1699+
1700+
if !holdups.is_empty() {
1701+
eprintln!(
1702+
"[DEBUG] copy_link_internal: pre-remove_file: printing process names (where available) holding dst=`{}`",
1703+
dst.display()
1704+
);
1705+
for (pid, process_exe) in holdups {
1706+
eprintln!(
1707+
"[DEBUG] copy_link_internal: pre-remove_file: process holding dst=`{}`: pid={pid}, process_name={:?}",
1708+
dst.display(),
1709+
process_exe
1710+
);
1711+
}
1712+
}
1713+
}
1714+
}
1715+
16661716
if let Err(e) = fs::remove_file(dst) {
16671717
if cfg!(windows) && e.kind() != io::ErrorKind::NotFound {
16681718
// workaround for https://github.com/rust-lang/rust/issues/127126
16691719
// if removing the file fails, attempt to rename it instead.
16701720
let now = t!(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH));
16711721
let _ = fs::rename(dst, format!("{}-{}", dst.display(), now.as_nanos()));
1722+
1723+
#[cfg(windows)]
1724+
{
1725+
eprintln!(
1726+
"[DEBUG]: copy_link_internal: `fs::remove_file` failed on dst=`{}`: {e}",
1727+
dst.display()
1728+
);
1729+
eprintln!("[DEBUG]: copy_link_internal: after `fs::remove_file` failed");
1730+
// HACK(jieyouxu): let's see what's holding up. Note that this is not robost to TOCTOU
1731+
// races where the process was holding on to the file when calling `remove_file` but
1732+
// released immediately after before gathering process IDs holding the file.
1733+
if fs::symlink_metadata(dst).is_ok() {
1734+
let mut process_ids = windows_hacks::process_ids_using_file(dst).unwrap();
1735+
process_ids.dedup();
1736+
process_ids.sort();
1737+
1738+
if !process_ids.is_empty() {
1739+
eprintln!(
1740+
"[DEBUG] copy_link_internal: pids holding dst=`{}`: {:?}",
1741+
dst.display(),
1742+
process_ids
1743+
);
1744+
}
1745+
1746+
use sysinfo::{ProcessRefreshKind, RefreshKind, System};
1747+
1748+
let sys = System::new_with_specifics(
1749+
RefreshKind::new().with_processes(ProcessRefreshKind::everything()),
1750+
);
1751+
1752+
let mut holdups = vec![];
1753+
for (pid, process) in sys.processes() {
1754+
if process_ids.contains(&(pid.as_u32() as usize)) {
1755+
holdups
1756+
.push((pid.as_u32(), process.exe().unwrap_or(Path::new(""))));
1757+
}
1758+
}
1759+
1760+
if holdups.is_empty() {
1761+
eprintln!(
1762+
"[DEBUG] copy_link_internal: did not find any process holding up dst=`{}`, so how did we fail?",
1763+
dst.display(),
1764+
);
1765+
} else {
1766+
eprintln!(
1767+
"[DEBUG] copy_link_internal: printing process names (where available) holding dst=`{}`",
1768+
dst.display()
1769+
);
1770+
for (pid, process_exe) in holdups {
1771+
eprintln!(
1772+
"[DEBUG] copy_link_internal: process holding dst=`{}`: pid={pid}, process_name={:?}",
1773+
dst.display(),
1774+
process_exe
1775+
);
1776+
}
1777+
}
1778+
}
1779+
}
16721780
}
16731781
}
16741782
let metadata = t!(src.symlink_metadata(), format!("src = {}", src.display()));

src/bootstrap/src/windows_hacks.rs

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
//! Experimental windows hacks to try find what the hecc is holding on to the files that cannot be
2+
//! deleted.
3+
4+
// Adapted from <https://stackoverflow.com/questions/67187979/how-to-call-ntopenfile> from
5+
// Delphi for Rust :3
6+
// Also references <https://gist.github.com/antonioCoco/9db236d6089b4b492746f7de31b21d9d>.
7+
8+
// SAFETY:
9+
// YOLO.
10+
11+
// Windows API naming
12+
#![allow(nonstandard_style)]
13+
14+
use std::os::windows::ffi::OsStrExt;
15+
use std::path::Path;
16+
use std::{fs, mem};
17+
18+
use anyhow::{anyhow, Result};
19+
use windows::core::PWSTR;
20+
use windows::Wdk::Foundation::OBJECT_ATTRIBUTES;
21+
use windows::Wdk::Storage::FileSystem::{
22+
NtOpenFile, NtQueryInformationFile, FILE_OPEN_REPARSE_POINT,
23+
};
24+
use windows::Wdk::System::SystemServices::FILE_PROCESS_IDS_USING_FILE_INFORMATION;
25+
use windows::Win32::Foundation::{
26+
CloseHandle, HANDLE, STATUS_INFO_LENGTH_MISMATCH, UNICODE_STRING,
27+
};
28+
use windows::Win32::Storage::FileSystem::{
29+
FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE,
30+
};
31+
use windows::Win32::System::WindowsProgramming::FILE_INFORMATION_CLASS;
32+
use windows::Win32::System::IO::IO_STATUS_BLOCK;
33+
34+
/// Wraps a windows API that returns [`NTSTATUS`]:
35+
///
36+
/// - First convert [`NTSTATUS`] to [`HRESULT`].
37+
/// - Then convert [`HRESULT`] into a [`WinError`] with or without optional info.
38+
macro_rules! try_syscall {
39+
($syscall: expr) => {{
40+
let status = $syscall;
41+
if status.is_err() {
42+
::anyhow::Result::Err(::windows::core::Error::from(status.to_hresult()))?;
43+
}
44+
}};
45+
($syscall: expr, $additional_info: expr) => {{
46+
let status = $syscall;
47+
if status.is_err() {
48+
::anyhow::Result::Err(::windows::core::Error::new(
49+
$syscall.into(),
50+
$additional_info.into(),
51+
))?;
52+
}
53+
}};
54+
}
55+
56+
pub(crate) fn process_ids_using_file(path: &Path) -> Result<Vec<usize>> {
57+
// Gotta have it in UTF-16LE.
58+
let mut nt_path = {
59+
let path = std::path::absolute(path)?;
60+
r"\??\".encode_utf16().chain(path.as_os_str().encode_wide()).collect::<Vec<u16>>()
61+
};
62+
63+
let nt_path_unicode_string = UNICODE_STRING {
64+
Length: u16::try_from(nt_path.len() * 2)?,
65+
MaximumLength: u16::try_from(nt_path.len() * 2)?,
66+
Buffer: PWSTR::from_raw(nt_path.as_mut_ptr()),
67+
};
68+
69+
let object_attributes = OBJECT_ATTRIBUTES {
70+
Length: mem::size_of::<OBJECT_ATTRIBUTES>() as _,
71+
ObjectName: &nt_path_unicode_string,
72+
..Default::default()
73+
};
74+
75+
let mut io_status = IO_STATUS_BLOCK::default();
76+
let mut handle = HANDLE::default();
77+
78+
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntopenfile
79+
try_syscall!(
80+
unsafe {
81+
NtOpenFile(
82+
&mut handle as *mut _,
83+
FILE_READ_ATTRIBUTES.0,
84+
&object_attributes,
85+
&mut io_status as *mut _,
86+
(FILE_SHARE_READ).0,
87+
FILE_OPEN_REPARSE_POINT.0,
88+
)
89+
},
90+
"tried to open file"
91+
);
92+
93+
/// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ne-wdm-_file_information_class
94+
// Remark: apparently windows 0.52 doesn't have this or something, it appears in at least >=
95+
// 0.53.
96+
const FileProcessIdsUsingFileInformation: FILE_INFORMATION_CLASS = FILE_INFORMATION_CLASS(47);
97+
98+
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntqueryinformationfile
99+
const INCREMENT: usize = 8;
100+
let mut buf = vec![FILE_PROCESS_IDS_USING_FILE_INFORMATION::default(); INCREMENT as usize];
101+
let mut buf_idx = 0;
102+
let mut status = unsafe {
103+
NtQueryInformationFile(
104+
handle,
105+
&mut io_status as *mut _,
106+
buf.as_mut_ptr().cast(),
107+
(INCREMENT * mem::size_of::<FILE_PROCESS_IDS_USING_FILE_INFORMATION>()) as u32,
108+
FileProcessIdsUsingFileInformation,
109+
)
110+
};
111+
while status == STATUS_INFO_LENGTH_MISMATCH {
112+
buf.resize(buf.len() + INCREMENT, FILE_PROCESS_IDS_USING_FILE_INFORMATION::default());
113+
buf_idx += INCREMENT;
114+
status = unsafe {
115+
NtQueryInformationFile(
116+
handle,
117+
&mut io_status as *mut _,
118+
buf.as_mut_ptr()
119+
.offset(
120+
(buf_idx * mem::size_of::<FILE_PROCESS_IDS_USING_FILE_INFORMATION>())
121+
as isize,
122+
)
123+
.cast(),
124+
(INCREMENT * mem::size_of::<FILE_PROCESS_IDS_USING_FILE_INFORMATION>()) as u32,
125+
FileProcessIdsUsingFileInformation,
126+
)
127+
};
128+
}
129+
130+
let mut process_ids = vec![];
131+
132+
for FILE_PROCESS_IDS_USING_FILE_INFORMATION {
133+
NumberOfProcessIdsInList,
134+
ProcessIdList: [ptr],
135+
} in buf
136+
{
137+
if NumberOfProcessIdsInList >= 1 {
138+
// only fetch the first one
139+
process_ids.push(unsafe {
140+
// This is almost certaintly UB, provenance be damned
141+
let ptr = ptr as *mut usize;
142+
*ptr
143+
});
144+
}
145+
}
146+
147+
try_syscall!(unsafe { CloseHandle(handle) }, "close file handle");
148+
149+
Ok(process_ids)
150+
}

0 commit comments

Comments
 (0)