From 02c35663f398b562e30e5688351bcd7feae45aee Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Fri, 24 May 2024 16:55:37 -0700 Subject: [PATCH 1/9] Optimize cloning files for macs --- cli/util/fs.rs | 82 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 5 deletions(-) diff --git a/cli/util/fs.rs b/cli/util/fs.rs index fdc7855e6230b7..e2434fc0e26714 100644 --- a/cli/util/fs.rs +++ b/cli/util/fs.rs @@ -492,10 +492,71 @@ pub async fn remove_dir_all_if_exists(path: &Path) -> std::io::Result<()> { } } -/// Copies a directory to another directory. +mod clone_dir_imp { + use deno_core::error::AnyError; + + use super::copy_dir_recursive; + use std::path::Path; + + cfg_if::cfg_if! { + if #[cfg(target_vendor = "apple")] { + use super::copy_dir_recursive_with; + use std::os::unix::ffi::OsStrExt; + fn clonefile(from: &Path, to: &Path) -> std::io::Result<()> { + let from = std::ffi::CString::new(from.as_os_str().as_bytes())?; + let to = std::ffi::CString::new(to.as_os_str().as_bytes())?; + let ret = unsafe { libc::clonefile(from.as_ptr(), to.as_ptr(), 0) }; + if ret != 0 { + return Err(std::io::Error::last_os_error()); + } + Ok(()) + } + + pub(super) fn clone_dir_recursive( + from: &Path, + to: &Path, + ) -> Result<(), AnyError> { + if let Some(parent) = to.parent() { + std::fs::create_dir_all(parent)?; + } + if let Err(err) = clonefile(from, to) { + if err.kind() != std::io::ErrorKind::AlreadyExists { + log::warn!("Failed to clone dir {:?} to {:?} via clonefile: {}", from, to, err); + } + if let Err(err) = copy_dir_recursive_with(from, to, |src, dst| clonefile(src, dst).map_err(Into::into)) { + log::warn!("Failed to clone dir {:?} to {:?}: {}", from, to, err); + copy_dir_recursive(from, to)?; + } + } + + Ok(()) + } + } else { + use super::hard_link_dir_recursive; + pub(super) fn clone_dir_recursive(from: &Path, to: &Path) -> Result<(), AnyError> { + if let Err(e) = hard_link_dir_recursive(from, to) { + log::debug!("Failed to hard link dir {:?} to {:?}: {}", from, to, e); + copy_dir_recursive(from, to)?; + } + } + } + } +} + +/// Clones a directory to another directory. The exact method +/// is not guaranteed - it may be a hardlink, copy, or other platform-specific +/// operation. /// /// Note: Does not handle symlinks. -pub fn copy_dir_recursive(from: &Path, to: &Path) -> Result<(), AnyError> { +pub fn clone_dir_recursive(from: &Path, to: &Path) -> Result<(), AnyError> { + clone_dir_imp::clone_dir_recursive(from, to) +} + +fn copy_dir_recursive_with( + from: &Path, + to: &Path, + file_copier: impl Fn(&Path, &Path) -> Result<(), AnyError>, +) -> Result<(), AnyError> { std::fs::create_dir_all(to) .with_context(|| format!("Creating {}", to.display()))?; let read_dir = std::fs::read_dir(from) @@ -512,15 +573,26 @@ pub fn copy_dir_recursive(from: &Path, to: &Path) -> Result<(), AnyError> { format!("Dir {} to {}", new_from.display(), new_to.display()) })?; } else if file_type.is_file() { - std::fs::copy(&new_from, &new_to).with_context(|| { - format!("Copying {} to {}", new_from.display(), new_to.display()) - })?; + file_copier(&new_from, &new_to)?; } } Ok(()) } +/// Copies a directory to another directory. +/// +/// Note: Does not handle symlinks. +pub fn copy_dir_recursive(from: &Path, to: &Path) -> Result<(), AnyError> { + copy_dir_recursive_with(from, to, |from, to| { + std::fs::copy(from, to).with_context(|| { + format!("Copying {} to {}", from.display(), to.display()) + })?; + Ok(()) + }) +} + +#[allow(dead_code)] /// Hardlinks the files in one directory to another directory. /// /// Note: Does not handle symlinks. From 95b9fc43e31bef452cda66353f37f77689508da1 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Fri, 24 May 2024 17:05:07 -0700 Subject: [PATCH 2/9] fallback if clonefile fails --- cli/util/fs.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/cli/util/fs.rs b/cli/util/fs.rs index e2434fc0e26714..621487851c959b 100644 --- a/cli/util/fs.rs +++ b/cli/util/fs.rs @@ -493,7 +493,7 @@ pub async fn remove_dir_all_if_exists(path: &Path) -> std::io::Result<()> { } mod clone_dir_imp { - use deno_core::error::AnyError; + use deno_core::{anyhow::Context, error::AnyError}; use super::copy_dir_recursive; use std::path::Path; @@ -520,10 +520,26 @@ mod clone_dir_imp { std::fs::create_dir_all(parent)?; } if let Err(err) = clonefile(from, to) { + // clonefile won't overwrite existing files, so if the dir exists + // we need to handle it recursively. if err.kind() != std::io::ErrorKind::AlreadyExists { log::warn!("Failed to clone dir {:?} to {:?} via clonefile: {}", from, to, err); } - if let Err(err) = copy_dir_recursive_with(from, to, |src, dst| clonefile(src, dst).map_err(Into::into)) { + if let Err(err) = copy_dir_recursive_with(from, to, |src, dst| { + let res = clonefile(src, dst); + if let Err(err) = res { + if err.kind() == std::io::ErrorKind::AlreadyExists { + // If the file already exists, try to copy it instead. + std::fs::copy(src, dst).with_context(|| { + format!("Copying {} to {}", from.display(), to.display()) + })?; + } else { + return Err(err.into()); + } + } + + Ok(()) + }) { log::warn!("Failed to clone dir {:?} to {:?}: {}", from, to, err); copy_dir_recursive(from, to)?; } From 0e6c1d64376c0fe646c9d2b1a9d0a134a94a7ba0 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Fri, 24 May 2024 17:54:30 -0700 Subject: [PATCH 3/9] Fast fallback --- Cargo.lock | 1 + cli/Cargo.toml | 1 + cli/util/fs.rs | 62 ++++++++++++++++++++++---------------------------- 3 files changed, 29 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ce435af30c0d1..60536ecb6c936a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1081,6 +1081,7 @@ dependencies = [ "bincode", "bytes", "cache_control", + "cfg-if", "chrono", "clap", "clap_complete", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 8c033f55a84d30..8800149a46a65a 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -152,6 +152,7 @@ uuid = { workspace = true, features = ["serde"] } walkdir = "=2.3.2" zeromq.workspace = true zstd.workspace = true +cfg-if = "1.0.0" [target.'cfg(windows)'.dependencies] junction.workspace = true diff --git a/cli/util/fs.rs b/cli/util/fs.rs index 621487851c959b..b3d49d369793a2 100644 --- a/cli/util/fs.rs +++ b/cli/util/fs.rs @@ -493,14 +493,13 @@ pub async fn remove_dir_all_if_exists(path: &Path) -> std::io::Result<()> { } mod clone_dir_imp { - use deno_core::{anyhow::Context, error::AnyError}; + use deno_core::error::AnyError; use super::copy_dir_recursive; use std::path::Path; cfg_if::cfg_if! { if #[cfg(target_vendor = "apple")] { - use super::copy_dir_recursive_with; use std::os::unix::ffi::OsStrExt; fn clonefile(from: &Path, to: &Path) -> std::io::Result<()> { let from = std::ffi::CString::new(from.as_os_str().as_bytes())?; @@ -512,6 +511,19 @@ mod clone_dir_imp { Ok(()) } + /// Clones a directories contents to another directory. + pub(super) fn clone_dir_shallow(from: &Path, to: &Path) -> std::io::Result<()> { + let read_dir = std::fs::read_dir(from)?; + for entry in read_dir { + let entry = entry?; + let new_from = from.join(entry.file_name()); + let new_to = to.join(entry.file_name()); + + clonefile(&new_from, &new_to)?; + } + Ok(()) + } + pub(super) fn clone_dir_recursive( from: &Path, to: &Path, @@ -519,28 +531,19 @@ mod clone_dir_imp { if let Some(parent) = to.parent() { std::fs::create_dir_all(parent)?; } + // Try to clone the whole directory if let Err(err) = clonefile(from, to) { // clonefile won't overwrite existing files, so if the dir exists // we need to handle it recursively. if err.kind() != std::io::ErrorKind::AlreadyExists { log::warn!("Failed to clone dir {:?} to {:?} via clonefile: {}", from, to, err); } - if let Err(err) = copy_dir_recursive_with(from, to, |src, dst| { - let res = clonefile(src, dst); - if let Err(err) = res { - if err.kind() == std::io::ErrorKind::AlreadyExists { - // If the file already exists, try to copy it instead. - std::fs::copy(src, dst).with_context(|| { - format!("Copying {} to {}", from.display(), to.display()) - })?; - } else { - return Err(err.into()); - } + // Try to clone its contents one level down + if let Err(err) = clone_dir_shallow(from, to) { + if err.kind() != std::io::ErrorKind::AlreadyExists { + log::warn!("Failed to clone dir {:?} to {:?} via clonefile: {}", from, to, err); } - - Ok(()) - }) { - log::warn!("Failed to clone dir {:?} to {:?}: {}", from, to, err); + // If that fails, fall back to copying recursively copy_dir_recursive(from, to)?; } } @@ -568,11 +571,10 @@ pub fn clone_dir_recursive(from: &Path, to: &Path) -> Result<(), AnyError> { clone_dir_imp::clone_dir_recursive(from, to) } -fn copy_dir_recursive_with( - from: &Path, - to: &Path, - file_copier: impl Fn(&Path, &Path) -> Result<(), AnyError>, -) -> Result<(), AnyError> { +/// Copies a directory to another directory. +/// +/// Note: Does not handle symlinks. +pub fn copy_dir_recursive(from: &Path, to: &Path) -> Result<(), AnyError> { std::fs::create_dir_all(to) .with_context(|| format!("Creating {}", to.display()))?; let read_dir = std::fs::read_dir(from) @@ -589,25 +591,15 @@ fn copy_dir_recursive_with( format!("Dir {} to {}", new_from.display(), new_to.display()) })?; } else if file_type.is_file() { - file_copier(&new_from, &new_to)?; + std::fs::copy(&new_from, &new_to).with_context(|| { + format!("Copying {} to {}", new_from.display(), new_to.display()) + })?; } } Ok(()) } -/// Copies a directory to another directory. -/// -/// Note: Does not handle symlinks. -pub fn copy_dir_recursive(from: &Path, to: &Path) -> Result<(), AnyError> { - copy_dir_recursive_with(from, to, |from, to| { - std::fs::copy(from, to).with_context(|| { - format!("Copying {} to {}", from.display(), to.display()) - })?; - Ok(()) - }) -} - #[allow(dead_code)] /// Hardlinks the files in one directory to another directory. /// From 049d9a1d7d6941c09cd541284bf34d117d2d47b6 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Fri, 24 May 2024 17:59:30 -0700 Subject: [PATCH 4/9] Use new fn --- cli/Cargo.toml | 2 +- cli/npm/managed/cache.rs | 4 ++-- cli/npm/managed/resolvers/local.rs | 18 ++++-------------- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 8800149a46a65a..3708104c485cff 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -86,6 +86,7 @@ base64.workspace = true bincode = "=1.3.3" bytes.workspace = true cache_control.workspace = true +cfg-if = "1.0.0" chrono = { workspace = true, features = ["now"] } clap = { version = "=4.4.17", features = ["env", "string"] } clap_complete = "=4.4.7" @@ -152,7 +153,6 @@ uuid = { workspace = true, features = ["serde"] } walkdir = "=2.3.2" zeromq.workspace = true zstd.workspace = true -cfg-if = "1.0.0" [target.'cfg(windows)'.dependencies] junction.workspace = true diff --git a/cli/npm/managed/cache.rs b/cli/npm/managed/cache.rs index 4056c97ad9ed16..b627d4e1977cd1 100644 --- a/cli/npm/managed/cache.rs +++ b/cli/npm/managed/cache.rs @@ -23,7 +23,7 @@ use crate::args::CacheSetting; use crate::http_util::HttpClient; use crate::npm::common::maybe_auth_header_for_npm_registry; use crate::npm::NpmCacheDir; -use crate::util::fs::hard_link_dir_recursive; +use crate::util::fs::clone_dir_recursive; use crate::util::progress_bar::ProgressBar; use super::tarball::verify_and_extract_tarball; @@ -195,7 +195,7 @@ impl NpmCache { // it seems Windows does an "AccessDenied" error when moving a // directory with hard links, so that's why this solution is done with_folder_sync_lock(&folder_id.nv, &package_folder, || { - hard_link_dir_recursive(&original_package_folder, &package_folder) + clone_dir_recursive(&original_package_folder, &package_folder) })?; Ok(()) } diff --git a/cli/npm/managed/resolvers/local.rs b/cli/npm/managed/resolvers/local.rs index 055fdfb23a2318..5362d2f611122c 100644 --- a/cli/npm/managed/resolvers/local.rs +++ b/cli/npm/managed/resolvers/local.rs @@ -17,6 +17,7 @@ use crate::cache::CACHE_PERM; use crate::npm::cache_dir::mixed_case_package_name_decode; use crate::util::fs::atomic_write_file; use crate::util::fs::canonicalize_path_maybe_not_exists_with_fs; +use crate::util::fs::clone_dir_recursive; use crate::util::fs::symlink_dir; use crate::util::fs::LaxSingleProcessFsFlag; use crate::util::progress_bar::ProgressBar; @@ -44,8 +45,6 @@ use serde::Deserialize; use serde::Serialize; use crate::npm::cache_dir::mixed_case_package_name_encode; -use crate::util::fs::copy_dir_recursive; -use crate::util::fs::hard_link_dir_recursive; use super::super::super::common::types_package_name; use super::super::cache::NpmCache; @@ -331,16 +330,9 @@ async fn sync_resolution_with_fs( let sub_node_modules = folder_path.join("node_modules"); let package_path = join_package_name(&sub_node_modules, &package.id.nv.name); - fs::create_dir_all(&package_path) - .with_context(|| format!("Creating '{}'", folder_path.display()))?; let cache_folder = cache.package_folder_for_name_and_version(&package.id.nv); - if hard_link_dir_recursive(&cache_folder, &package_path).is_err() { - // Fallback to copying the directory. - // - // Also handles EXDEV when when trying to hard link across volumes. - copy_dir_recursive(&cache_folder, &package_path)?; - } + clone_dir_recursive(&cache_folder, &package_path)?; // write out a file that indicates this folder has been initialized fs::write(initialized_file, "")?; @@ -373,9 +365,7 @@ async fn sync_resolution_with_fs( let sub_node_modules = destination_path.join("node_modules"); let package_path = join_package_name(&sub_node_modules, &package.id.nv.name); - fs::create_dir_all(&package_path).with_context(|| { - format!("Creating '{}'", destination_path.display()) - })?; + let source_path = join_package_name( &deno_local_registry_dir .join(get_package_folder_id_folder_name( @@ -384,7 +374,7 @@ async fn sync_resolution_with_fs( .join("node_modules"), &package.id.nv.name, ); - hard_link_dir_recursive(&source_path, &package_path)?; + clone_dir_recursive(&source_path, &package_path)?; // write out a file that indicates this folder has been initialized fs::write(initialized_file, "")?; } From 8500795a8d50edf0fdecb0608dc946e2b3b497b8 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Fri, 24 May 2024 18:01:36 -0700 Subject: [PATCH 5/9] Safety comment --- cli/util/fs.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/util/fs.rs b/cli/util/fs.rs index b3d49d369793a2..28d7464e010209 100644 --- a/cli/util/fs.rs +++ b/cli/util/fs.rs @@ -504,6 +504,7 @@ mod clone_dir_imp { fn clonefile(from: &Path, to: &Path) -> std::io::Result<()> { let from = std::ffi::CString::new(from.as_os_str().as_bytes())?; let to = std::ffi::CString::new(to.as_os_str().as_bytes())?; + // SAFETY: `from` and `to` are valid C strings. let ret = unsafe { libc::clonefile(from.as_ptr(), to.as_ptr(), 0) }; if ret != 0 { return Err(std::io::Error::last_os_error()); From 8280028b490a4070fcfeb0eb8e3c20a8c32415a4 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Fri, 24 May 2024 18:17:53 -0700 Subject: [PATCH 6/9] Remove a layer of fallback --- cli/util/fs.rs | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/cli/util/fs.rs b/cli/util/fs.rs index 28d7464e010209..dcc15824dee198 100644 --- a/cli/util/fs.rs +++ b/cli/util/fs.rs @@ -512,19 +512,6 @@ mod clone_dir_imp { Ok(()) } - /// Clones a directories contents to another directory. - pub(super) fn clone_dir_shallow(from: &Path, to: &Path) -> std::io::Result<()> { - let read_dir = std::fs::read_dir(from)?; - for entry in read_dir { - let entry = entry?; - let new_from = from.join(entry.file_name()); - let new_to = to.join(entry.file_name()); - - clonefile(&new_from, &new_to)?; - } - Ok(()) - } - pub(super) fn clone_dir_recursive( from: &Path, to: &Path, @@ -534,19 +521,12 @@ mod clone_dir_imp { } // Try to clone the whole directory if let Err(err) = clonefile(from, to) { - // clonefile won't overwrite existing files, so if the dir exists - // we need to handle it recursively. if err.kind() != std::io::ErrorKind::AlreadyExists { log::warn!("Failed to clone dir {:?} to {:?} via clonefile: {}", from, to, err); } - // Try to clone its contents one level down - if let Err(err) = clone_dir_shallow(from, to) { - if err.kind() != std::io::ErrorKind::AlreadyExists { - log::warn!("Failed to clone dir {:?} to {:?} via clonefile: {}", from, to, err); - } - // If that fails, fall back to copying recursively - copy_dir_recursive(from, to)?; - } + // clonefile won't overwrite existing files, so if the dir exists + // we need to handle it recursively. + copy_dir_recursive(from, to)?; } Ok(()) From 52a9f73264b5079fb75bf64b9e7025401cd21d44 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Fri, 24 May 2024 18:18:09 -0700 Subject: [PATCH 7/9] Hardlink npm cache --- cli/npm/managed/cache.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/npm/managed/cache.rs b/cli/npm/managed/cache.rs index b627d4e1977cd1..4056c97ad9ed16 100644 --- a/cli/npm/managed/cache.rs +++ b/cli/npm/managed/cache.rs @@ -23,7 +23,7 @@ use crate::args::CacheSetting; use crate::http_util::HttpClient; use crate::npm::common::maybe_auth_header_for_npm_registry; use crate::npm::NpmCacheDir; -use crate::util::fs::clone_dir_recursive; +use crate::util::fs::hard_link_dir_recursive; use crate::util::progress_bar::ProgressBar; use super::tarball::verify_and_extract_tarball; @@ -195,7 +195,7 @@ impl NpmCache { // it seems Windows does an "AccessDenied" error when moving a // directory with hard links, so that's why this solution is done with_folder_sync_lock(&folder_id.nv, &package_folder, || { - clone_dir_recursive(&original_package_folder, &package_folder) + hard_link_dir_recursive(&original_package_folder, &package_folder) })?; Ok(()) } From 71c569f9c2f9bb53f6e9e66677adbc02b6fe13d3 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Fri, 24 May 2024 18:27:58 -0700 Subject: [PATCH 8/9] Fix non mac --- cli/util/fs.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/util/fs.rs b/cli/util/fs.rs index dcc15824dee198..532ae9c95ff788 100644 --- a/cli/util/fs.rs +++ b/cli/util/fs.rs @@ -538,6 +538,8 @@ mod clone_dir_imp { log::debug!("Failed to hard link dir {:?} to {:?}: {}", from, to, e); copy_dir_recursive(from, to)?; } + + Ok(()) } } } @@ -581,7 +583,6 @@ pub fn copy_dir_recursive(from: &Path, to: &Path) -> Result<(), AnyError> { Ok(()) } -#[allow(dead_code)] /// Hardlinks the files in one directory to another directory. /// /// Note: Does not handle symlinks. From 461dfa2cbcdf7614ceb24c08086d1070b59306f6 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Tue, 28 May 2024 09:59:09 -0700 Subject: [PATCH 9/9] remove cfg-if --- Cargo.lock | 1 - cli/Cargo.toml | 1 - cli/util/fs.rs | 92 +++++++++++++++++++++++++++----------------------- 3 files changed, 49 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 60536ecb6c936a..4ce435af30c0d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1081,7 +1081,6 @@ dependencies = [ "bincode", "bytes", "cache_control", - "cfg-if", "chrono", "clap", "clap_complete", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 3708104c485cff..8c033f55a84d30 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -86,7 +86,6 @@ base64.workspace = true bincode = "=1.3.3" bytes.workspace = true cache_control.workspace = true -cfg-if = "1.0.0" chrono = { workspace = true, features = ["now"] } clap = { version = "=4.4.17", features = ["env", "string"] } clap_complete = "=4.4.7" diff --git a/cli/util/fs.rs b/cli/util/fs.rs index 532ae9c95ff788..9bdb1d014e554a 100644 --- a/cli/util/fs.rs +++ b/cli/util/fs.rs @@ -493,56 +493,62 @@ pub async fn remove_dir_all_if_exists(path: &Path) -> std::io::Result<()> { } mod clone_dir_imp { - use deno_core::error::AnyError; - - use super::copy_dir_recursive; - use std::path::Path; - - cfg_if::cfg_if! { - if #[cfg(target_vendor = "apple")] { - use std::os::unix::ffi::OsStrExt; - fn clonefile(from: &Path, to: &Path) -> std::io::Result<()> { - let from = std::ffi::CString::new(from.as_os_str().as_bytes())?; - let to = std::ffi::CString::new(to.as_os_str().as_bytes())?; - // SAFETY: `from` and `to` are valid C strings. - let ret = unsafe { libc::clonefile(from.as_ptr(), to.as_ptr(), 0) }; - if ret != 0 { - return Err(std::io::Error::last_os_error()); - } - Ok(()) - } - pub(super) fn clone_dir_recursive( - from: &Path, - to: &Path, - ) -> Result<(), AnyError> { - if let Some(parent) = to.parent() { - std::fs::create_dir_all(parent)?; - } - // Try to clone the whole directory - if let Err(err) = clonefile(from, to) { - if err.kind() != std::io::ErrorKind::AlreadyExists { - log::warn!("Failed to clone dir {:?} to {:?} via clonefile: {}", from, to, err); - } - // clonefile won't overwrite existing files, so if the dir exists - // we need to handle it recursively. - copy_dir_recursive(from, to)?; - } + #[cfg(target_vendor = "apple")] + mod apple { + use super::super::copy_dir_recursive; + use deno_core::error::AnyError; + use std::os::unix::ffi::OsStrExt; + use std::path::Path; + fn clonefile(from: &Path, to: &Path) -> std::io::Result<()> { + let from = std::ffi::CString::new(from.as_os_str().as_bytes())?; + let to = std::ffi::CString::new(to.as_os_str().as_bytes())?; + // SAFETY: `from` and `to` are valid C strings. + let ret = unsafe { libc::clonefile(from.as_ptr(), to.as_ptr(), 0) }; + if ret != 0 { + return Err(std::io::Error::last_os_error()); + } + Ok(()) + } - Ok(()) + pub fn clone_dir_recursive(from: &Path, to: &Path) -> Result<(), AnyError> { + if let Some(parent) = to.parent() { + std::fs::create_dir_all(parent)?; } - } else { - use super::hard_link_dir_recursive; - pub(super) fn clone_dir_recursive(from: &Path, to: &Path) -> Result<(), AnyError> { - if let Err(e) = hard_link_dir_recursive(from, to) { - log::debug!("Failed to hard link dir {:?} to {:?}: {}", from, to, e); - copy_dir_recursive(from, to)?; + // Try to clone the whole directory + if let Err(err) = clonefile(from, to) { + if err.kind() != std::io::ErrorKind::AlreadyExists { + log::warn!( + "Failed to clone dir {:?} to {:?} via clonefile: {}", + from, + to, + err + ); } - - Ok(()) + // clonefile won't overwrite existing files, so if the dir exists + // we need to handle it recursively. + copy_dir_recursive(from, to)?; } + + Ok(()) } } + + #[cfg(target_vendor = "apple")] + pub(super) use apple::clone_dir_recursive; + + #[cfg(not(target_vendor = "apple"))] + pub(super) fn clone_dir_recursive( + from: &std::path::Path, + to: &std::path::Path, + ) -> Result<(), deno_core::error::AnyError> { + if let Err(e) = super::hard_link_dir_recursive(from, to) { + log::debug!("Failed to hard link dir {:?} to {:?}: {}", from, to, e); + super::copy_dir_recursive(from, to)?; + } + + Ok(()) + } } /// Clones a directory to another directory. The exact method