@@ -2106,6 +2106,177 @@ 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+ 7 ); // src, dest, preserveTimestamps, 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 errorOnExist = args[3 ]->IsTrue ();
2129+ bool force = args[4 ]->IsTrue ();
2130+ bool recursive = args[5 ]->IsTrue ();
2131+ bool verbatimSymlinks = args[6 ]->IsTrue ();
2132+
2133+ using copy_options = std::filesystem::copy_options;
2134+ using file_type = std::filesystem::file_type;
2135+
2136+ std::error_code error_code{};
2137+ copy_options options = copy_options::copy_symlinks;
2138+
2139+ // When true timestamps from src will be preserved.
2140+ if (preserveTimestamps) options |= copy_options::create_hard_links;
2141+ // Overwrite existing file or directory.
2142+ if (force) {
2143+ options |= copy_options::overwrite_existing;
2144+ } else {
2145+ options |= copy_options::skip_existing;
2146+ }
2147+ // Copy directories recursively.
2148+ if (recursive) {
2149+ options |= copy_options::recursive;
2150+ }
2151+ // When true, path resolution for symlinks will be skipped.
2152+ if (verbatimSymlinks) options |= copy_options::copy_symlinks;
2153+
2154+ auto src_path = std::filesystem::path (src.ToStringView ());
2155+ auto dest_path = std::filesystem::path (dest.ToStringView ());
2156+
2157+ auto resolved_src = src_path.lexically_normal ();
2158+ auto resolved_dest = dest_path.lexically_normal ();
2159+
2160+ if (resolved_src == resolved_dest) {
2161+ std::string message =
2162+ " src and dest cannot be the same " + resolved_src.string ();
2163+ return THROW_ERR_FS_CP_EINVAL (env, message.c_str ());
2164+ }
2165+
2166+ auto get_stat = [](const std::filesystem::path& path)
2167+ -> std::optional<std::filesystem::file_status> {
2168+ std::error_code error_code{};
2169+ auto file_status = std::filesystem::status (path, error_code);
2170+ if (error_code) {
2171+ return std::nullopt ;
2172+ }
2173+ return file_status;
2174+ };
2175+
2176+ auto src_type = get_stat (src_path);
2177+ auto dest_type = get_stat (dest_path);
2178+
2179+ if (!src_type.has_value ()) {
2180+ std::string message = " Src path " + src_path.string () + " does not exist" ;
2181+ return THROW_ERR_FS_CP_EINVAL (env, message.c_str ());
2182+ }
2183+
2184+ const bool src_is_dir = src_type->type () == file_type::directory;
2185+
2186+ if (dest_type.has_value ()) {
2187+ // Check if src and dest are identical.
2188+ if (std::filesystem::equivalent (src_path, dest_path)) {
2189+ std::string message =
2190+ " src and dest cannot be the same " + dest_path.string ();
2191+ return THROW_ERR_FS_CP_EINVAL (env, message.c_str ());
2192+ }
2193+
2194+ const bool dest_is_dir = dest_type->type () == file_type::directory;
2195+
2196+ if (src_is_dir && !dest_is_dir) {
2197+ std::string message = " Cannot overwrite non-directory " +
2198+ src_path.string () + " with directory " +
2199+ dest_path.string ();
2200+ return THROW_ERR_FS_CP_DIR_TO_NON_DIR (env, message.c_str ());
2201+ }
2202+
2203+ if (!src_is_dir && dest_is_dir) {
2204+ std::string message = " Cannot overwrite directory " + dest_path.string () +
2205+ " with non-directory " + src_path.string ();
2206+ return THROW_ERR_FS_CP_NON_DIR_TO_DIR (env, message.c_str ());
2207+ }
2208+ }
2209+
2210+ if (src_is_dir && dest_path.string ().starts_with (src_path.string ())) {
2211+ std::string message = " Cannot copy " + src_path.string () +
2212+ " to a subdirectory of self " + dest_path.string ();
2213+ return THROW_ERR_FS_CP_EINVAL (env, message.c_str ());
2214+ }
2215+
2216+ auto dest_parent = dest_path.parent_path ();
2217+ // "/" parent is itself. Therefore, we need to check if the parent is the same
2218+ // as itself.
2219+ while (src_path.parent_path () != dest_parent &&
2220+ dest_parent.has_parent_path () &&
2221+ dest_parent.parent_path () != dest_parent) {
2222+ if (std::filesystem::equivalent (
2223+ src_path, dest_path.parent_path (), error_code)) {
2224+ std::string message = " Cannot copy " + src_path.string () +
2225+ " to a subdirectory of self " + dest_path.string ();
2226+ return THROW_ERR_FS_CP_EINVAL (env, message.c_str ());
2227+ }
2228+
2229+ // If equivalent fails, it's highly likely that dest_parent does not exist
2230+ if (error_code) {
2231+ break ;
2232+ }
2233+
2234+ dest_parent = dest_parent.parent_path ();
2235+ }
2236+
2237+ if (src_is_dir && !recursive) {
2238+ std::string message = src_path.string () + " is a directory (not copied)" ;
2239+ return THROW_ERR_FS_EISDIR (env, message.c_str ());
2240+ }
2241+
2242+ switch (src_type->type ()) {
2243+ case file_type::socket: {
2244+ std::string message = " Cannot copy a socket file: " + dest_path.string ();
2245+ return THROW_ERR_FS_CP_SOCKET (env, message.c_str ());
2246+ }
2247+ case file_type::fifo: {
2248+ std::string message = " Cannot copy a FIFO pipe: " + dest_path.string ();
2249+ return THROW_ERR_FS_CP_FIFO_PIPE (env, message.c_str ());
2250+ }
2251+ case file_type::unknown: {
2252+ std::string message =
2253+ " Cannot copy an unknown file type: " + dest_path.string ();
2254+ return THROW_ERR_FS_CP_UNKNOWN (env, message.c_str ());
2255+ }
2256+ default :
2257+ break ;
2258+ }
2259+
2260+ if (dest_type.has_value () && errorOnExist) {
2261+ std::string message = dest_path.string () + " already exists" ;
2262+ return THROW_ERR_FS_CP_EEXIST (env, message.c_str ());
2263+ }
2264+
2265+ std::filesystem::create_directories (dest_path, error_code);
2266+ std::filesystem::copy (src_path, dest_path, options, error_code);
2267+ if (error_code) {
2268+ if (error_code == std::errc::file_exists) {
2269+ std::string message = " File already exists" ;
2270+ return THROW_ERR_FS_CP_EEXIST (env, message.c_str ());
2271+ }
2272+
2273+ std::string message = " Unhandled error " +
2274+ std::to_string (error_code.value ()) + " : " +
2275+ error_code.message ();
2276+ return THROW_ERR_FS_CP_EINVAL (env, message.c_str ());
2277+ }
2278+ }
2279+
21092280static void CopyFile (const FunctionCallbackInfo<Value>& args) {
21102281 Environment* env = Environment::GetCurrent (args);
21112282 Isolate* isolate = env->isolate ();
@@ -3344,6 +3515,7 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
33443515 SetMethod (isolate, target, " writeFileUtf8" , WriteFileUtf8);
33453516 SetMethod (isolate, target, " realpath" , RealPath);
33463517 SetMethod (isolate, target, " copyFile" , CopyFile);
3518+ SetMethod (isolate, target, " cpSync" , CpSync);
33473519
33483520 SetMethod (isolate, target, " chmod" , Chmod);
33493521 SetMethod (isolate, target, " fchmod" , FChmod);
@@ -3466,6 +3638,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
34663638 registry->Register (WriteFileUtf8);
34673639 registry->Register (RealPath);
34683640 registry->Register (CopyFile);
3641+ registry->Register (CpSync);
34693642
34703643 registry->Register (Chmod);
34713644 registry->Register (FChmod);
0 commit comments