@@ -2106,6 +2106,169 @@ static void OpenFileHandle(const FunctionCallbackInfo<Value>& args) {
21062106 }
21072107}
21082108
2109+ // TODO(@anonrig): Implement v8 fast APi calls for `cpSync`.
2110+ static void CpSync (const FunctionCallbackInfo<Value>& args) {
2111+ Environment* env = Environment::GetCurrent (args);
2112+ CHECK_EQ (args.Length (),
2113+ 8 ); // src, dest, preserveTimestamps, dereference, errorOnExist,
2114+ // force, recursive, verbatimSymlinks
2115+ BufferValue src (env->isolate (), args[0 ]);
2116+ CHECK_NOT_NULL (*src);
2117+ ToNamespacedPath (env, &src);
2118+ THROW_IF_INSUFFICIENT_PERMISSIONS (
2119+ env, permission::PermissionScope::kFileSystemRead , src.ToStringView ());
2120+
2121+ BufferValue dest (env->isolate (), args[1 ]);
2122+ CHECK_NOT_NULL (*dest);
2123+ ToNamespacedPath (env, &dest);
2124+ THROW_IF_INSUFFICIENT_PERMISSIONS (
2125+ env, permission::PermissionScope::kFileSystemWrite , dest.ToStringView ());
2126+
2127+ bool preserveTimestamps = args[2 ]->IsTrue ();
2128+ bool dereference = args[3 ]->IsTrue ();
2129+ bool errorOnExist = args[4 ]->IsTrue ();
2130+ bool force = args[5 ]->IsTrue ();
2131+ bool recursive = args[6 ]->IsTrue ();
2132+ bool verbatimSymlinks = args[7 ]->IsTrue ();
2133+
2134+ using copy_options = std::filesystem::copy_options;
2135+ using file_type = std::filesystem::file_type;
2136+
2137+ std::error_code error_code{};
2138+ copy_options options = copy_options ::skip_existing;
2139+
2140+ // When true timestamps from src will be preserved.
2141+ if (preserveTimestamps) options |= copy_options::create_hard_links;
2142+ // Dereference symbolic links.
2143+ if (dereference) options |= copy_options::copy_symlinks;
2144+ // Overwrite existing file or directory.
2145+ if (force) options |= copy_options::overwrite_existing;
2146+ // Copy directories recursively.
2147+ if (recursive) options |= copy_options::recursive;
2148+ // When true, path resolution for symlinks will be skipped.
2149+ if (verbatimSymlinks) options |= copy_options::skip_symlinks;
2150+
2151+ auto src_path = std::filesystem::path (src.ToStringView ());
2152+ auto dest_path = std::filesystem::path (dest.ToStringView ());
2153+
2154+ auto resolved_src = src_path.lexically_normal ();
2155+ auto resolved_dest = dest_path.lexically_normal ();
2156+
2157+ if (resolved_src == resolved_dest) {
2158+ std::string message =
2159+ " src and dest cannot be the same " + resolved_src.string ();
2160+ return THROW_ERR_FS_CP_EINVAL (env, message.c_str ());
2161+ }
2162+
2163+ auto get_stat = [](const std::filesystem::path& path)
2164+ -> std::optional<std::filesystem::file_status> {
2165+ std::error_code error_code{};
2166+ auto file_status = std::filesystem::status (path, error_code);
2167+ if (error_code) {
2168+ return std::nullopt ;
2169+ }
2170+ return file_status;
2171+ };
2172+
2173+ auto src_type = get_stat (src_path);
2174+ auto dest_type = get_stat (dest_path);
2175+
2176+ if (!src_type.has_value ()) {
2177+ std::string message = " Src path " + src_path.string () + " does not exist" ;
2178+ return THROW_ERR_FS_CP_EINVAL (env, message.c_str ());
2179+ }
2180+
2181+ const bool src_is_dir = src_type->type () == file_type::directory;
2182+
2183+ if (dest_type.has_value ()) {
2184+ // Check if src and dest are identical.
2185+ if (std::filesystem::equivalent (src_path, dest_path)) {
2186+ std::string message =
2187+ " src and dest cannot be the same " + dest_path.string ();
2188+ return THROW_ERR_FS_CP_EINVAL (env, message.c_str ());
2189+ }
2190+
2191+ const bool dest_is_dir = dest_type->type () == file_type::directory;
2192+
2193+ if (src_is_dir && !dest_is_dir) {
2194+ std::string message = " Cannot overwrite non-directory " +
2195+ src_path.string () + " with directory " +
2196+ dest_path.string ();
2197+ return THROW_ERR_FS_CP_DIR_TO_NON_DIR (env, message.c_str ());
2198+ }
2199+
2200+ if (!src_is_dir && dest_is_dir) {
2201+ std::string message = " Cannot overwrite directory " + dest_path.string () +
2202+ " with non-directory " + src_path.string ();
2203+ return THROW_ERR_FS_CP_NON_DIR_TO_DIR (env, message.c_str ());
2204+ }
2205+ }
2206+
2207+ if (src_is_dir && dest_path.string ().starts_with (src_path.string ())) {
2208+ std::string message = " Cannot copy " + src_path.string () +
2209+ " to a subdirectory of self " + dest_path.string ();
2210+ return THROW_ERR_FS_CP_EINVAL (env, message.c_str ());
2211+ }
2212+
2213+ auto dest_parent = dest_path.parent_path ();
2214+ // "/" parent is itself. Therefore, we need to check if the parent is the same
2215+ // as itself.
2216+ while (src_path.parent_path () != dest_parent &&
2217+ dest_parent.has_parent_path () &&
2218+ dest_parent.parent_path () != dest_parent) {
2219+ if (std::filesystem::equivalent (
2220+ src_path, dest_path.parent_path (), error_code)) {
2221+ std::string message = " Cannot copy " + src_path.string () +
2222+ " to a subdirectory of self " + dest_path.string ();
2223+ return THROW_ERR_FS_CP_EINVAL (env, message.c_str ());
2224+ }
2225+
2226+ // If equivalent fails, it's highly likely that dest_parent does not exist
2227+ if (error_code) {
2228+ break ;
2229+ }
2230+
2231+ dest_parent = dest_parent.parent_path ();
2232+ }
2233+
2234+ if (src_is_dir && !recursive) {
2235+ std::string message = src_path.string () + " is a directory (not copied)" ;
2236+ return THROW_ERR_FS_EISDIR (env, message.c_str ());
2237+ }
2238+
2239+ switch (src_type->type ()) {
2240+ case file_type::socket: {
2241+ std::string message = " Cannot copy a socket file: " + dest_path.string ();
2242+ return THROW_ERR_FS_CP_SOCKET (env, message.c_str ());
2243+ }
2244+ case file_type::fifo: {
2245+ std::string message = " Cannot copy a FIFO pipe: " + dest_path.string ();
2246+ return THROW_ERR_FS_CP_FIFO_PIPE (env, message.c_str ());
2247+ }
2248+ case file_type::unknown: {
2249+ std::string message =
2250+ " Cannot copy an unknown file type: " + dest_path.string ();
2251+ return THROW_ERR_FS_CP_UNKNOWN (env, message.c_str ());
2252+ }
2253+ default :
2254+ break ;
2255+ }
2256+
2257+ if (dest_type.has_value () && errorOnExist) {
2258+ std::string message = dest_path.string () + " already exists" ;
2259+ return THROW_ERR_FS_CP_EEXIST (env, message.c_str ());
2260+ }
2261+
2262+ std::filesystem::create_directories (dest_path, error_code);
2263+ std::filesystem::copy (src_path, dest_path, options, error_code);
2264+ if (error_code) {
2265+ std::string message = " Unhandled error " +
2266+ std::to_string (error_code.value ()) + " : " +
2267+ error_code.message ();
2268+ return THROW_ERR_FS_CP_EINVAL (env, message.c_str ());
2269+ }
2270+ }
2271+
21092272static void CopyFile (const FunctionCallbackInfo<Value>& args) {
21102273 Environment* env = Environment::GetCurrent (args);
21112274 Isolate* isolate = env->isolate ();
@@ -3344,6 +3507,7 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
33443507 SetMethod (isolate, target, " writeFileUtf8" , WriteFileUtf8);
33453508 SetMethod (isolate, target, " realpath" , RealPath);
33463509 SetMethod (isolate, target, " copyFile" , CopyFile);
3510+ SetMethod (isolate, target, " cpSync" , CpSync);
33473511
33483512 SetMethod (isolate, target, " chmod" , Chmod);
33493513 SetMethod (isolate, target, " fchmod" , FChmod);
@@ -3466,6 +3630,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
34663630 registry->Register (WriteFileUtf8);
34673631 registry->Register (RealPath);
34683632 registry->Register (CopyFile);
3633+ registry->Register (CpSync);
34693634
34703635 registry->Register (Chmod);
34713636 registry->Register (FChmod);
0 commit comments