Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c489c90

Browse files
committedSep 1, 2024
do not look, cursed things inside
1 parent 9649706 commit c489c90

File tree

4 files changed

+278
-1
lines changed

4 files changed

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

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