#[cfg(not(windows))]
use std::fs::File;
use std::io;
#[cfg(not(windows))]
use std::io::{BufReader, BufWriter};
use std::path::Path;
use crate::util::AlignedBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ReflinkMode {
#[default]
Auto,
Always,
Never,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FsyncMode {
#[default]
Auto,
Always,
Never,
}
const DEFAULT_BUF: usize = 1 << 20;
pub fn copy_file_into(
src: &Path,
tmp: &Path,
reflink: ReflinkMode,
sparse: bool,
) -> io::Result<u64> {
copy_file_into_sized(src, tmp, reflink, sparse, DEFAULT_BUF)
}
pub fn copy_file_into_sized(
src: &Path,
tmp: &Path,
reflink: ReflinkMode,
sparse: bool,
buffer_size: usize,
) -> io::Result<u64> {
#[cfg(not(windows))]
{
copy_file_into_portable(src, tmp, reflink, sparse, buffer_size)
}
#[cfg(windows)]
{
let _ = sparse;
let _ = buffer_size;
crate::io::windows::copy_file_into(src, tmp, reflink)
}
}
#[cfg(not(windows))]
fn copy_file_into_portable(
src: &Path,
tmp: &Path,
reflink: ReflinkMode,
sparse: bool,
buffer_size: usize,
) -> io::Result<u64> {
if reflink != ReflinkMode::Never {
match reflink_copy::reflink(src, tmp) {
Ok(()) => return std::fs::metadata(tmp).map(|m| m.len()),
Err(e) => {
let _ = std::fs::remove_file(tmp);
if reflink == ReflinkMode::Always {
return Err(e);
}
}
}
}
#[cfg(any(
target_os = "linux",
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "solaris"
))]
if sparse {
match sparse_copy_sized(src, tmp, buffer_size) {
Ok(n) => return Ok(n),
Err(_) => {
let _ = std::fs::remove_file(tmp);
}
}
}
#[cfg(target_os = "macos")]
{
match macos_fcopyfile_copy(src, tmp) {
Ok(n) => return Ok(n),
Err(_) => {
let _ = std::fs::remove_file(tmp);
}
}
}
#[cfg(all(target_os = "linux", feature = "io-uring"))]
{
let file_len = std::fs::metadata(src).map(|m| m.len()).unwrap_or(0);
if file_len > 1_048_576 && file_len <= 67_108_864 {
match crate::io::uring::copy_single_large(src, tmp, file_len) {
Ok(n) => return Ok(n),
Err(_) => {
let _ = std::fs::remove_file(tmp);
}
}
}
}
#[cfg(target_os = "linux")]
{
match copy_file_range_all(src, tmp) {
Ok(n) => return Ok(n),
Err(_) => {
let _ = std::fs::remove_file(tmp);
}
}
}
buffered_copy_sized(src, tmp, buffer_size)
}
#[cfg(target_os = "macos")]
fn macos_fcopyfile_copy(src: &Path, tmp: &Path) -> io::Result<u64> {
use std::os::unix::io::AsRawFd;
let infile = File::open(src)?;
let len = infile.metadata()?.len();
if len >= 8 * 1024 * 1024 {
let _ = crate::io::macos::set_nocache(infile.as_raw_fd());
}
let outfile = std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(tmp)?;
if len >= 8 * 1024 * 1024 {
let _ = crate::io::macos::set_nocache(outfile.as_raw_fd());
}
crate::io::macos::copy_file_with_fcopyfile(infile.as_raw_fd(), outfile.as_raw_fd())
}
#[cfg(any(
target_os = "linux",
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "solaris"
))]
fn sparse_copy_sized(src: &Path, tmp: &Path, buffer_size: usize) -> io::Result<u64> {
use std::os::unix::fs::FileExt;
use rustix::fs::SeekFrom;
let infile = File::open(src)?;
let outfile = File::create(tmp)?;
let len = infile.metadata()?.len();
outfile.set_len(len)?;
let mut offset = 0_u64;
let mut buf = AlignedBuf::new(buffer_size);
let mut bytes_written = 0_u64;
while offset < len {
let data = match rustix::fs::seek(&infile, SeekFrom::Data(offset)) {
Ok(data) => data,
Err(error) if error == rustix::io::Errno::NXIO => break,
Err(error) => return Err(io::Error::from(error)),
};
let hole = rustix::fs::seek(&infile, SeekFrom::Hole(data))?.min(len);
let mut position = data;
while position < hole {
let wanted = usize::try_from((hole - position).min(buf.len() as u64))
.unwrap_or(buf.len());
let read = infile.read_at(&mut buf[..wanted], position)?;
if read == 0 {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"short sparse extent read",
));
}
let mut written = 0;
while written < read {
let count =
outfile.write_at(&buf[written..read], position + written as u64)?;
if count == 0 {
return Err(io::Error::new(
io::ErrorKind::WriteZero,
"short sparse extent write",
));
}
written += count;
}
position += read as u64;
}
bytes_written = hole;
offset = hole;
}
outfile.set_len(bytes_written)?;
Ok(bytes_written)
}
#[cfg(not(windows))]
fn advise_sequential(file: &File, len: u64) {
#[cfg(target_os = "linux")]
{
use rustix::fs::{Advice, fadvise};
if len >= 8 * 1024 * 1024 {
let span = std::num::NonZeroU64::new(len);
let _ = fadvise(file, 0, span, Advice::Sequential);
let _ = fadvise(file, 0, span, Advice::WillNeed);
}
}
#[cfg(not(target_os = "linux"))]
let _ = (file, len);
}
#[cfg(not(windows))]
fn buffered_copy_sized(src: &Path, tmp: &Path, buffer_size: usize) -> io::Result<u64> {
let reader = File::open(src)?;
let writer = File::create(tmp)?;
let file_len = reader.metadata()?.len();
advise_sequential(&reader, file_len);
#[cfg(target_os = "macos")]
if file_len >= 8 * 1024 * 1024 {
use std::os::unix::io::AsRawFd;
let _ = crate::io::macos::set_nocache(reader.as_raw_fd());
let _ = crate::io::macos::set_nocache(writer.as_raw_fd());
}
let mut r = BufReader::with_capacity(buffer_size, reader);
let mut w = BufWriter::with_capacity(buffer_size, writer);
let n = io::copy(&mut r, &mut w)?;
w.into_inner()?;
Ok(n)
}
#[cfg(target_os = "linux")]
fn copy_file_range_all(src: &Path, tmp: &Path) -> io::Result<u64> {
let infile = File::open(src)?;
let outfile = File::create(tmp)?;
let len = infile.metadata()?.len();
advise_sequential(&infile, len);
let mut remaining = len;
while remaining > 0 {
let chunk = usize::try_from(remaining.min(1 << 30)).unwrap_or(usize::MAX);
let copied = rustix::fs::copy_file_range(&infile, None, &outfile, None, chunk)?;
if copied == 0 {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"source file truncated during copy",
));
}
remaining -= copied as u64;
}
Ok(len - remaining)
}