#![warn(clippy::cargo)]
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt as _;
#[cfg(unix)]
use std::os::unix::io::AsRawFd;
use std::{fs::File, io, path::Path};
#[cfg(feature = "copy-time")]
use filetime::{set_file_handle_times, FileTime};
const FILE_FLAG_BACKUP_SEMANTICS: u32 = 0x2000000;
const FILE_FLAG_OPEN_REPARSE_POINT: u32 = 0x00200000;
#[inline]
fn open_file_for_metadata(path: &Path, is_source: bool) -> io::Result<File> {
let mut opts = std::fs::OpenOptions::new();
#[cfg(windows)]
{
use std::os::windows::fs::{MetadataExt, OpenOptionsExt};
opts.read(true);
let access = if is_source { 0x0080 } else { 0x0100 };
opts.access_mode(access)
.share_mode(0x7) .custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT);
let f = opts.open(path)?;
if f.metadata()?.file_attributes() & 0x400 != 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Symlinks not supported",
));
}
Ok(f)
}
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
let flags = libc::O_NONBLOCK | libc::O_NOFOLLOW;
opts.custom_flags(flags);
opts.read(true);
match opts.open(path) {
Ok(f) => Ok(f),
Err(e) if e.raw_os_error() == Some(libc::ELOOP) => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Symlinks not supported",
)),
Err(e) if e.kind() == io::ErrorKind::PermissionDenied && !is_source => {
let mut write_opts = std::fs::OpenOptions::new();
write_opts.write(true).custom_flags(flags);
match write_opts.open(path) {
Ok(f) => Ok(f),
Err(we) if we.raw_os_error() == Some(libc::ELOOP) => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Symlinks not supported",
)),
Err(we) => Err(we),
}
}
Err(e) => Err(e),
}
}
}
#[cfg(unix)]
fn copy_permission_impl(
to_file: &File,
from_meta: &std::fs::Metadata,
to_meta: &std::fs::Metadata,
) -> io::Result<()> {
use std::os::unix::fs::MetadataExt;
let from_gid = from_meta.gid();
let to_gid = to_meta.gid();
let from_uid = from_meta.uid();
let mut perms = from_meta.permissions();
perms.set_mode(perms.mode() & 0o0777);
if from_gid != to_gid {
let fd = to_file.as_raw_fd();
let res = unsafe { libc::fchown(fd, from_uid, from_gid) };
if res != 0 {
let new_perms = (perms.mode() & 0o0707) | ((perms.mode() & 0o07) << 3);
perms.set_mode(new_perms);
}
}
to_file.set_permissions(perms)
}
#[cfg(windows)]
#[inline]
fn copy_permission_impl(
to_file: &File,
from_meta: &std::fs::Metadata,
_to_meta: &std::fs::Metadata,
) -> io::Result<()> {
to_file.set_permissions(from_meta.permissions())
}
pub fn copy_metadata(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
let from_file = open_file_for_metadata(from.as_ref(), true)?;
let from_meta = from_file.metadata()?;
let to_file = open_file_for_metadata(to.as_ref(), false)?;
let to_meta = to_file.metadata()?;
#[cfg(feature = "copy-time")]
{
let atime = FileTime::from_last_access_time(&from_meta);
let mtime = FileTime::from_last_modification_time(&from_meta);
set_file_handle_times(&to_file, Some(atime), Some(mtime))?;
}
copy_permission_impl(&to_file, &from_meta, &to_meta)
}
pub fn copy_permission(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
let from_file = open_file_for_metadata(from.as_ref(), true)?;
let from_meta = from_file.metadata()?;
let to_file = open_file_for_metadata(to.as_ref(), false)?;
let to_meta = to_file.metadata()?;
copy_permission_impl(&to_file, &from_meta, &to_meta)
}
#[cfg(feature = "copy-time")]
pub fn copy_time(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
let from_file = open_file_for_metadata(from.as_ref(), true)?;
let from_meta = from_file.metadata()?;
let atime = FileTime::from_last_access_time(&from_meta);
let mtime = FileTime::from_last_modification_time(&from_meta);
let to_file = open_file_for_metadata(to.as_ref(), false)?;
set_file_handle_times(&to_file, Some(atime), Some(mtime))
}