use std::path::Path;
#[cfg(target_os = "macos")]
pub fn supports_cow_reflinks(path: &Path) -> bool {
use std::ffi::CString;
use std::os::unix::ffi::OsStrExt;
#[repr(C)]
struct statfs {
f_bsize: u32,
f_iosize: i32,
f_blocks: u64,
f_bfree: u64,
f_bavail: u64,
f_files: u64,
f_ffree: u64,
f_fsid: [i32; 2],
f_owner: u32,
f_type: u32,
f_flags: u32,
f_fssubtype: u32,
f_fstypename: [u8; 16],
f_mntonname: [u8; 1024],
f_mntfromname: [u8; 1024],
f_reserved: [u32; 8],
}
extern "C" {
fn statfs(path: *const libc::c_char, buf: *mut statfs) -> libc::c_int;
}
let path_bytes = path.as_os_str().as_bytes();
let path_c = match CString::new(path_bytes) {
Ok(p) => p,
Err(_) => return false,
};
unsafe {
let mut stat: std::mem::MaybeUninit<statfs> = std::mem::MaybeUninit::uninit();
if statfs(path_c.as_ptr(), stat.as_mut_ptr()) == 0 {
let stat = stat.assume_init();
let fs_type = std::str::from_utf8(&stat.f_fstypename).ok().and_then(|s| s.split('\0').next()).unwrap_or("");
fs_type == "apfs"
} else {
false
}
}
}
#[cfg(target_os = "linux")]
pub fn supports_cow_reflinks(path: &Path) -> bool {
use std::ffi::CString;
use std::os::unix::ffi::OsStrExt;
let path_bytes = path.as_os_str().as_bytes();
let path_c = match CString::new(path_bytes) {
Ok(p) => p,
Err(_) => return false,
};
unsafe {
let mut stat: std::mem::MaybeUninit<libc::statfs> = std::mem::MaybeUninit::uninit();
if libc::statfs(path_c.as_ptr(), stat.as_mut_ptr()) == 0 {
let stat = stat.assume_init();
matches!(stat.f_type, 0x9123683E | 0x58465342)
} else {
false
}
}
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
pub fn supports_cow_reflinks(_path: &Path) -> bool {
false
}
#[cfg(unix)]
pub fn same_filesystem(path1: &Path, path2: &Path) -> bool {
use std::os::unix::fs::MetadataExt;
let meta1 = match std::fs::metadata(path1) {
Ok(m) => m,
Err(_) => return false,
};
let meta2 = match std::fs::metadata(path2) {
Ok(m) => m,
Err(_) => return false,
};
meta1.dev() == meta2.dev()
}
#[cfg(not(unix))]
pub fn same_filesystem(_path1: &Path, _path2: &Path) -> bool {
false
}
#[cfg(unix)]
pub fn has_hard_links(path: &Path) -> bool {
use std::os::unix::fs::MetadataExt;
std::fs::metadata(path).map(|m| m.nlink() > 1).unwrap_or(false)
}
#[cfg(not(unix))]
pub fn has_hard_links(_path: &Path) -> bool {
false
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_cow_detection() {
let temp = TempDir::new().unwrap();
let test_file = temp.path().join("test.txt");
fs::write(&test_file, b"test").unwrap();
let supports_cow = supports_cow_reflinks(&test_file);
#[cfg(target_os = "macos")]
{
println!("macOS COW support: {}", supports_cow);
}
#[cfg(target_os = "linux")]
{
println!("Linux COW support: {}", supports_cow);
}
}
#[test]
#[cfg(unix)]
fn test_same_filesystem() {
let temp = TempDir::new().unwrap();
let file1 = temp.path().join("file1.txt");
let file2 = temp.path().join("file2.txt");
fs::write(&file1, b"test1").unwrap();
fs::write(&file2, b"test2").unwrap();
assert!(same_filesystem(&file1, &file2));
assert!(same_filesystem(&file1, temp.path()));
}
#[test]
#[cfg(unix)]
fn test_hard_link_detection() {
let temp = TempDir::new().unwrap();
let file1 = temp.path().join("file1.txt");
let file2 = temp.path().join("file2.txt");
fs::write(&file1, b"test").unwrap();
assert!(!has_hard_links(&file1));
#[cfg(unix)]
{
std::fs::hard_link(&file1, &file2).unwrap();
assert!(has_hard_links(&file1));
assert!(has_hard_links(&file2));
}
}
#[test]
#[cfg(windows)]
fn test_windows_no_cow_support() {
let temp = TempDir::new().unwrap();
let test_file = temp.path().join("test.txt");
fs::write(&test_file, b"test").unwrap();
assert!(!supports_cow_reflinks(&test_file));
}
#[test]
#[cfg(windows)]
fn test_windows_same_filesystem_conservative() {
let temp = TempDir::new().unwrap();
let file1 = temp.path().join("file1.txt");
let file2 = temp.path().join("file2.txt");
fs::write(&file1, b"test1").unwrap();
fs::write(&file2, b"test2").unwrap();
assert!(!same_filesystem(&file1, &file2));
}
#[test]
#[cfg(windows)]
fn test_windows_no_hard_link_detection() {
let temp = TempDir::new().unwrap();
let file1 = temp.path().join("file1.txt");
fs::write(&file1, b"test").unwrap();
assert!(!has_hard_links(&file1));
}
#[test]
#[cfg(target_os = "linux")]
fn test_linux_filesystem_detection() {
let temp = TempDir::new().unwrap();
let test_file = temp.path().join("test.txt");
fs::write(&test_file, b"test").unwrap();
let supports_cow = supports_cow_reflinks(&test_file);
println!("Linux temp directory COW support: {}", supports_cow);
}
#[test]
#[cfg(target_os = "linux")]
fn test_linux_btrfs_detection() {
const BTRFS_SUPER_MAGIC: i64 = 0x9123683E;
assert_eq!(BTRFS_SUPER_MAGIC, 0x9123683E);
}
#[test]
#[cfg(target_os = "linux")]
fn test_linux_xfs_detection() {
const XFS_SUPER_MAGIC: i64 = 0x58465342;
assert_eq!(XFS_SUPER_MAGIC, 0x58465342);
}
#[test]
#[cfg(target_os = "linux")]
fn test_linux_same_filesystem() {
let temp = TempDir::new().unwrap();
let file1 = temp.path().join("file1.txt");
let file2 = temp.path().join("file2.txt");
fs::write(&file1, b"test1").unwrap();
fs::write(&file2, b"test2").unwrap();
assert!(same_filesystem(&file1, &file2));
}
#[test]
#[cfg(target_os = "macos")]
fn test_macos_apfs_detection() {
let temp = TempDir::new().unwrap();
let test_file = temp.path().join("test.txt");
fs::write(&test_file, b"test").unwrap();
let supports_cow = supports_cow_reflinks(&test_file);
println!("macOS COW support (APFS detection): {}", supports_cow);
}
#[test]
#[cfg(target_os = "macos")]
fn test_macos_architecture_info() {
#[cfg(target_arch = "aarch64")]
{
println!("Running on Apple Silicon (ARM64/aarch64)");
assert_eq!(std::env::consts::ARCH, "aarch64");
}
#[cfg(target_arch = "x86_64")]
{
println!("Running on Intel (x86_64)");
assert_eq!(std::env::consts::ARCH, "x86_64");
}
}
#[test]
#[cfg(target_os = "macos")]
fn test_macos_same_filesystem() {
let temp = TempDir::new().unwrap();
let file1 = temp.path().join("file1.txt");
let file2 = temp.path().join("file2.txt");
fs::write(&file1, b"test1").unwrap();
fs::write(&file2, b"test2").unwrap();
assert!(same_filesystem(&file1, &file2));
}
#[test]
#[cfg(target_os = "macos")]
fn test_macos_hard_links() {
let temp = TempDir::new().unwrap();
let file1 = temp.path().join("file1.txt");
let file2 = temp.path().join("file2.txt");
fs::write(&file1, b"test").unwrap();
assert!(!has_hard_links(&file1));
std::fs::hard_link(&file1, &file2).unwrap();
assert!(has_hard_links(&file1));
assert!(has_hard_links(&file2));
}
#[test]
#[cfg(target_os = "macos")]
fn test_macos_apfs_magic_string() {
const APFS_TYPE_NAME: &str = "apfs";
assert_eq!(APFS_TYPE_NAME, "apfs");
assert_eq!(APFS_TYPE_NAME.len(), 4);
}
#[test]
fn test_cow_detection_nonexistent_path() {
let nonexistent = Path::new("/nonexistent/path/that/does/not/exist.txt");
assert!(!supports_cow_reflinks(nonexistent), "Non-existent path should return false for COW support");
}
#[test]
fn test_cow_detection_nonexistent_parent() {
let nonexistent = Path::new("/nonexistent/parent/dir/file.txt");
assert!(!supports_cow_reflinks(nonexistent), "Path with non-existent parent should return false");
}
#[test]
#[cfg(unix)]
fn test_same_filesystem_nonexistent_paths() {
let path1 = Path::new("/nonexistent/path1.txt");
let path2 = Path::new("/nonexistent/path2.txt");
assert!(!same_filesystem(path1, path2), "Non-existent paths should return false");
let temp = TempDir::new().unwrap();
let existing = temp.path().join("exists.txt");
fs::write(&existing, b"test").unwrap();
let nonexistent = temp.path().join("nonexistent.txt");
assert!(!same_filesystem(&existing, &nonexistent), "Mixed existent/non-existent should return false");
}
#[test]
#[cfg(unix)]
fn test_same_filesystem_parent_and_child() {
let temp = TempDir::new().unwrap();
let file = temp.path().join("test.txt");
fs::write(&file, b"test").unwrap();
assert!(same_filesystem(&file, temp.path()), "File and parent directory should be on same filesystem");
let subdir = temp.path().join("subdir");
fs::create_dir(&subdir).unwrap();
let nested_file = subdir.join("nested.txt");
fs::write(&nested_file, b"test").unwrap();
assert!(same_filesystem(&nested_file, temp.path()), "Nested file and grandparent should be on same filesystem");
}
#[test]
#[cfg(unix)]
fn test_has_hard_links_nonexistent() {
let nonexistent = Path::new("/nonexistent/file.txt");
assert!(!has_hard_links(nonexistent), "Non-existent file should return false for hard links");
}
#[test]
#[cfg(unix)]
fn test_has_hard_links_directory() {
let temp = TempDir::new().unwrap();
let dir = temp.path().join("testdir");
fs::create_dir(&dir).unwrap();
let _ = has_hard_links(&dir);
}
#[test]
#[cfg(unix)]
fn test_hard_links_three_way() {
let temp = TempDir::new().unwrap();
let file1 = temp.path().join("file1.txt");
let file2 = temp.path().join("file2.txt");
let file3 = temp.path().join("file3.txt");
fs::write(&file1, b"shared").unwrap();
fs::hard_link(&file1, &file2).unwrap();
fs::hard_link(&file1, &file3).unwrap();
assert!(has_hard_links(&file1));
assert!(has_hard_links(&file2));
assert!(has_hard_links(&file3));
}
#[test]
#[cfg(unix)]
fn test_same_filesystem_symlink() {
let temp = TempDir::new().unwrap();
let file = temp.path().join("file.txt");
fs::write(&file, b"test").unwrap();
let symlink = temp.path().join("link.txt");
#[cfg(unix)]
std::os::unix::fs::symlink(&file, &symlink).unwrap();
assert!(same_filesystem(&file, &symlink), "Symlink and target should be on same filesystem");
}
#[test]
#[cfg(target_os = "linux")]
fn test_linux_filesystem_magic_numbers() {
const BTRFS_SUPER_MAGIC: i64 = 0x9123683E;
const XFS_SUPER_MAGIC: i64 = 0x58465342;
assert_eq!(BTRFS_SUPER_MAGIC, 0x9123683E);
assert_eq!(XFS_SUPER_MAGIC, 0x58465342);
assert_ne!(BTRFS_SUPER_MAGIC, XFS_SUPER_MAGIC);
}
#[test]
#[cfg(target_os = "macos")]
fn test_macos_hfs_plus_not_cow() {
const HFS_PLUS_TYPE_NAME: &str = "hfs";
assert_eq!(HFS_PLUS_TYPE_NAME, "hfs");
assert_ne!(HFS_PLUS_TYPE_NAME, "apfs");
}
}