use core::sync::atomic::{AtomicI32, Ordering};
#[cfg(not(windows))]
use crate::E;
use crate::Fd;
#[cfg(not(any(target_os = "linux", target_os = "android")))]
use crate::Tag;
static debug: bun_core::output::ScopedLogger =
bun_core::output::ScopedLogger::new("copy_file", bun_core::output::Visibility::Hidden);
#[cfg(windows)]
pub type InputType<'a> = &'a bun_core::WStr; #[cfg(not(windows))]
pub type InputType<'a> = Fd;
bitflags::bitflags! {
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct LinuxCopyFileState: u8 {
const HAS_SEEN_EXDEV = 1 << 0;
const HAS_IOCTL_FICLONE_FAILED = 1 << 1;
const HAS_COPY_FILE_RANGE_FAILED = 1 << 2;
const HAS_SENDFILE_FAILED = 1 << 3;
}
}
impl Default for LinuxCopyFileState {
fn default() -> Self {
Self::empty()
}
}
#[derive(Default, Clone, Copy)]
pub struct EmptyCopyFileState;
#[cfg(any(target_os = "linux", target_os = "android"))]
pub type CopyFileState = LinuxCopyFileState;
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub type CopyFileState = EmptyCopyFileState;
type CopyFileReturnType = crate::Result<()>;
pub fn copy_file_with_state(
in_: InputType<'_>,
out: InputType<'_>,
copy_file_state: &mut CopyFileState,
) -> CopyFileReturnType {
#[cfg(not(any(target_os = "linux", target_os = "android")))]
let _ = copy_file_state;
#[cfg(target_os = "macos")]
{
unsafe extern "C" {
safe fn fcopyfile(
from: libc::c_int,
to: libc::c_int,
state: Option<core::ptr::NonNull<core::ffi::c_void>>,
flags: u32,
) -> libc::c_int;
}
let rc = fcopyfile(in_.native(), out.native(), None, libc::COPYFILE_DATA);
match crate::get_errno(rc) {
E::SUCCESS => return Ok(()),
E::EOPNOTSUPP => {}
e => return Err(crate::Error::from_code(e, Tag::copyfile)),
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
{
if can_use_ioctl_ficlone()
&& !copy_file_state.contains(LinuxCopyFileState::HAS_SEEN_EXDEV)
&& !copy_file_state.contains(LinuxCopyFileState::HAS_IOCTL_FICLONE_FAILED)
{
let rc = crate::linux::ioctl_ficlone(out, in_);
crate::syslog!("ioctl_ficlone({}, {}) = {}", in_, out, rc);
match crate::get_errno(rc) {
E::SUCCESS => return Ok(()),
E::EXDEV => {
copy_file_state.insert(LinuxCopyFileState::HAS_SEEN_EXDEV);
}
E::EINTR => {}
E::EACCES | E::EBADF | E::EINVAL | E::ENOTSUP | E::ENOSYS | E::EPERM => {
bun_core::scoped_log!(debug, "ioctl_ficlonerange is NOT supported");
CAN_USE_IOCTL_FICLONE_.store(-1, Ordering::Relaxed);
copy_file_state.insert(LinuxCopyFileState::HAS_IOCTL_FICLONE_FAILED);
}
_ => {
copy_file_state.insert(LinuxCopyFileState::HAS_IOCTL_FICLONE_FAILED);
}
}
}
let mut offset: u64 = 0;
'cfr_loop: loop {
let amt = copy_file_range(
in_.native(),
out.native(),
(i32::MAX - 1) as usize,
0,
copy_file_state,
)?;
if amt == 0 {
break 'cfr_loop;
}
offset += amt as u64;
}
let _ = offset;
return Ok(());
}
#[cfg(target_os = "freebsd")]
{
loop {
let rc = unsafe {
libc::copy_file_range(
in_.native(),
core::ptr::null_mut(),
out.native(),
core::ptr::null_mut(),
(i32::MAX - 1) as usize,
0,
)
};
crate::syslog!(
"copy_file_range({}, {}) = {}",
in_.native(),
out.native(),
rc
);
match crate::get_errno(rc) {
E::SUCCESS => {
if rc == 0 {
return Ok(());
}
}
E::EXDEV | E::EINVAL | E::EOPNOTSUPP | E::EBADF => break,
E::EINTR => continue,
e => return Err(crate::Error::from_code(e, Tag::copy_file_range)),
}
}
}
#[cfg(windows)]
{
let rc = unsafe { crate::windows::CopyFileW(in_.as_ptr(), out.as_ptr(), 0) };
if rc == 0 {
return Err(crate::Error::from_code(
crate::windows::get_last_errno(),
Tag::copyfile,
));
}
return Ok(());
}
#[cfg(not(any(target_os = "linux", target_os = "android", windows)))]
{
loop {
{
let amt =
copy_file_read_write_loop(in_.native(), out.native(), (i32::MAX - 1) as usize)?;
if amt == 0 {
break;
}
}
}
return Ok(());
}
}
pub fn copy_file(in_: InputType<'_>, out: InputType<'_>) -> CopyFileReturnType {
let mut state: CopyFileState = CopyFileState::default();
copy_file_with_state(in_, out, &mut state)
}
static CAN_USE_COPY_FILE_RANGE: AtomicI32 = AtomicI32::new(0);
#[inline]
pub fn disable_copy_file_range_syscall() {
#[cfg(not(any(target_os = "linux", target_os = "android")))]
{
return;
}
#[cfg(any(target_os = "linux", target_os = "android"))]
CAN_USE_COPY_FILE_RANGE.store(-1, Ordering::Relaxed);
}
pub fn can_use_copy_file_range_syscall() -> bool {
let result = CAN_USE_COPY_FILE_RANGE.load(Ordering::Relaxed);
if result == 0 {
if bun_core::env_var::BUN_CONFIG_DISABLE_COPY_FILE_RANGE
.get()
.unwrap_or(false)
{
bun_core::scoped_log!(
debug,
"copy_file_range is disabled by BUN_CONFIG_DISABLE_COPY_FILE_RANGE"
);
CAN_USE_COPY_FILE_RANGE.store(-1, Ordering::Relaxed);
return false;
}
if kernel_at_least(4, 5) {
bun_core::scoped_log!(debug, "copy_file_range is supported");
CAN_USE_COPY_FILE_RANGE.store(1, Ordering::Relaxed);
return true;
} else {
bun_core::scoped_log!(debug, "copy_file_range is NOT supported");
CAN_USE_COPY_FILE_RANGE.store(-1, Ordering::Relaxed);
return false;
}
}
result == 1
}
pub static CAN_USE_IOCTL_FICLONE_: AtomicI32 = AtomicI32::new(0);
#[inline]
pub fn disable_ioctl_ficlone() {
#[cfg(not(any(target_os = "linux", target_os = "android")))]
{
return;
}
#[cfg(any(target_os = "linux", target_os = "android"))]
CAN_USE_IOCTL_FICLONE_.store(-1, Ordering::Relaxed);
}
pub fn can_use_ioctl_ficlone() -> bool {
let result = CAN_USE_IOCTL_FICLONE_.load(Ordering::Relaxed);
if result == 0 {
if bun_core::env_var::BUN_CONFIG_DISABLE_ioctl_ficlonerange
.get()
.unwrap_or(false)
{
bun_core::scoped_log!(
debug,
"ioctl_ficlonerange is disabled by BUN_CONFIG_DISABLE_ioctl_ficlonerange"
);
CAN_USE_IOCTL_FICLONE_.store(-1, Ordering::Relaxed);
return false;
}
if kernel_at_least(4, 5) {
bun_core::scoped_log!(debug, "ioctl_ficlonerange is supported");
CAN_USE_IOCTL_FICLONE_.store(1, Ordering::Relaxed);
return true;
} else {
bun_core::scoped_log!(debug, "ioctl_ficlonerange is NOT supported");
CAN_USE_IOCTL_FICLONE_.store(-1, Ordering::Relaxed);
return false;
}
}
result == 1
}
#[allow(non_camel_case_types)]
type fd_t = core::ffi::c_int;
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn copy_file_range(
in_: fd_t,
out: fd_t,
len: usize,
flags: u32,
copy_file_state: &mut CopyFileState,
) -> crate::Result<usize> {
if can_use_copy_file_range_syscall()
&& !copy_file_state.contains(LinuxCopyFileState::HAS_SEEN_EXDEV)
&& !copy_file_state.contains(LinuxCopyFileState::HAS_COPY_FILE_RANGE_FAILED)
{
loop {
let rc = unsafe {
crate::linux::copy_file_range(
in_,
core::ptr::null_mut(),
out,
core::ptr::null_mut(),
len,
flags,
)
};
crate::syslog!("copy_file_range({}, {}, {}) = {}", in_, out, len, rc);
match crate::get_errno(rc) {
E::SUCCESS => return Ok(rc as usize),
E::EINVAL => {
copy_file_state.insert(LinuxCopyFileState::HAS_COPY_FILE_RANGE_FAILED);
}
E::EXDEV => {
copy_file_state.insert(LinuxCopyFileState::HAS_SEEN_EXDEV);
copy_file_state.insert(LinuxCopyFileState::HAS_COPY_FILE_RANGE_FAILED);
}
E::ENOTSUP | E::ENOSYS => {
copy_file_state.insert(LinuxCopyFileState::HAS_COPY_FILE_RANGE_FAILED);
bun_core::scoped_log!(debug, "copy_file_range is NOT supported");
CAN_USE_COPY_FILE_RANGE.store(-1, Ordering::Relaxed);
}
E::EINTR => continue,
_ => {
copy_file_state.insert(LinuxCopyFileState::HAS_COPY_FILE_RANGE_FAILED);
}
}
break;
}
}
while !copy_file_state.contains(LinuxCopyFileState::HAS_SENDFILE_FAILED) {
let rc = unsafe { crate::linux::sendfile(out, in_, core::ptr::null_mut(), len) };
crate::syslog!("sendfile({}, {}, {}) = {}", in_, out, len, rc);
match crate::get_errno(rc) {
E::SUCCESS => return Ok(rc as usize),
E::EINTR => continue,
E::EINVAL => {
copy_file_state.insert(LinuxCopyFileState::HAS_SENDFILE_FAILED);
}
E::EXDEV => {
copy_file_state.insert(LinuxCopyFileState::HAS_SEEN_EXDEV);
copy_file_state.insert(LinuxCopyFileState::HAS_SENDFILE_FAILED);
}
E::ENOTSUP | E::ENOSYS => {
copy_file_state.insert(LinuxCopyFileState::HAS_SENDFILE_FAILED);
}
_ => {
copy_file_state.insert(LinuxCopyFileState::HAS_SENDFILE_FAILED);
}
}
break;
}
copy_file_read_write_loop(in_, out, len)
}
pub fn copy_file_read_write_loop(in_: fd_t, out: fd_t, len: usize) -> crate::Result<usize> {
let mut buf = [0u8; 8 * 4096];
let adjusted_count = buf.len().min(len);
match crate::read(Fd::from_native(in_ as _), &mut buf[0..adjusted_count]) {
Ok(amt_read) => {
let mut amt_written: usize = 0;
if amt_read == 0 {
return Ok(0);
}
while amt_written < amt_read {
{
let wrote =
crate::write(Fd::from_native(out as _), &buf[amt_written..amt_read])?;
if wrote == 0 {
return Ok(amt_written);
}
amt_written += wrote;
}
}
if amt_read == 0 {
return Ok(0);
}
Ok(amt_read)
}
Err(err) => Err(err),
}
}
#[inline]
fn kernel_at_least(major: u32, minor: u32) -> bool {
let v = bun_core::linux_kernel_version();
(v.major, v.minor, v.patch) >= (major, minor, 0)
}