Skip to content

Commit 3d44a82

Browse files
committed
do not look, cursed things inside
1 parent 9649706 commit 3d44a82

File tree

4 files changed

+228
-1
lines changed

4 files changed

+228
-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

+58
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,68 @@ Executed at: {executed_at}"#,
16631665
if src == dst {
16641666
return;
16651667
}
1668+
16661669
if let Err(e) = fs::remove_file(dst) {
16671670
if cfg!(windows) && e.kind() != io::ErrorKind::NotFound {
16681671
// workaround for https://github.com/rust-lang/rust/issues/127126
16691672
// if removing the file fails, attempt to rename it instead.
16701673
let now = t!(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH));
16711674
let _ = fs::rename(dst, format!("{}-{}", dst.display(), now.as_nanos()));
1675+
1676+
#[cfg(windows)]
1677+
{
1678+
eprintln!(
1679+
"[DEBUG]: copy_link_internal: `fs::remove_file` failed on dst=`{}`: {e}",
1680+
dst.display()
1681+
);
1682+
eprintln!("[DEBUG]: copy_link_internal: after `fs::remove_file` failed");
1683+
// HACK(jieyouxu): let's see what's holding up. Note that this is not robost to TOCTOU
1684+
// races where the process was holding on to the file when calling `remove_file` but
1685+
// released immediately after before gathering process IDs holding the file.
1686+
let mut process_ids = windows_hacks::process_ids_using_file(dst).unwrap();
1687+
process_ids.dedup();
1688+
process_ids.sort();
1689+
1690+
if !process_ids.is_empty() {
1691+
eprintln!(
1692+
"[DEBUG] copy_link_internal: pids holding dst=`{}`: {:?}",
1693+
dst.display(),
1694+
process_ids
1695+
);
1696+
}
1697+
1698+
use sysinfo::{ProcessRefreshKind, RefreshKind, System};
1699+
1700+
let sys = System::new_with_specifics(
1701+
RefreshKind::new().with_processes(ProcessRefreshKind::everything()),
1702+
);
1703+
1704+
let mut holdups = vec![];
1705+
for (pid, process) in sys.processes() {
1706+
if process_ids.contains(&(pid.as_u32() as usize)) {
1707+
holdups.push((pid.as_u32(), process.exe().unwrap_or(Path::new(""))));
1708+
}
1709+
}
1710+
1711+
if holdups.is_empty() {
1712+
eprintln!(
1713+
"[DEBUG] copy_link_internal: did not find any process holding up dst=`{}`, so how did we fail?",
1714+
dst.display(),
1715+
);
1716+
} else {
1717+
eprintln!(
1718+
"[DEBUG] copy_link_internal: printing process names (where available) holding dst=`{}`",
1719+
dst.display()
1720+
);
1721+
for (pid, process_exe) in holdups {
1722+
eprintln!(
1723+
"[DEBUG] copy_link_internal: process holding dst=`{}`: pid={pid}, process_name={:?}",
1724+
dst.display(),
1725+
process_exe
1726+
);
1727+
}
1728+
}
1729+
}
16721730
}
16731731
}
16741732
let metadata = t!(src.symlink_metadata(), format!("src = {}", src.display()));

src/bootstrap/src/windows_hacks.rs

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

0 commit comments

Comments
 (0)