use super::*;
pub(in crate::daemon::server) fn break_output_hardlink_before_compile(
path: &Path,
) -> std::io::Result<()> {
match std::fs::metadata(path) {
Ok(meta) if meta.is_file() => {}
Ok(_) => return Ok(()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()),
Err(e) => return Err(e),
}
if hard_link_count(path)? <= 1 {
return Ok(());
}
let parent = path.parent().unwrap_or_else(|| Path::new("."));
let file_name = path
.file_name()
.unwrap_or_else(|| std::ffi::OsStr::new("output"))
.to_string_lossy();
let nonce = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let pid = std::process::id();
let mut last_err = None;
for attempt in 0..32 {
let tmp_path = parent.join(format!(
".zccache-detach-{pid}-{nonce}-{attempt}-{file_name}"
));
let copy_result = (|| {
let mut src = std::fs::File::open(path)?;
let mut dst = std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&tmp_path)?;
std::io::copy(&mut src, &mut dst)?;
dst.sync_all()?;
let permissions = src.metadata()?.permissions();
std::fs::set_permissions(&tmp_path, permissions)?;
Ok::<(), std::io::Error>(())
})();
match copy_result {
Ok(()) => {
if let Err(e) = std::fs::remove_file(path) {
let _ = std::fs::remove_file(&tmp_path);
return Err(e);
}
if let Err(e) = std::fs::rename(&tmp_path, path) {
let _ = std::fs::remove_file(&tmp_path);
return Err(e);
}
return Ok(());
}
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
last_err = Some(e);
}
Err(e) => {
let _ = std::fs::remove_file(&tmp_path);
return Err(e);
}
}
}
Err(last_err.unwrap_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::AlreadyExists,
"failed to create hardlink detach temp file",
)
}))
}
#[cfg(unix)]
pub(in crate::daemon::server) fn hard_link_count(path: &Path) -> std::io::Result<u64> {
use std::os::unix::fs::MetadataExt;
Ok(std::fs::metadata(path)?.nlink())
}
#[cfg(windows)]
pub(in crate::daemon::server) fn hard_link_count(path: &Path) -> std::io::Result<u64> {
use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::Foundation::CloseHandle;
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION, FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
};
let wide: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
unsafe {
let handle = CreateFileW(
wide.as_ptr(),
0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
std::ptr::null(),
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
std::ptr::null_mut(),
);
if handle == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE {
return Err(std::io::Error::last_os_error());
}
let mut info: BY_HANDLE_FILE_INFORMATION = std::mem::zeroed();
let ok = GetFileInformationByHandle(handle, &mut info);
let close_result = CloseHandle(handle);
if ok == 0 {
return Err(std::io::Error::last_os_error());
}
if close_result == 0 {
return Err(std::io::Error::last_os_error());
}
Ok(info.nNumberOfLinks as u64)
}
}
#[cfg(unix)]
pub(in crate::daemon::server) fn same_file(a: &Path, b: &Path) -> bool {
use std::os::unix::fs::MetadataExt;
match (std::fs::metadata(a), std::fs::metadata(b)) {
(Ok(ma), Ok(mb)) => ma.dev() == mb.dev() && ma.ino() == mb.ino(),
_ => false,
}
}
#[cfg(windows)]
pub(in crate::daemon::server) fn same_file(a: &Path, b: &Path) -> bool {
get_file_id(a)
.zip(get_file_id(b))
.map(|(ia, ib)| ia == ib)
.unwrap_or(false)
}
#[cfg(windows)]
pub(in crate::daemon::server) fn get_file_id(path: &Path) -> Option<(u32, u32, u32)> {
use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::Foundation::CloseHandle;
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION, FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
};
let wide: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
unsafe {
let handle = CreateFileW(
wide.as_ptr(),
0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
std::ptr::null(),
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
std::ptr::null_mut(),
);
if handle == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE {
return None;
}
let mut info: BY_HANDLE_FILE_INFORMATION = std::mem::zeroed();
let ok = GetFileInformationByHandle(handle, &mut info);
CloseHandle(handle);
if ok == 0 {
return None;
}
Some((
info.dwVolumeSerialNumber,
info.nFileIndexHigh,
info.nFileIndexLow,
))
}
}