| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent d6ea36a commit fcef56c
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -133,18 +133,27 @@ function setDestTimestamps(src, dest) { | |||
| 133 | 133 | ||
| 134 | 134 | // TODO(@anonrig): Move this function to C++. | |
| 135 | 135 | function onDir(srcStat, destStat, src, dest, opts) { | |
| 136 | - if (!destStat) return mkDirAndCopy(srcStat.mode, src, dest, opts); | ||
| 136 | + if (!destStat) return copyDir(src, dest, opts, true, srcStat.mode); | ||
| 137 | 137 | return copyDir(src, dest, opts); | |
| 138 | 138 | } | |
| 139 | 139 | ||
| 140 | - function mkDirAndCopy(srcMode, src, dest, opts) { | ||
| 141 | - mkdirSync(dest); | ||
| 142 | - copyDir(src, dest, opts); | ||
| 143 | - return setDestMode(dest, srcMode); | ||
| 144 | - } | ||
| 140 | + function copyDir(src, dest, opts, mkDir, srcMode) { | ||
| 141 | + if (!opts.filter) { | ||
| 142 | + // The caller didn't provide a js filter function, in this case | ||
| 143 | + // we can run the whole function faster in C++ | ||
| 144 | + // TODO(dario-piotrowicz): look into making cpSyncCopyDir also accept the potential filter function | ||
| 145 | + return fsBinding.cpSyncCopyDir(src, dest, | ||
| 146 | + opts.force, | ||
| 147 | + opts.dereference, | ||
| 148 | + opts.errorOnExist, | ||
| 149 | + opts.verbatimSymlinks, | ||
| 150 | + opts.preserveTimestamps); | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + if (mkDir) { | ||
| 154 | + mkdirSync(dest); | ||
| 155 | + } | ||
| 145 | 156 | ||
| 146 | - // TODO(@anonrig): Move this function to C++. | ||
| 147 | - function copyDir(src, dest, opts) { | ||
| 148 | 157 | const dir = opendirSync(src); | |
| 149 | 158 | ||
| 150 | 159 | try { | |
@@ -169,6 +178,10 @@ function copyDir(src, dest, opts) { | |||
| 169 | 178 | } | |
| 170 | 179 | } finally { | |
| 171 | 180 | dir.closeSync(); | |
| 181 | + | ||
| 182 | + if (srcMode !== undefined) { | ||
| 183 | + setDestMode(dest, srcMode); | ||
| 184 | + } | ||
| 172 | 185 | } | |
| 173 | 186 | } | |
| 174 | 187 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -78,7 +78,9 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details); | |||
| 78 | 78 | V(ERR_FS_CP_EINVAL, Error) \ | |
| 79 | 79 | V(ERR_FS_CP_DIR_TO_NON_DIR, Error) \ | |
| 80 | 80 | V(ERR_FS_CP_NON_DIR_TO_DIR, Error) \ | |
| 81 | + V(ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY, Error) \ | ||
| 81 | 82 | V(ERR_FS_EISDIR, Error) \ | |
| 83 | + V(ERR_FS_CP_EEXIST, Error) \ | ||
| 82 | 84 | V(ERR_FS_CP_SOCKET, Error) \ | |
| 83 | 85 | V(ERR_FS_CP_FIFO_PIPE, Error) \ | |
| 84 | 86 | V(ERR_FS_CP_UNKNOWN, Error) \ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -43,6 +43,8 @@ | |||
| 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); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -521,6 +521,9 @@ if (!isWindows && !isInsideDirWithUnusualChars) { | |||
| 521 | 521 | const dest = nextdir(); | |
| 522 | 522 | mkdirSync(dest, mustNotMutateObjectDeep({ recursive: true })); | |
| 523 | 523 | writeFileSync(join(src, 'foo.txt'), 'foo', mustNotMutateObjectDeep({ mode: 0o444 })); | |
| 524 | + // Small wait to make sure that destStat.mtime.getTime() would produce a time | ||
| 525 | + // different from srcStat.mtime.getTime() if preserveTimestamps wasn't set to true | ||
| 526 | + await setTimeout(5); | ||
| 524 | 527 | cpSync(src, dest, mustNotMutateObjectDeep({ preserveTimestamps: true, recursive: true })); | |
| 525 | 528 | assertDirEquivalent(src, dest); | |
| 526 | 529 | const srcStat = lstatSync(join(src, 'foo.txt')); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -78,6 +78,7 @@ declare namespace InternalFSBinding { | |||
| 78 | 78 | ||
| 79 | 79 | function cpSyncCheckPaths(src: StringOrBuffer, dest: StringOrBuffer, dereference: boolean, recursive: boolean): void; | |
| 80 | 80 | function cpSyncOverrideFile(src: StringOrBuffer, dest: StringOrBuffer, mode: number, preserveTimestamps: boolean): void; | |
| 81 | + function cpSyncCopyDir(src: StringOrBuffer, dest: StringOrBuffer, force: boolean, errorOnExist: boolean, verbatimSymlinks: boolean, dereference: boolean): void; | ||
| 81 | 82 | ||
| 82 | 83 | function fchmod(fd: number, mode: number, req: FSReqCallback): void; | |
| 83 | 84 | function fchmod(fd: number, mode: number): void; | |
@@ -262,6 +263,7 @@ export interface FsBinding { | |||
| 262 | 263 | copyFile: typeof InternalFSBinding.copyFile; | |
| 263 | 264 | cpSyncCheckPaths: typeof InternalFSBinding.cpSyncCheckPaths; | |
| 264 | 265 | cpSyncOverrideFile: typeof InternalFSBinding.cpSyncOverrideFile; | |
| 266 | + cpSyncCopyDir: typeof InternalFSBinding.cpSyncCopyDir; | ||
| 265 | 267 | fchmod: typeof InternalFSBinding.fchmod; | |
| 266 | 268 | fchown: typeof InternalFSBinding.fchown; | |
| 267 | 269 | fdatasync: typeof InternalFSBinding.fdatasync; | |
| Back | FazBrowse Home | New Git URL |
0 commit comments