use std::{io, io::ErrorKind::AlreadyExists, path::Path};
#[cfg(unix)]
pub fn create(original: &Path, link: &Path) -> io::Result<()> {
std::os::unix::fs::symlink(original, link)
}
#[cfg(target_os = "wasi")]
pub fn create(original: &Path, link: &Path) -> io::Result<()> {
std::fs::soft_link(original, link)
}
#[cfg(not(any(unix, windows, target_os = "wasi")))]
pub fn create(_original: &Path, _link: &Path) -> io::Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"symbolic links are not supported on this platform",
))
}
#[cfg(any(unix, target_os = "wasi"))]
pub fn remove(path: &Path) -> io::Result<()> {
std::fs::remove_file(path)
}
#[cfg(not(any(unix, windows, target_os = "wasi")))]
pub fn remove(_path: &Path) -> io::Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"symbolic links are not supported on this platform",
))
}
#[cfg(windows)]
pub fn remove(path: &Path) -> io::Result<()> {
if let Ok(meta) = std::fs::metadata(path) {
if meta.is_file() {
std::fs::remove_file(path) } else {
std::fs::remove_dir(path) }
} else {
std::fs::remove_file(path).or_else(|_| std::fs::remove_dir(path))
}
}
#[cfg(windows)]
pub fn create(original: &Path, link: &Path) -> io::Result<()> {
create_inner(original, link, false).map(|_| ())
}
pub fn create_or_delay(original: &Path, link: &Path) -> io::Result<bool> {
#[cfg(windows)]
{
create_inner(original, link, true)
}
#[cfg(not(windows))]
{
create(original, link).map(|()| true)
}
}
#[cfg(windows)]
fn starts_with_separator(path: &Path) -> bool {
matches!(path.as_os_str().as_encoded_bytes().first(), Some(b'/' | b'\\'))
}
#[cfg(windows)]
fn create_inner(original: &Path, link: &Path, delay_if_unknown: bool) -> io::Result<bool> {
use std::os::windows::fs::{symlink_dir, symlink_file};
let is_dir = if starts_with_separator(original) {
false
} else {
let parent = link
.parent()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "symlink path has no parent directory"))?;
match target_is_dir(&parent.join(original)) {
Ok(is_dir) => is_dir,
Err(_) if delay_if_unknown => return Ok(false),
Err(_) => false,
}
};
if is_dir {
symlink_dir(original, link)?;
} else {
symlink_file(original, link)?;
}
Ok(true)
}
#[cfg(windows)]
fn target_is_dir(path: &Path) -> io::Result<bool> {
use std::path::{Component, PathBuf, Prefix};
let mut path = path.to_owned();
'resolve: for _ in 0..40 {
let mut current = PathBuf::new();
let mut components = path.components();
while let Some(component) = components.next() {
current.push(component);
if let Component::Prefix(prefix) = component {
if !matches!(prefix.kind(), Prefix::Disk(_) | Prefix::VerbatimDisk(_)) {
return Ok(false);
}
continue;
}
let meta = current.symlink_metadata()?;
if meta.file_type().is_symlink() {
let target = std::fs::read_link(¤t)?;
if starts_with_separator(&target) {
return Ok(false);
}
current.pop();
path = current.join(target).join(components.as_path());
continue 'resolve;
}
if components.as_path().as_os_str().is_empty() {
return Ok(meta.is_dir());
}
}
}
Err(io::Error::other("symlink target exceeds the resolution limit"))
}
#[cfg(any(unix, target_os = "wasi"))]
pub fn is_collision_error(err: &std::io::Error) -> bool {
err.kind() == AlreadyExists
|| err.kind() == std::io::ErrorKind::IsADirectory
|| err.raw_os_error() == Some(62) || err.raw_os_error() == Some(40) }
#[cfg(windows)]
pub fn is_collision_error(err: &std::io::Error) -> bool {
err.kind() == AlreadyExists || err.kind() == std::io::ErrorKind::PermissionDenied
}
#[cfg(not(any(unix, windows, target_os = "wasi")))]
pub fn is_collision_error(err: &std::io::Error) -> bool {
err.kind() == AlreadyExists
}