|
42 | 42 | #include "uv.h"
|
43 | 43 | #include "v8-fast-api-calls.h"
|
44 | 44 |
|
| 45 | +#include <errno.h> |
| 46 | +#include <cerrno> |
45 | 47 | #include <cstdio>
|
46 | 48 | #include <filesystem>
|
47 | 49 |
|
@@ -3303,6 +3305,223 @@ static void CpSyncOverrideFile(const FunctionCallbackInfo<Value>& args) {
|
3303 | 3305 | }
|
3304 | 3306 | }
|
3305 | 3307 |
|
| 3308 | +std::vector<std::string> normalizePathToArray( |
| 3309 | + const std::filesystem::path& path) { |
| 3310 | + std::vector<std::string> parts; |
| 3311 | + std::filesystem::path absPath = std::filesystem::absolute(path); |
| 3312 | + for (const auto& part : absPath) { |
| 3313 | + if (!part.empty()) parts.push_back(part.string()); |
| 3314 | + } |
| 3315 | + return parts; |
| 3316 | +} |
| 3317 | + |
| 3318 | +bool isInsideDir(const std::filesystem::path& src, |
| 3319 | + const std::filesystem::path& dest) { |
| 3320 | + auto srcArr = normalizePathToArray(src); |
| 3321 | + auto destArr = normalizePathToArray(dest); |
| 3322 | + if (srcArr.size() > destArr.size()) return false; |
| 3323 | + return std::equal(srcArr.begin(), srcArr.end(), destArr.begin()); |
| 3324 | +} |
| 3325 | + |
| 3326 | +static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) { |
| 3327 | + CHECK_EQ(args.Length(), 7); // src, dest, force, dereference, errorOnExist, |
| 3328 | + // verbatimSymlinks, preserveTimestamps |
| 3329 | + |
| 3330 | + Environment* env = Environment::GetCurrent(args); |
| 3331 | + Isolate* isolate = env->isolate(); |
| 3332 | + |
| 3333 | + BufferValue src(isolate, args[0]); |
| 3334 | + CHECK_NOT_NULL(*src); |
| 3335 | + ToNamespacedPath(env, &src); |
| 3336 | + |
| 3337 | + BufferValue dest(isolate, args[1]); |
| 3338 | + CHECK_NOT_NULL(*dest); |
| 3339 | + ToNamespacedPath(env, &dest); |
| 3340 | + |
| 3341 | + bool force = args[2]->IsTrue(); |
| 3342 | + bool dereference = args[3]->IsTrue(); |
| 3343 | + bool error_on_exist = args[4]->IsTrue(); |
| 3344 | + bool verbatim_symlinks = args[5]->IsTrue(); |
| 3345 | + bool preserve_timestamps = args[6]->IsTrue(); |
| 3346 | + |
| 3347 | + std::error_code error; |
| 3348 | + std::filesystem::create_directories(*dest, error); |
| 3349 | + if (error) { |
| 3350 | + return env->ThrowStdErrException(error, "cp", *dest); |
| 3351 | + } |
| 3352 | + |
| 3353 | + auto file_copy_opts = std::filesystem::copy_options::recursive; |
| 3354 | + if (force) { |
| 3355 | + file_copy_opts |= std::filesystem::copy_options::overwrite_existing; |
| 3356 | + } else if (error_on_exist) { |
| 3357 | + file_copy_opts |= std::filesystem::copy_options::none; |
| 3358 | + } else { |
| 3359 | + file_copy_opts |= std::filesystem::copy_options::skip_existing; |
| 3360 | + } |
| 3361 | + |
| 3362 | + std::function<bool(std::filesystem::path, std::filesystem::path)> |
| 3363 | + copy_dir_contents; |
| 3364 | + copy_dir_contents = [verbatim_symlinks, |
| 3365 | + ©_dir_contents, |
| 3366 | + &env, |
| 3367 | + file_copy_opts, |
| 3368 | + preserve_timestamps, |
| 3369 | + force, |
| 3370 | + error_on_exist, |
| 3371 | + dereference, |
| 3372 | + &isolate](std::filesystem::path src, |
| 3373 | + std::filesystem::path dest) { |
| 3374 | + std::error_code error; |
| 3375 | + for (auto dir_entry : std::filesystem::directory_iterator(src)) { |
| 3376 | + auto dest_file_path = dest / dir_entry.path().filename(); |
| 3377 | + auto dest_str = PathToString(dest); |
| 3378 | + |
| 3379 | + if (dir_entry.is_symlink()) { |
| 3380 | + if (verbatim_symlinks) { |
| 3381 | + std::filesystem::copy_symlink( |
| 3382 | + dir_entry.path(), dest_file_path, error); |
| 3383 | + if (error) { |
| 3384 | + env->ThrowStdErrException(error, "cp", dest_str.c_str()); |
| 3385 | + return false; |
| 3386 | + } |
| 3387 | + } else { |
| 3388 | + auto symlink_target = |
| 3389 | + std::filesystem::read_symlink(dir_entry.path().c_str(), error); |
| 3390 | + if (error) { |
| 3391 | + env->ThrowStdErrException(error, "cp", dest_str.c_str()); |
| 3392 | + return false; |
| 3393 | + } |
| 3394 | + |
| 3395 | + if (std::filesystem::exists(dest_file_path)) { |
| 3396 | + if (std::filesystem::is_symlink((dest_file_path.c_str()))) { |
| 3397 | + auto current_dest_symlink_target = |
| 3398 | + std::filesystem::read_symlink(dest_file_path.c_str(), error); |
| 3399 | + if (error) { |
| 3400 | + env->ThrowStdErrException(error, "cp", dest_str.c_str()); |
| 3401 | + return false; |
| 3402 | + } |
| 3403 | + |
| 3404 | + if (!dereference && |
| 3405 | + std::filesystem::is_directory(symlink_target) && |
| 3406 | + isInsideDir(symlink_target, current_dest_symlink_target)) { |
| 3407 | + std::string message = |
| 3408 | + "Cannot copy %s to a subdirectory of self %s"; |
| 3409 | + THROW_ERR_FS_CP_EINVAL(env, |
| 3410 | + message.c_str(), |
| 3411 | + symlink_target.c_str(), |
| 3412 | + current_dest_symlink_target.c_str()); |
| 3413 | + return false; |
| 3414 | + } |
| 3415 | + |
| 3416 | + // Prevent copy if src is a subdir of dest since unlinking |
| 3417 | + // dest in this case would result in removing src contents |
| 3418 | + // and therefore a broken symlink would be created. |
| 3419 | + if (std::filesystem::is_directory(dest_file_path) && |
| 3420 | + isInsideDir(current_dest_symlink_target, symlink_target)) { |
| 3421 | + std::string message = "cannot overwrite %s with %s"; |
| 3422 | + THROW_ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY( |
| 3423 | + env, |
| 3424 | + message.c_str(), |
| 3425 | + current_dest_symlink_target.c_str(), |
| 3426 | + symlink_target.c_str()); |
| 3427 | + return false; |
| 3428 | + } |
| 3429 | + |
| 3430 | + // symlinks get overridden by cp even if force: false, this is |
| 3431 | + // being applied here for backward compatibility, but is it |
| 3432 | + // correct? or is it a bug? |
| 3433 | + std::filesystem::remove(dest_file_path, error); |
| 3434 | + if (error) { |
| 3435 | + env->ThrowStdErrException(error, "cp", dest_str.c_str()); |
| 3436 | + return false; |
| 3437 | + } |
| 3438 | + } else if (std::filesystem::is_regular_file(dest_file_path)) { |
| 3439 | + if (!dereference || (!force && error_on_exist)) { |
| 3440 | + auto dest_file_path_str = PathToString(dest_file_path); |
| 3441 | + env->ThrowStdErrException( |
| 3442 | + std::make_error_code(std::errc::file_exists), |
| 3443 | + "cp", |
| 3444 | + dest_file_path_str.c_str()); |
| 3445 | + return false; |
| 3446 | + } |
| 3447 | + } |
| 3448 | + } |
| 3449 | + auto symlink_target_absolute = std::filesystem::weakly_canonical( |
| 3450 | + std::filesystem::absolute(src / symlink_target)); |
| 3451 | + if (dir_entry.is_directory()) { |
| 3452 | + std::filesystem::create_directory_symlink( |
| 3453 | + symlink_target_absolute, dest_file_path, error); |
| 3454 | + } else { |
| 3455 | + std::filesystem::create_symlink( |
| 3456 | + symlink_target_absolute, dest_file_path, error); |
| 3457 | + } |
| 3458 | + if (error) { |
| 3459 | + env->ThrowStdErrException(error, "cp", dest_str.c_str()); |
| 3460 | + return false; |
| 3461 | + } |
| 3462 | + } |
| 3463 | + } else if (dir_entry.is_directory()) { |
| 3464 | + auto entry_dir_path = src / dir_entry.path().filename(); |
| 3465 | + std::filesystem::create_directory(dest_file_path); |
| 3466 | + auto success = copy_dir_contents(entry_dir_path, dest_file_path); |
| 3467 | + if (!success) { |
| 3468 | + return false; |
| 3469 | + } |
| 3470 | + } else if (dir_entry.is_regular_file()) { |
| 3471 | + std::filesystem::copy_file( |
| 3472 | + dir_entry.path(), dest_file_path, file_copy_opts, error); |
| 3473 | + if (error) { |
| 3474 | + if (error.value() == EEXIST) { |
| 3475 | + THROW_ERR_FS_CP_EEXIST(isolate, |
| 3476 | + "[ERR_FS_CP_EEXIST]: Target already exists: " |
| 3477 | + "cp returned EEXIST (%s already exists)", |
| 3478 | + dest_file_path.c_str()); |
| 3479 | + return false; |
| 3480 | + } |
| 3481 | + env->ThrowStdErrException(error, "cp", dest_str.c_str()); |
| 3482 | + return false; |
| 3483 | + } |
| 3484 | + |
| 3485 | + if (preserve_timestamps) { |
| 3486 | + uv_fs_t req; |
| 3487 | + auto cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); }); |
| 3488 | + |
| 3489 | + auto dir_entry_path_str = PathToString(dir_entry.path()); |
| 3490 | + int result = |
| 3491 | + uv_fs_stat(nullptr, &req, dir_entry_path_str.c_str(), nullptr); |
| 3492 | + if (is_uv_error(result)) { |
| 3493 | + env->ThrowUVException( |
| 3494 | + result, "stat", nullptr, dir_entry_path_str.c_str()); |
| 3495 | + return false; |
| 3496 | + } |
| 3497 | + |
| 3498 | + const uv_stat_t* const s = static_cast<const uv_stat_t*>(req.ptr); |
| 3499 | + const double source_atime = |
| 3500 | + s->st_atim.tv_sec + s->st_atim.tv_nsec / 1e9; |
| 3501 | + const double source_mtime = |
| 3502 | + s->st_mtim.tv_sec + s->st_mtim.tv_nsec / 1e9; |
| 3503 | + |
| 3504 | + auto dest_file_path_str = PathToString(dest_file_path); |
| 3505 | + int utime_result = uv_fs_utime(nullptr, |
| 3506 | + &req, |
| 3507 | + dest_file_path_str.c_str(), |
| 3508 | + source_atime, |
| 3509 | + source_mtime, |
| 3510 | + nullptr); |
| 3511 | + if (is_uv_error(utime_result)) { |
| 3512 | + env->ThrowUVException( |
| 3513 | + utime_result, "utime", nullptr, dest_file_path_str.c_str()); |
| 3514 | + return false; |
| 3515 | + } |
| 3516 | + } |
| 3517 | + } |
| 3518 | + } |
| 3519 | + return true; |
| 3520 | + }; |
| 3521 | + |
| 3522 | + copy_dir_contents(std::filesystem::path(*src), std::filesystem::path(*dest)); |
| 3523 | +} |
| 3524 | + |
3306 | 3525 | BindingData::FilePathIsFileReturnType BindingData::FilePathIsFile(
|
3307 | 3526 | Environment* env, const std::string& file_path) {
|
3308 | 3527 | THROW_IF_INSUFFICIENT_PERMISSIONS(
|
@@ -3642,6 +3861,7 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
|
3642 | 3861 |
|
3643 | 3862 | SetMethod(isolate, target, "cpSyncCheckPaths", CpSyncCheckPaths);
|
3644 | 3863 | SetMethod(isolate, target, "cpSyncOverrideFile", CpSyncOverrideFile);
|
| 3864 | + SetMethod(isolate, target, "cpSyncCopyDir", CpSyncCopyDir); |
3645 | 3865 |
|
3646 | 3866 | StatWatcher::CreatePerIsolateProperties(isolate_data, target);
|
3647 | 3867 | BindingData::CreatePerIsolateProperties(isolate_data, target);
|
@@ -3754,6 +3974,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
3754 | 3974 |
|
3755 | 3975 | registry->Register(CpSyncCheckPaths);
|
3756 | 3976 | registry->Register(CpSyncOverrideFile);
|
| 3977 | + registry->Register(CpSyncCopyDir); |
3757 | 3978 |
|
3758 | 3979 | registry->Register(Chmod);
|
3759 | 3980 | registry->Register(FChmod);
|
|
0 commit comments