#![warn(clippy::cargo)]
#[cfg(unix)]
use std::os::unix::fs::{chown, MetadataExt as _, PermissionsExt as _};
use std::{fs::File, io, path::Path};
use filetime::{set_file_handle_times, set_file_times, FileTime};
const FILE_FLAG_BACKUP_SEMANTICS: u32 = 0x2000000;
#[inline]
fn open_file_for_metadata(path: &Path) -> io::Result<File> {
let mut opts = std::fs::OpenOptions::new();
opts.read(true);
#[cfg(windows)]
{
use std::os::windows::fs::OpenOptionsExt;
opts.access_mode(0x0100)
.share_mode(0x7)
.custom_flags(FILE_FLAG_BACKUP_SEMANTICS);
}
opts.open(path)
}
#[cfg(unix)]
fn copy_permission_impl(
to_path: &Path,
to_file: Option<&File>,
from_meta: &std::fs::Metadata,
to_meta: &std::fs::Metadata,
) -> io::Result<()> {
let from_gid = from_meta.gid();
let to_gid = to_meta.gid();
let mut perms = from_meta.permissions();
perms.set_mode(perms.mode() & 0o0777);
if from_gid != to_gid && chown(to_path, None, Some(from_gid)).is_err() {
let new_perms = (perms.mode() & 0o0707) | ((perms.mode() & 0o07) << 3);
perms.set_mode(new_perms);
}
if let Some(file) = to_file {
file.set_permissions(perms)
} else {
std::fs::set_permissions(to_path, perms)
}
}
#[cfg(windows)]
#[inline]
fn copy_permission_impl(
to_path: &Path,
to_file: Option<&File>,
from_meta: &std::fs::Metadata,
_to_meta: &std::fs::Metadata,
) -> io::Result<()> {
let permissions = from_meta.permissions();
if let Some(file) = to_file {
file.set_permissions(permissions)
} else {
std::fs::set_permissions(to_path, permissions)
}
}
#[inline]
fn copy_time_path(to: &Path, from_meta: &std::fs::Metadata) -> io::Result<()> {
let atime = FileTime::from_last_access_time(from_meta);
let mtime = FileTime::from_last_modification_time(from_meta);
set_file_times(to, atime, mtime)
}
pub fn copy_metadata(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
let (from, to) = (from.as_ref(), to.as_ref());
let from_meta = std::fs::metadata(from)?;
match open_file_for_metadata(to) {
Ok(to_file) => {
let to_meta = to_file.metadata()?;
let atime = FileTime::from_last_access_time(&from_meta);
let mtime = FileTime::from_last_modification_time(&from_meta);
if let Err(e) = set_file_handle_times(&to_file, Some(atime), Some(mtime)) {
if e.kind() == io::ErrorKind::PermissionDenied {
copy_time_path(to, &from_meta)?;
} else {
return Err(e);
}
}
copy_permission_impl(to, Some(&to_file), &from_meta, &to_meta)
}
Err(_) => {
let to_meta = std::fs::metadata(to)?;
let res = copy_time_path(to, &from_meta);
copy_permission_impl(to, None, &from_meta, &to_meta)?;
if let Err(err) = res {
if err.kind() == io::ErrorKind::PermissionDenied {
copy_time_path(to, &from_meta)?;
} else {
return Err(err);
}
}
Ok(())
}
}
}
pub fn copy_permission(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
let (from, to) = (from.as_ref(), to.as_ref());
let from_meta = std::fs::metadata(from)?;
match open_file_for_metadata(to) {
Ok(to_file) => {
let to_meta = to_file.metadata()?;
copy_permission_impl(to, Some(&to_file), &from_meta, &to_meta)
}
Err(_) => {
let to_meta = std::fs::metadata(to)?;
copy_permission_impl(to, None, &from_meta, &to_meta)
}
}
}
pub fn copy_time(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
let (from, to) = (from.as_ref(), to.as_ref());
let from_meta = std::fs::metadata(from)?;
let atime = FileTime::from_last_access_time(&from_meta);
let mtime = FileTime::from_last_modification_time(&from_meta);
if let Ok(to_file) = open_file_for_metadata(to) {
if let Err(e) = set_file_handle_times(&to_file, Some(atime), Some(mtime)) {
if e.kind() == io::ErrorKind::PermissionDenied {
return set_file_times(to, atime, mtime);
}
return Err(e);
}
Ok(())
} else {
set_file_times(to, atime, mtime)
}
}