@@ -3417,6 +3417,208 @@ static void CpSyncOverrideFile(const FunctionCallbackInfo<Value>& args) {
34173417 }
34183418}
34193419
3420+ std::vector<std::string> normalizePathToArray (
3421+ const std::filesystem::path& path) {
3422+ std::vector<std::string> parts;
3423+ std::filesystem::path absPath = std::filesystem::absolute (path);
3424+ for (const auto & part : absPath) {
3425+ if (!part.empty ()) parts.push_back (part.string ());
3426+ }
3427+ return parts;
3428+ }
3429+
3430+ bool isInsideDir (const std::filesystem::path& src,
3431+ const std::filesystem::path& dest) {
3432+ auto srcArr = normalizePathToArray (src);
3433+ auto destArr = normalizePathToArray (dest);
3434+ if (srcArr.size () > destArr.size ()) return false ;
3435+ return std::equal (srcArr.begin (), srcArr.end (), destArr.begin ());
3436+ }
3437+
3438+ static void CpSyncCopyDir (const FunctionCallbackInfo<Value>& args) {
3439+ Environment* env = Environment::GetCurrent (args);
3440+ Isolate* isolate = env->isolate ();
3441+
3442+ CHECK_EQ (args.Length (), 7 ); // src, dest, force, dereference, errorOnExist,
3443+ // verbatimSymlinks, preserveTimestamps
3444+
3445+ BufferValue src (isolate, args[0 ]);
3446+ CHECK_NOT_NULL (*src);
3447+ ToNamespacedPath (env, &src);
3448+
3449+ BufferValue dest (isolate, args[1 ]);
3450+ CHECK_NOT_NULL (*dest);
3451+ ToNamespacedPath (env, &dest);
3452+
3453+ bool force = args[2 ]->IsTrue ();
3454+ bool dereference = args[3 ]->IsTrue ();
3455+ bool error_on_exist = args[4 ]->IsTrue ();
3456+ bool verbatim_symlinks = args[5 ]->IsTrue ();
3457+ bool preserve_timestamps = args[6 ]->IsTrue ();
3458+
3459+ std::error_code error;
3460+ std::filesystem::create_directories (*dest, error);
3461+ if (error) {
3462+ return env->ThrowStdErrException (error, " cp" , *dest);
3463+ }
3464+
3465+ auto file_copy_opts = std::filesystem::copy_options::recursive;
3466+ if (force) {
3467+ file_copy_opts |= std::filesystem::copy_options::overwrite_existing;
3468+ } else if (error_on_exist) {
3469+ file_copy_opts |= std::filesystem::copy_options::none;
3470+ } else {
3471+ file_copy_opts |= std::filesystem::copy_options::skip_existing;
3472+ }
3473+
3474+ std::function<bool (std::filesystem::path, std::filesystem::path)>
3475+ copy_dir_contents;
3476+ copy_dir_contents = [verbatim_symlinks,
3477+ ©_dir_contents,
3478+ &env,
3479+ file_copy_opts,
3480+ preserve_timestamps, force, error_on_exist, dereference](std::filesystem::path src,
3481+ std::filesystem::path dest) {
3482+ std::error_code error;
3483+ for (auto dir_entry : std::filesystem::directory_iterator (src)) {
3484+ auto dest_file = dest / dir_entry.path ().filename ();
3485+
3486+ if (dir_entry.is_symlink ()) {
3487+ auto src_entry_path = std::filesystem::relative (dir_entry.path (), src);
3488+
3489+ if (verbatim_symlinks) {
3490+ std::filesystem::copy_symlink (dir_entry.path (), dest_file, error);
3491+ if (error) {
3492+ env->ThrowStdErrException (error, " cp" , dest.c_str ());
3493+ return false ;
3494+ }
3495+ } else {
3496+ auto target =
3497+ std::filesystem::read_symlink (dir_entry.path ().c_str (), error);
3498+ if (error) {
3499+ env->ThrowStdErrException (error, " cp" , dest.c_str ());
3500+ return false ;
3501+ }
3502+ auto target_absolute = std::filesystem::weakly_canonical (
3503+ std::filesystem::absolute (src / target));
3504+
3505+
3506+ if (std::filesystem::exists (dest_file)) {
3507+ if (std::filesystem::is_symlink ((dest_file.c_str ()))) {
3508+ auto current_target =
3509+ std::filesystem::read_symlink (dest_file.c_str (), error);
3510+ if (error) {
3511+ env->ThrowStdErrException (error, " cp" , dest.c_str ());
3512+ return false ;
3513+ }
3514+
3515+ if (!dereference && isInsideDir (target, current_target)) {
3516+ std::string message =
3517+ " Cannot copy %s to a subdirectory of self %s" ;
3518+ THROW_ERR_FS_CP_EINVAL (
3519+ env, message.c_str (), target.c_str (), current_target.c_str ());
3520+ return false ;
3521+ }
3522+
3523+ // Prevent copy if src is a subdir of dest since unlinking
3524+ // dest in this case would result in removing src contents
3525+ // and therefore a broken symlink would be created.
3526+ if (std::filesystem::is_directory (dest_file) &&
3527+ isInsideDir (current_target, target)) {
3528+ std::string message = " cannot overwrite %s with %s" ;
3529+ THROW_ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY (
3530+ env, message.c_str (), current_target.c_str (), target.c_str ());
3531+ return false ;
3532+ }
3533+
3534+ // symlinks get overridden by cp even if force: false, this is being
3535+ // applied here for backward compatibility, but is it correct? or is
3536+ // it a bug?
3537+ std::filesystem::remove (dest_file, error);
3538+ if (error) {
3539+ env->ThrowStdErrException (error, " cp" , dest.c_str ());
3540+ return false ;
3541+ }
3542+ return true ;
3543+ }
3544+
3545+ if (std::filesystem::is_regular_file (dest_file)) {
3546+ if (!dereference || (!force && error_on_exist)) {
3547+ env->ThrowStdErrException (
3548+ std::make_error_code (std::errc::file_exists),
3549+ " cp" ,
3550+ dest_file.c_str ());
3551+ return false ;
3552+ }
3553+ }
3554+ }
3555+
3556+ if (dir_entry.is_directory ()) {
3557+ std::filesystem::create_directory_symlink (
3558+ target_absolute, dest_file, error);
3559+ } else {
3560+ std::filesystem::create_symlink (target_absolute, dest_file, error);
3561+ }
3562+ if (error) {
3563+ env->ThrowStdErrException (error, " cp" , dest.c_str ());
3564+ return false ;
3565+ }
3566+ }
3567+ } else if (dir_entry.is_directory ()) {
3568+ auto src_entry_path = std::filesystem::relative (dir_entry.path (), src);
3569+ auto filename = dir_entry.path ().filename ();
3570+ std::filesystem::create_directory (dest_file);
3571+ auto success = copy_dir_contents (src / filename, dest_file);
3572+ if (!success) {
3573+ return false ;
3574+ }
3575+ } else if (dir_entry.is_regular_file ()) {
3576+ auto src_entry_path = std::filesystem::relative (dir_entry.path (), src);
3577+ auto dest_entry_path = dest / src_entry_path;
3578+ std::filesystem::copy_file (
3579+ dir_entry.path (), dest_entry_path, file_copy_opts, error);
3580+ if (error) {
3581+ env->ThrowStdErrException (error, " cp" , dest.c_str ());
3582+ return false ;
3583+ }
3584+
3585+ if (preserve_timestamps) {
3586+ uv_fs_t req;
3587+ auto cleanup = OnScopeLeave ([&req]() { uv_fs_req_cleanup (&req); });
3588+ int result =
3589+ uv_fs_stat (nullptr , &req, dir_entry.path ().c_str (), nullptr );
3590+ if (is_uv_error (result)) {
3591+ env->ThrowUVException (
3592+ result, " stat" , nullptr , dir_entry.path ().c_str ());
3593+ return false ;
3594+ }
3595+
3596+ const uv_stat_t * const s = static_cast <const uv_stat_t *>(req.ptr );
3597+ const double source_atime =
3598+ s->st_atim .tv_sec + s->st_atim .tv_nsec / 1e9 ;
3599+ const double source_mtime =
3600+ s->st_mtim .tv_sec + s->st_mtim .tv_nsec / 1e9 ;
3601+
3602+ int utime_result = uv_fs_utime (nullptr ,
3603+ &req,
3604+ dest_entry_path.c_str (),
3605+ source_atime,
3606+ source_mtime,
3607+ nullptr );
3608+ if (is_uv_error (utime_result)) {
3609+ env->ThrowUVException (
3610+ utime_result, " utime" , nullptr , dest_entry_path.c_str ());
3611+ return false ;
3612+ }
3613+ }
3614+ }
3615+ }
3616+ return true ;
3617+ };
3618+
3619+ copy_dir_contents (std::filesystem::path (*src), std::filesystem::path (*dest));
3620+ }
3621+
34203622BindingData::FilePathIsFileReturnType BindingData::FilePathIsFile (
34213623 Environment* env, const std::string& file_path) {
34223624 THROW_IF_INSUFFICIENT_PERMISSIONS (
@@ -3757,6 +3959,7 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
37573959
37583960 SetMethod (isolate, target, " cpSyncCheckPaths" , CpSyncCheckPaths);
37593961 SetMethod (isolate, target, " cpSyncOverrideFile" , CpSyncOverrideFile);
3962+ SetMethod (isolate, target, " cpSyncCopyDir" , CpSyncCopyDir);
37603963
37613964 StatWatcher::CreatePerIsolateProperties (isolate_data, target);
37623965 BindingData::CreatePerIsolateProperties (isolate_data, target);
@@ -3870,6 +4073,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
38704073
38714074 registry->Register (CpSyncCheckPaths);
38724075 registry->Register (CpSyncOverrideFile);
4076+ registry->Register (CpSyncCopyDir);
38734077
38744078 registry->Register (Chmod);
38754079 registry->Register (FChmod);
0 commit comments