use crate::error::Result;
#[cfg(not(target_os = "windows"))]
use log::{debug, warn};
use std::fs;
use std::path::Path;
#[cfg(unix)]
pub fn create_symlink(target: &Path, link: &Path) -> std::io::Result<()> {
use std::os::unix::fs as unix_fs;
unix_fs::symlink(target, link)
}
#[cfg(windows)]
pub fn create_symlink(target: &Path, link: &Path) -> std::io::Result<()> {
let bytes_copied = fs::copy(target, link)?;
let source_size = fs::metadata(target)?.len();
if bytes_copied != source_size {
return Err(std::io::Error::other(format!(
"Copy size mismatch: expected {source_size} bytes, copied {bytes_copied} bytes"
)));
}
Ok(())
}
#[cfg(unix)]
pub fn verify_symlink(link: &Path, expected_target: &Path) -> std::io::Result<bool> {
if !link.exists() {
return Ok(false);
}
let metadata = fs::symlink_metadata(link)?;
if !metadata.file_type().is_symlink() {
return Ok(false);
}
let target = fs::read_link(link)?;
Ok(target == expected_target)
}
#[cfg(windows)]
pub fn verify_symlink(link: &Path, _expected_target: &Path) -> std::io::Result<bool> {
Ok(link.exists() && link.is_file())
}
#[cfg(unix)]
pub fn is_symlink(path: &Path) -> std::io::Result<bool> {
let metadata = fs::symlink_metadata(path)?;
Ok(metadata.file_type().is_symlink())
}
#[cfg(windows)]
pub fn is_symlink(_path: &Path) -> std::io::Result<bool> {
Ok(false)
}
#[cfg(not(target_os = "windows"))]
pub fn cleanup_orphaned_symlinks(dir: &Path) -> Result<()> {
debug!("Cleaning up orphaned symlinks in {}", dir.display());
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if let Ok(metadata) = fs::symlink_metadata(&path)
&& metadata.file_type().is_symlink()
{
if fs::metadata(&path).is_err() {
debug!("Removing orphaned symlink: {}", path.display());
if let Err(e) = fs::remove_file(&path) {
warn!(
"Failed to remove orphaned symlink {}: {}",
path.display(),
e
);
}
}
}
}
}
Ok(())
}
#[cfg(target_os = "windows")]
pub fn cleanup_orphaned_symlinks(_dir: &Path) -> Result<()> {
Ok(())
}
#[cfg(test)]
mod tests {
#[cfg(not(target_os = "windows"))]
use super::*;
#[cfg(not(target_os = "windows"))]
use tempfile::TempDir;
#[cfg(not(target_os = "windows"))]
#[test]
fn test_cleanup_orphaned_symlinks() {
let temp_dir = TempDir::new().unwrap();
let symlink_path = temp_dir.path().join("test_link");
let target_path = temp_dir.path().join("nonexistent_target");
#[cfg(unix)]
{
std::os::unix::fs::symlink(&target_path, &symlink_path).unwrap();
assert!(std::fs::symlink_metadata(&symlink_path).is_ok()); assert!(!target_path.exists());
cleanup_orphaned_symlinks(temp_dir.path()).unwrap();
assert!(std::fs::symlink_metadata(&symlink_path).is_err()); }
}
}