diff --git a/src/bootstrap/Cargo.lock b/src/bootstrap/Cargo.lock index 357c6175152d4..3a24e064a4971 100644 --- a/src/bootstrap/Cargo.lock +++ b/src/bootstrap/Cargo.lock @@ -17,6 +17,12 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + [[package]] name = "bitflags" version = "2.6.0" @@ -36,6 +42,7 @@ dependencies = [ name = "bootstrap" version = "0.0.0" dependencies = [ + "anyhow", "build_helper", "cc", "clap", diff --git a/src/bootstrap/Cargo.toml b/src/bootstrap/Cargo.toml index 07f7444aaff64..015c4c57823fc 100644 --- a/src/bootstrap/Cargo.toml +++ b/src/bootstrap/Cargo.toml @@ -6,6 +6,7 @@ build = "build.rs" default-run = "bootstrap" [features] +default = ["sysinfo"] build-metrics = ["sysinfo"] bootstrap-self-test = [] # enabled in the bootstrap unit tests @@ -41,7 +42,7 @@ cc = "=1.0.97" cmake = "=0.1.48" build_helper = { path = "../tools/build_helper" } -clap = { version = "4.4", default-features = false, features = ["std", "usage", "help", "derive", "error-context"] } +clap = { version = "4.4", default-features = false, features = ["derive", "error-context", "help", "std", "usage"] } clap_complete = "4.4" fd-lock = "4.0" home = "0.5" @@ -62,6 +63,9 @@ toml = "0.5" walkdir = "2.4" xz2 = "0.1" +# EXPERIMENTAL +anyhow = "1" + # Dependencies needed by the build-metrics feature sysinfo = { version = "0.31.2", default-features = false, optional = true, features = ["system"] } @@ -71,13 +75,19 @@ version = "1.0.0" [target.'cfg(windows)'.dependencies.windows] version = "0.52" features = [ + "Wdk_Foundation", + "Wdk_Storage_FileSystem", + "Wdk_System_SystemServices", "Win32_Foundation", "Win32_Security", + "Win32_Storage_FileSystem", "Win32_System_Diagnostics_Debug", + "Win32_System_IO", "Win32_System_JobObjects", "Win32_System_ProcessStatus", "Win32_System_Threading", "Win32_System_Time", + "Win32_System_WindowsProgramming", ] [dev-dependencies] diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index 268392c5fb118..a09590e0d4636 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -43,6 +43,8 @@ use crate::utils::helpers::{ mod core; mod utils; +#[cfg(windows)] +mod windows_hacks; pub use core::builder::PathSet; pub use core::config::flags::{Flags, Subcommand}; @@ -1669,6 +1671,27 @@ Executed at: {executed_at}"#, // if removing the file fails, attempt to rename it instead. let now = t!(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)); let _ = fs::rename(dst, format!("{}-{}", dst.display(), now.as_nanos())); + + // HACK(jieyouxu): let's see what's holding up + let mut process_ids = windows_hacks::process_ids_using_file(dst).unwrap(); + process_ids.dedup(); + process_ids.sort(); + use sysinfo::{ProcessRefreshKind, RefreshKind, System}; + + let sys = System::new_with_specifics( + RefreshKind::new().with_processes(ProcessRefreshKind::everything()), + ); + + eprintln!("[DEBUG] printing processes holding dst=`{}`", dst.display()); + for (pid, process) in sys.processes() { + if process_ids.contains(&(pid.as_u32() as usize)) { + eprintln!( + "[DEBUG] process holding dst=`{}`: pid={pid}, process_name={:?}", + dst.display(), + process.exe().unwrap_or(Path::new("")) + ); + } + } } } let metadata = t!(src.symlink_metadata(), format!("src = {}", src.display())); diff --git a/src/bootstrap/src/windows_hacks.rs b/src/bootstrap/src/windows_hacks.rs new file mode 100644 index 0000000000000..9cca40e662af2 --- /dev/null +++ b/src/bootstrap/src/windows_hacks.rs @@ -0,0 +1,150 @@ +//! Experimental windows hacks to try find what the hecc is holding on to the files that cannot be +//! deleted. + +// Adapted from from +// Delphi for Rust :3 +// Also references . + +// SAFETY: +// YOLO. + +// Windows API naming +#![allow(nonstandard_style)] + +use std::mem; +use std::os::windows::ffi::OsStrExt; +use std::path::Path; + +use anyhow::Result; +use windows::core::PWSTR; +use windows::Wdk::Foundation::OBJECT_ATTRIBUTES; +use windows::Wdk::Storage::FileSystem::{ + NtOpenFile, NtQueryInformationFile, FILE_OPEN_REPARSE_POINT, +}; +use windows::Wdk::System::SystemServices::FILE_PROCESS_IDS_USING_FILE_INFORMATION; +use windows::Win32::Foundation::{ + CloseHandle, HANDLE, STATUS_INFO_LENGTH_MISMATCH, UNICODE_STRING, +}; +use windows::Win32::Storage::FileSystem::{ + FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, +}; +use windows::Win32::System::WindowsProgramming::FILE_INFORMATION_CLASS; +use windows::Win32::System::IO::IO_STATUS_BLOCK; + +/// Wraps a windows API that returns [`NTSTATUS`]: +/// +/// - First convert [`NTSTATUS`] to [`HRESULT`]. +/// - Then convert [`HRESULT`] into a [`WinError`] with or without optional info. +macro_rules! try_syscall { + ($syscall: expr) => {{ + let status = $syscall; + if status.is_err() { + ::anyhow::Result::Err(::windows::core::Error::from(status.to_hresult()))?; + } + }}; + ($syscall: expr, $additional_info: expr) => {{ + let status = $syscall; + if status.is_err() { + ::anyhow::Result::Err(::windows::core::Error::new( + $syscall.into(), + $additional_info.into(), + ))?; + } + }}; +} + +pub(crate) fn process_ids_using_file(path: &Path) -> Result> { + // Gotta have it in UTF-16LE. + let mut nt_path = { + let path = std::path::absolute(path)?; + r"\??\".encode_utf16().chain(path.as_os_str().encode_wide()).collect::>() + }; + + let nt_path_unicode_string = UNICODE_STRING { + Length: u16::try_from(nt_path.len() * 2)?, + MaximumLength: u16::try_from(nt_path.len() * 2)?, + Buffer: PWSTR::from_raw(nt_path.as_mut_ptr()), + }; + + let object_attributes = OBJECT_ATTRIBUTES { + Length: mem::size_of::() as _, + ObjectName: &nt_path_unicode_string, + ..Default::default() + }; + + let mut io_status = IO_STATUS_BLOCK::default(); + let mut handle = HANDLE::default(); + + // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntopenfile + try_syscall!( + unsafe { + NtOpenFile( + &mut handle as *mut _, + FILE_READ_ATTRIBUTES.0, + &object_attributes, + &mut io_status as *mut _, + (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE).0, + FILE_OPEN_REPARSE_POINT.0, + ) + }, + "tried to open file" + ); + + /// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ne-wdm-_file_information_class + // Remark: apparently windows 0.52 doesn't have this or something, it appears in at least >= + // 0.53. + const FileProcessIdsUsingFileInformation: FILE_INFORMATION_CLASS = FILE_INFORMATION_CLASS(47); + + // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntqueryinformationfile + const INCREMENT: usize = 8; + let mut buf = vec![FILE_PROCESS_IDS_USING_FILE_INFORMATION::default(); INCREMENT as usize]; + let mut buf_idx = 0; + let mut status = unsafe { + NtQueryInformationFile( + handle, + &mut io_status as *mut _, + buf.as_mut_ptr().cast(), + (INCREMENT * mem::size_of::()) as u32, + FileProcessIdsUsingFileInformation, + ) + }; + while status == STATUS_INFO_LENGTH_MISMATCH { + buf.resize(buf.len() + INCREMENT, FILE_PROCESS_IDS_USING_FILE_INFORMATION::default()); + buf_idx += INCREMENT; + status = unsafe { + NtQueryInformationFile( + handle, + &mut io_status as *mut _, + buf.as_mut_ptr() + .offset( + (buf_idx * mem::size_of::()) + as isize, + ) + .cast(), + (INCREMENT * mem::size_of::()) as u32, + FileProcessIdsUsingFileInformation, + ) + }; + } + + let mut process_ids = vec![]; + + for FILE_PROCESS_IDS_USING_FILE_INFORMATION { + NumberOfProcessIdsInList, + ProcessIdList: [ptr], + } in buf + { + if NumberOfProcessIdsInList >= 1 { + // only fetch the first one + process_ids.push(unsafe { + // This is almost certaintly UB, provenance be damned + let ptr = ptr as *mut usize; + *ptr + }); + } + } + + try_syscall!(unsafe { CloseHandle(handle) }, "close file handle"); + + Ok(process_ids) +}