43
43
#include "uv.h"
44
44
#include "v8-fast-api-calls.h"
45
45
46
+
#include <errno.h>
47
+
#include <cerrno>
46
48
#include <cstdio>
47
49
#include <filesystem>
48
50
@@ -3390,6 +3392,223 @@ static void CpSyncOverrideFile(const FunctionCallbackInfo<Value>& args) {
3390
3392
}
3391
3393
}
3392
3394
3395
+
std::vector<std::string> normalizePathToArray(
3396
+
const std::filesystem::path& path) {
3397
+
std::vector<std::string> parts;
3398
+
std::filesystem::path absPath = std::filesystem::absolute(path);
3399
+
for (const auto& part : absPath) {
3400
+
if (!part.empty()) parts.push_back(part.string());
3401
+
}
3402
+
return parts;
3403
+
}
3404
+
3405
+
bool isInsideDir(const std::filesystem::path& src,
3406
+
const std::filesystem::path& dest) {
3407
+
auto srcArr = normalizePathToArray(src);
3408
+
auto destArr = normalizePathToArray(dest);
3409
+
if (srcArr.size() > destArr.size()) return false;
3410
+
return std::equal(srcArr.begin(), srcArr.end(), destArr.begin());
3411
+
}
3412
+
3413
+
static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
3414
+
CHECK_EQ(args.Length(), 7); // src, dest, force, dereference, errorOnExist,
3415
+
// verbatimSymlinks, preserveTimestamps
3416
+
3417
+
Environment* env = Environment::GetCurrent(args);
3418
+
Isolate* isolate = env->isolate();
3419
+
3420
+
BufferValue src(isolate, args[0]);
3421
+
CHECK_NOT_NULL(*src);
3422
+
ToNamespacedPath(env, &src);
3423
+
3424
+
BufferValue dest(isolate, args[1]);
3425
+
CHECK_NOT_NULL(*dest);
3426
+
ToNamespacedPath(env, &dest);
3427
+
3428
+
bool force = args[2]->IsTrue();
3429
+
bool dereference = args[3]->IsTrue();
3430
+
bool error_on_exist = args[4]->IsTrue();
3431
+
bool verbatim_symlinks = args[5]->IsTrue();
3432
+
bool preserve_timestamps = args[6]->IsTrue();
3433
+
3434
+
std::error_code error;
3435
+
std::filesystem::create_directories(*dest, error);
3436
+
if (error) {
3437
+
return env->ThrowStdErrException(error, "cp", *dest);
3438
+
}
3439
+
3440
+
auto file_copy_opts = std::filesystem::copy_options::recursive;
3441
+
if (force) {
3442
+
file_copy_opts |= std::filesystem::copy_options::overwrite_existing;
3443
+
} else if (error_on_exist) {
3444
+
file_copy_opts |= std::filesystem::copy_options::none;
3445
+
} else {
3446
+
file_copy_opts |= std::filesystem::copy_options::skip_existing;
3447
+
}
3448
+
3449
+
std::function<bool(std::filesystem::path, std::filesystem::path)>
3450
+
copy_dir_contents;
3451
+
copy_dir_contents = [verbatim_symlinks,
3452
+
©_dir_contents,
3453
+
&env,
3454
+
file_copy_opts,
3455
+
preserve_timestamps,
3456
+
force,
3457
+
error_on_exist,
3458
+
dereference,
3459
+
&isolate](std::filesystem::path src,
3460
+
std::filesystem::path dest) {
3461
+
std::error_code error;
3462
+
for (auto dir_entry : std::filesystem::directory_iterator(src)) {
3463
+
auto dest_file_path = dest / dir_entry.path().filename();
3464
+
auto dest_str = PathToString(dest);
3465
+
3466
+
if (dir_entry.is_symlink()) {
3467
+
if (verbatim_symlinks) {
3468
+
std::filesystem::copy_symlink(
3469
+
dir_entry.path(), dest_file_path, error);
3470
+
if (error) {
3471
+
env->ThrowStdErrException(error, "cp", dest_str.c_str());
3472
+
return false;
3473
+
}
3474
+
} else {
3475
+
auto symlink_target =
3476
+
std::filesystem::read_symlink(dir_entry.path().c_str(), error);
3477
+
if (error) {
3478
+
env->ThrowStdErrException(error, "cp", dest_str.c_str());
3479
+
return false;
3480
+
}
3481
+
3482
+
if (std::filesystem::exists(dest_file_path)) {
3483
+
if (std::filesystem::is_symlink((dest_file_path.c_str()))) {
3484
+
auto current_dest_symlink_target =
3485
+
std::filesystem::read_symlink(dest_file_path.c_str(), error);
3486
+
if (error) {
3487
+
env->ThrowStdErrException(error, "cp", dest_str.c_str());
3488
+
return false;
3489
+
}
3490
+
3491
+
if (!dereference &&
3492
+
std::filesystem::is_directory(symlink_target) &&
3493
+
isInsideDir(symlink_target, current_dest_symlink_target)) {
3494
+
std::string message =
3495
+
"Cannot copy %s to a subdirectory of self %s";
3496
+
THROW_ERR_FS_CP_EINVAL(env,
3497
+
message.c_str(),
3498
+
symlink_target.c_str(),
3499
+
current_dest_symlink_target.c_str());
3500
+
return false;
3501
+
}
3502
+
3503
+
// Prevent copy if src is a subdir of dest since unlinking
3504
+
// dest in this case would result in removing src contents
3505
+
// and therefore a broken symlink would be created.
3506
+
if (std::filesystem::is_directory(dest_file_path) &&
3507
+
isInsideDir(current_dest_symlink_target, symlink_target)) {
3508
+
std::string message = "cannot overwrite %s with %s";
3509
+
THROW_ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY(
3510
+
env,
3511
+
message.c_str(),
3512
+
current_dest_symlink_target.c_str(),
3513
+
symlink_target.c_str());
3514
+
return false;
3515
+
}
3516
+
3517
+
// symlinks get overridden by cp even if force: false, this is
3518
+
// being applied here for backward compatibility, but is it
3519
+
// correct? or is it a bug?
3520
+
std::filesystem::remove(dest_file_path, error);
3521
+
if (error) {
3522
+
env->ThrowStdErrException(error, "cp", dest_str.c_str());
3523
+
return false;
3524
+
}
3525
+
} else if (std::filesystem::is_regular_file(dest_file_path)) {
3526
+
if (!dereference || (!force && error_on_exist)) {
3527
+
auto dest_file_path_str = PathToString(dest_file_path);
3528
+
env->ThrowStdErrException(
3529
+
std::make_error_code(std::errc::file_exists),
3530
+
"cp",
3531
+
dest_file_path_str.c_str());
3532
+
return false;
3533
+
}
3534
+
}
3535
+
}
3536
+
auto symlink_target_absolute = std::filesystem::weakly_canonical(
3537
+
std::filesystem::absolute(src / symlink_target));
3538
+
if (dir_entry.is_directory()) {
3539
+
std::filesystem::create_directory_symlink(
3540
+
symlink_target_absolute, dest_file_path, error);
3541
+
} else {
3542
+
std::filesystem::create_symlink(
3543
+
symlink_target_absolute, dest_file_path, error);
3544
+
}
3545
+
if (error) {
3546
+
env->ThrowStdErrException(error, "cp", dest_str.c_str());
3547
+
return false;
3548
+
}
3549
+
}
3550
+
} else if (dir_entry.is_directory()) {
3551
+
auto entry_dir_path = src / dir_entry.path().filename();
3552
+
std::filesystem::create_directory(dest_file_path);
3553
+
auto success = copy_dir_contents(entry_dir_path, dest_file_path);
3554
+
if (!success) {
3555
+
return false;
3556
+
}
3557
+
} else if (dir_entry.is_regular_file()) {
3558
+
std::filesystem::copy_file(
3559
+
dir_entry.path(), dest_file_path, file_copy_opts, error);
3560
+
if (error) {
3561
+
if (error.value() == EEXIST) {
3562
+
THROW_ERR_FS_CP_EEXIST(isolate,
3563
+
"[ERR_FS_CP_EEXIST]: Target already exists: "
3564
+
"cp returned EEXIST (%s already exists)",
3565
+
dest_file_path.c_str());
3566
+
return false;
3567
+
}
3568
+
env->ThrowStdErrException(error, "cp", dest_str.c_str());
3569
+
return false;
3570
+
}
3571
+
3572
+
if (preserve_timestamps) {
3573
+
uv_fs_t req;
3574
+
auto cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); });
3575
+
3576
+
auto dir_entry_path_str = PathToString(dir_entry.path());
3577
+
int result =
3578
+
uv_fs_stat(nullptr, &req, dir_entry_path_str.c_str(), nullptr);
3579
+
if (is_uv_error(result)) {
3580
+
env->ThrowUVException(
3581
+
result, "stat", nullptr, dir_entry_path_str.c_str());
3582
+
return false;
3583
+
}
3584
+
3585
+
const uv_stat_t* const s = static_cast<const uv_stat_t*>(req.ptr);
3586
+
const double source_atime =
3587
+
s->st_atim.tv_sec + s->st_atim.tv_nsec / 1e9;
3588
+
const double source_mtime =
3589
+
s->st_mtim.tv_sec + s->st_mtim.tv_nsec / 1e9;
3590
+
3591
+
auto dest_file_path_str = PathToString(dest_file_path);
3592
+
int utime_result = uv_fs_utime(nullptr,
3593
+
&req,
3594
+
dest_file_path_str.c_str(),
3595
+
source_atime,
3596
+
source_mtime,
3597
+
nullptr);
3598
+
if (is_uv_error(utime_result)) {
3599
+
env->ThrowUVException(
3600
+
utime_result, "utime", nullptr, dest_file_path_str.c_str());
3601
+
return false;
3602
+
}
3603
+
}
3604
+
}
3605
+
}
3606
+
return true;
3607
+
};
3608
+
3609
+
copy_dir_contents(std::filesystem::path(*src), std::filesystem::path(*dest));
3610
+
}
3611
+
3393
3612
BindingData::FilePathIsFileReturnType BindingData::FilePathIsFile(
3394
3613
Environment* env, const std::string& file_path) {
3395
3614
THROW_IF_INSUFFICIENT_PERMISSIONS(
@@ -3726,6 +3945,7 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
3726
3945
3727
3946
SetMethod(isolate, target, "cpSyncCheckPaths", CpSyncCheckPaths);
3728
3947
SetMethod(isolate, target, "cpSyncOverrideFile", CpSyncOverrideFile);
3948
+
SetMethod(isolate, target, "cpSyncCopyDir", CpSyncCopyDir);
3729
3949
3730
3950
StatWatcher::CreatePerIsolateProperties(isolate_data, target);
3731
3951
BindingData::CreatePerIsolateProperties(isolate_data, target);
@@ -3837,6 +4057,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
3837
4057
3838
4058
registry->Register(CpSyncCheckPaths);
3839
4059
registry->Register(CpSyncOverrideFile);
4060
+
registry->Register(CpSyncCopyDir);
3840
4061
3841
4062
registry->Register(Chmod);
3842
4063
registry->Register(FChmod);
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4