#[cfg(windows)]
use std::fs;
use std::path::Path;
pub fn is_qualification_mode() -> bool {
std::env::var("EGGSERVE_WINDOWS_QUALIFY")
.map(|v| v == "1" || v == "true")
.unwrap_or(false)
}
#[derive(Debug, Clone)]
pub struct Capabilities {
pub os_version: String,
pub arch: String,
pub filesystem_type: String,
pub developer_mode: bool,
pub symlink_dir_privilege: bool,
pub junction_creation: bool,
pub is_ntfs: bool,
pub is_local_volume: bool,
}
#[cfg(windows)]
pub fn detect_capabilities(test_root: &Path) -> Capabilities {
let os_version = std::env::var("OS").unwrap_or_else(|_| "unknown".to_string());
let arch = std::env::consts::ARCH.to_string();
let fs_type = get_filesystem_type(test_root);
let is_ntfs = fs_type.eq_ignore_ascii_case("NTFS");
let test_file = test_root.join("cap_probe_file.txt");
let _ = fs::write(&test_file, "probe");
let dm_link = test_root.join("cap_probe_dm_link");
let developer_mode = std::os::windows::fs::symlink_file(&test_file, &dm_link).is_ok();
if developer_mode {
let _ = fs::remove_file(&dm_link);
}
let dir_link = test_root.join("cap_probe_dir_link");
let symlink_dir_privilege = std::os::windows::fs::symlink_dir(test_root, &dir_link).is_ok();
if symlink_dir_privilege {
let _ = fs::remove_dir(&dir_link);
}
let junction_target = test_root.join("subdir");
let junction_link = test_root.join("cap_probe_junction");
let junction_creation = std::process::Command::new("cmd")
.args([
"/C",
"mklink",
"/J",
junction_link.to_str().unwrap(),
junction_target.to_str().unwrap(),
])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false);
if junction_creation {
let _ = fs::remove_dir(&junction_link);
}
let is_local_volume = is_ntfs;
let _ = fs::remove_file(&test_file);
Capabilities {
os_version,
arch,
filesystem_type: fs_type,
developer_mode,
symlink_dir_privilege,
junction_creation,
is_ntfs,
is_local_volume,
}
}
#[cfg(not(windows))]
pub fn detect_capabilities(_test_root: &Path) -> Capabilities {
Capabilities {
os_version: "non-windows".to_string(),
arch: std::env::consts::ARCH.to_string(),
filesystem_type: "non-windows".to_string(),
developer_mode: false,
symlink_dir_privilege: false,
junction_creation: false,
is_ntfs: false,
is_local_volume: false,
}
}
pub fn emit_capabilities(cap: &Capabilities) {
eprintln!("=== Windows Qualification Capabilities ===");
eprintln!("os: {}", cap.os_version);
eprintln!("arch: {}", cap.arch);
eprintln!("filesystem: {}", cap.filesystem_type);
eprintln!("is-ntfs: {}", cap.is_ntfs);
eprintln!("is-local-volume: {}", cap.is_local_volume);
eprintln!("developer-mode: {}", cap.developer_mode);
eprintln!("symlink-dir-privilege: {}", cap.symlink_dir_privilege);
eprintln!("junction-creation: {}", cap.junction_creation);
eprintln!("qualification-mode: {}", is_qualification_mode());
eprintln!("==========================================");
}
#[macro_export]
macro_rules! blocked {
($msg:expr $(, $arg:expr)*) => {
if $crate::qualification::is_qualification_mode() {
panic!("QUALIFICATION FAILURE: {} (qualification mode — fixture must succeed)", format_args!($msg $(, $arg)*));
} else {
panic!("blocked-fixture: {} (standard CI — expected block)", format_args!($msg $(, $arg)*));
}
};
}
#[macro_export]
macro_rules! require_capability {
($cap:expr, $name:expr) => {
if !$cap {
$crate::blocked!("{} not available", $name);
}
};
}
#[cfg(windows)]
fn get_filesystem_type(path: &Path) -> String {
use std::os::windows::ffi::OsStrExt;
use std::ptr;
extern "system" {
fn GetVolumeInformationW(
lp_root_path_name: *const u16,
lp_volume_name_buffer: *mut u16,
n_volume_name_size: u32,
lp_volume_serial_number: *mut u32,
lp_maximum_component_length: *mut u32,
lp_file_system_flags: *mut u32,
lp_file_system_name_buffer: *mut u16,
n_file_system_name_size: u32,
) -> i32;
fn GetVolumePathNameW(
lpsz_file_name: *const u16,
lpsz_volume_path_name: *mut u16,
cch_buffer_length: u32,
) -> i32;
}
let wide: Vec<u16> = path
.to_str()
.map(|s| s.encode_utf16().chain(std::iter::once(0)).collect())
.unwrap_or_default();
let mut volume_root = vec![0u16; 32768];
let root_success = unsafe {
GetVolumePathNameW(
wide.as_ptr(),
volume_root.as_mut_ptr(),
volume_root.len() as u32,
)
};
if root_success == 0 {
return "unknown".to_string();
}
let mut fs_name = vec![0u16; 256];
let success = unsafe {
GetVolumeInformationW(
volume_root.as_ptr(),
ptr::null_mut(),
0,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
fs_name.as_mut_ptr(),
fs_name.len() as u32,
)
};
if success != 0 {
fs_name.truncate(
fs_name
.iter()
.position(|&c| c == 0)
.unwrap_or(fs_name.len()),
);
String::from_utf16_lossy(&fs_name)
} else {
"unknown".to_string()
}
}
#[cfg(not(windows))]
#[allow(dead_code)]
fn get_filesystem_type(_path: &Path) -> String {
"non-windows".to_string()
}