use crate::fs::{open, OpenOptions};
#[cfg(any(target_os = "android", target_os = "linux"))]
use rustix::fs::copy_file_range;
#[cfg(any(target_os = "macos", target_os = "ios"))]
use rustix::fs::{
copyfile_state_alloc, copyfile_state_free, copyfile_state_get_copied, copyfile_state_t,
fclonefileat, fcopyfile, CloneFlags, CopyfileFlags,
};
use std::path::Path;
use std::{fs, io};
fn open_from(start: &fs::File, path: &Path) -> io::Result<(fs::File, fs::Metadata)> {
let reader = open(start, path, OpenOptions::new().read(true))?;
let metadata = reader.metadata()?;
if !metadata.is_file() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"the source path is not an existing regular file",
));
}
Ok((reader, metadata))
}
#[cfg(not(target_os = "wasi"))]
fn open_to_and_set_permissions(
start: &fs::File,
path: &Path,
reader_metadata: fs::Metadata,
) -> io::Result<(fs::File, fs::Metadata)> {
use rustix::fs::OpenOptionsExt;
use std::os::unix::fs::PermissionsExt;
let perm = reader_metadata.permissions();
let writer = open(
start,
path,
OpenOptions::new()
.mode(perm.mode())
.write(true)
.create(true)
.truncate(true),
)?;
let writer_metadata = writer.metadata()?;
if writer_metadata.is_file() {
writer.set_permissions(perm)?;
}
Ok((writer, writer_metadata))
}
#[cfg(target_os = "wasi")]
fn open_to_and_set_permissions(
start: &fs::File,
path: &Path,
reader_metadata: fs::Metadata,
) -> io::Result<(fs::File, fs::Metadata)> {
let writer = open(
start,
path,
OpenOptions::new()
.write(true)
.create(true)
.truncate(true),
)?;
let writer_metadata = writer.metadata()?;
Ok((writer, writer_metadata))
}
#[cfg(not(any(
target_os = "linux",
target_os = "android",
target_os = "macos",
target_os = "ios"
)))]
pub(crate) fn copy_impl(
from_start: &fs::File,
from_path: &Path,
to_start: &fs::File,
to_path: &Path,
) -> io::Result<u64> {
let (mut reader, reader_metadata) = open_from(from_start, from_path)?;
let (mut writer, _) = open_to_and_set_permissions(to_start, to_path, reader_metadata)?;
io::copy(&mut reader, &mut writer)
}
#[cfg(any(target_os = "android", target_os = "linux"))]
pub(crate) fn copy_impl(
from_start: &fs::File,
from_path: &Path,
to_start: &fs::File,
to_path: &Path,
) -> io::Result<u64> {
use std::cmp;
use std::sync::atomic::{AtomicBool, Ordering};
static HAS_COPY_FILE_RANGE: AtomicBool = AtomicBool::new(true);
let (mut reader, reader_metadata) = open_from(from_start, from_path)?;
let len = reader_metadata.len();
let (mut writer, _) = open_to_and_set_permissions(to_start, to_path, reader_metadata)?;
let has_copy_file_range = HAS_COPY_FILE_RANGE.load(Ordering::Relaxed);
let mut written = 0_u64;
while written < len {
let copy_result = if has_copy_file_range {
let bytes_to_copy = cmp::min(len - written, usize::MAX as u64);
let copy_result = copy_file_range(&reader, None, &writer, None, bytes_to_copy);
if let Err(copy_err) = copy_result {
match copy_err {
rustix::io::Errno::NOSYS | rustix::io::Errno::PERM => {
HAS_COPY_FILE_RANGE.store(false, Ordering::Relaxed);
}
_ => {}
}
}
copy_result
} else {
Err(rustix::io::Errno::NOSYS)
};
match copy_result {
Ok(ret) => written += ret as u64,
Err(err) => {
match err {
rustix::io::Errno::NOSYS
| rustix::io::Errno::XDEV
| rustix::io::Errno::INVAL
| rustix::io::Errno::PERM => {
assert_eq!(written, 0);
return io::copy(&mut reader, &mut writer);
}
_ => return Err(err.into()),
}
}
}
}
Ok(written)
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[allow(non_upper_case_globals)]
#[allow(unsafe_code)]
pub(crate) fn copy_impl(
from_start: &fs::File,
from_path: &Path,
to_start: &fs::File,
to_path: &Path,
) -> io::Result<u64> {
use std::sync::atomic::{AtomicBool, Ordering};
struct FreeOnDrop(copyfile_state_t);
impl Drop for FreeOnDrop {
fn drop(&mut self) {
unsafe {
copyfile_state_free(self.0).ok();
}
}
}
static HAS_FCLONEFILEAT: AtomicBool = AtomicBool::new(true);
let (reader, reader_metadata) = open_from(from_start, from_path)?;
if HAS_FCLONEFILEAT.load(Ordering::Relaxed) {
let clonefile_result = fclonefileat(&reader, to_start, to_path, CloneFlags::empty());
match clonefile_result {
Ok(_) => return Ok(reader_metadata.len()),
Err(err) => match err {
rustix::io::Errno::NOTSUP | rustix::io::Errno::EXIST | rustix::io::Errno::XDEV => {
()
}
rustix::io::Errno::NOSYS => HAS_FCLONEFILEAT.store(false, Ordering::Relaxed),
_ => return Err(err.into()),
},
}
}
let (writer, writer_metadata) =
open_to_and_set_permissions(to_start, to_path, reader_metadata)?;
let state = {
let state = copyfile_state_alloc()?;
FreeOnDrop(state)
};
let flags = if writer_metadata.is_file() {
CopyfileFlags::ALL
} else {
CopyfileFlags::DATA
};
unsafe {
fcopyfile(&reader, &writer, state.0, flags)?;
Ok(copyfile_state_get_copied(state.0)?)
}
}