use super::*;
pub fn rename_path(original_path: PathBuf, new_path: PathBuf) -> Result<(), Error> {
rename(original_path, new_path)?;
Ok(())
}
pub fn create_file(path: PathBuf) -> Result<(), Error> {
std::fs::OpenOptions::new().write(true).create_new(true).open(path)?;
Ok(())
}
pub fn create_directory(path: PathBuf) -> Result<(), Error> {
create_dir(path)?;
Ok(())
}
pub fn create_symlink(target: &Path, link: &Path, is_dir: bool) -> Result<(), Error> {
if path_exists(link) {
return Err(Error::new(ErrorKind::AlreadyExists, format!("{} already exists", link.display())));
}
#[cfg(unix)]
{
let _ = is_dir;
std::os::unix::fs::symlink(target, link)
}
#[cfg(windows)]
{
if is_dir { std::os::windows::fs::symlink_dir(target, link) } else { std::os::windows::fs::symlink_file(target, link) }
}
#[cfg(not(any(unix, windows)))]
{
let _ = (target, is_dir);
Err(Error::new(ErrorKind::Unsupported, "Symbolic links are not supported here"))
}
}
pub fn create_hard_link(target: &Path, link: &Path, is_dir: bool) -> Result<(), Error> {
if is_dir {
return Err(Error::new(ErrorKind::InvalidInput, "a directory cannot be hard linked"));
}
if path_exists(link) {
return Err(Error::new(ErrorKind::AlreadyExists, format!("{} already exists", link.display())));
}
let target = if target.is_symlink() { std::fs::canonicalize(target)? } else { target.to_path_buf() };
std::fs::hard_link(target, link)
}
pub fn save_file(path: &Path, content: &[u8]) -> Result<(), Error> {
let target = fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
let in_place = || fs::write(&target, content);
let Ok(metadata) = fs::metadata(&target) else {
return in_place();
};
if !metadata.is_file() {
return Err(not_regular(&target));
}
File::options().write(true).open(&target)?;
if has_other_links(&metadata) {
return in_place();
}
let (temp, mut file) = match temp_beside(&target) {
Ok(pair) => pair,
Err(e) if e.kind() == ErrorKind::PermissionDenied => return in_place(),
Err(e) => return Err(e),
};
if keep_owner(&file, &metadata).is_err() {
drop(file);
let _ = remove_file(&temp);
return in_place();
}
let written = file.write_all(content).and_then(|()| file.set_permissions(metadata.permissions())).and_then(|()| file.sync_all());
drop(file);
if let Err(e) = written.and_then(|()| rename(&temp, &target)) {
let _ = remove_file(&temp);
return Err(e);
}
Ok(())
}
#[cfg(unix)]
fn has_other_links(metadata: &fs::Metadata) -> bool {
use std::os::unix::fs::MetadataExt;
metadata.nlink() > 1
}
#[cfg(not(unix))]
fn has_other_links(_metadata: &fs::Metadata) -> bool {
false
}
#[cfg(unix)]
fn keep_owner(file: &File, metadata: &fs::Metadata) -> Result<(), Error> {
use std::os::unix::fs::MetadataExt;
std::os::unix::fs::fchown(file, Some(metadata.uid()), Some(metadata.gid()))
}
#[cfg(not(unix))]
fn keep_owner(_file: &File, _metadata: &fs::Metadata) -> Result<(), Error> {
Ok(())
}
pub fn write_whole(dest: &Path, content: &[u8], replacing: bool) -> Result<(), Error> {
let (temp, mut file) = temp_beside(dest)?;
if let Err(e) = file.write_all(content).and_then(|()| file.sync_all()) {
drop(file);
let _ = remove_file(&temp);
return Err(e);
}
drop(file);
super::transfer::place(&temp, dest, replacing)
}
pub(super) fn temp_beside(dest: &Path) -> Result<(PathBuf, File), Error> {
const NAME_BYTES: usize = 64;
let mut name = String::new();
for character in dest.file_name().unwrap_or_default().to_string_lossy().chars() {
if name.len() + character.len_utf8() > NAME_BYTES {
break;
}
name.push(character);
}
for attempt in 0..1000u32 {
let candidate = dest.with_file_name(format!(".{name}.fm84-{}-{attempt}", std::process::id()));
match File::options().write(true).create_new(true).open(&candidate) {
Ok(file) => return Ok((candidate, file)),
Err(e) if e.kind() == ErrorKind::AlreadyExists => continue,
Err(e) => return Err(e),
}
}
Err(Error::new(ErrorKind::AlreadyExists, format!("Cannot make room beside {}", dest.display())))
}
pub(super) fn clear_link(dest: &Path) -> Result<(), Error> {
match dest.symlink_metadata() {
Ok(metadata) if metadata.file_type().is_symlink() => remove_file(dest).or_else(|_| remove_dir(dest)),
_ => Ok(()),
}
}
pub(super) fn clear_for_link(dest: &Path) -> Result<(), Error> {
match dest.symlink_metadata() {
Ok(metadata) if metadata.is_dir() => Err(Error::new(ErrorKind::AlreadyExists, format!("Cannot replace directory {} with a link", dest.display()))),
Ok(_) => clear_link(dest).and_then(|()| if path_exists(dest) { remove_file(dest) } else { Ok(()) }),
Err(_) => Ok(()),
}
}