@@ -2106,6 +2106,174 @@ 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+ 6 ); // src, dest, preserveTimestamps, errorOnExist,
2114+ // force, recursive
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+
2132+ using copy_options = std::filesystem::copy_options;
2133+ using file_type = std::filesystem::file_type;
2134+
2135+ std::error_code error_code{};
2136+ copy_options options = copy_options::copy_symlinks;
2137+
2138+ // When true timestamps from src will be preserved.
2139+ if (preserveTimestamps) options |= copy_options::create_hard_links;
2140+ // Overwrite existing file or directory.
2141+ if (force) {
2142+ options |= copy_options::overwrite_existing;
2143+ } else {
2144+ options |= copy_options::skip_existing;
2145+ }
2146+ // Copy directories recursively.
2147+ if (recursive) {
2148+ options |= copy_options::recursive;
2149+ }
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+ if (error_code == std::errc::file_exists) {
2266+ std::string message = " File already exists" ;
2267+ return THROW_ERR_FS_CP_EEXIST (env, message.c_str ());
2268+ }
2269+
2270+ std::string message = " Unhandled error " +
2271+ std::to_string (error_code.value ()) + " : " +
2272+ error_code.message ();
2273+ return THROW_ERR_FS_CP_EINVAL (env, message.c_str ());
2274+ }
2275+ }
2276+
21092277static void CopyFile (const FunctionCallbackInfo<Value>& args) {
21102278 Environment* env = Environment::GetCurrent (args);
21112279 Isolate* isolate = env->isolate ();
@@ -3344,6 +3512,7 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
33443512 SetMethod (isolate, target, " writeFileUtf8" , WriteFileUtf8);
33453513 SetMethod (isolate, target, " realpath" , RealPath);
33463514 SetMethod (isolate, target, " copyFile" , CopyFile);
3515+ SetMethod (isolate, target, " cpSync" , CpSync);
33473516
33483517 SetMethod (isolate, target, " chmod" , Chmod);
33493518 SetMethod (isolate, target, " fchmod" , FChmod);
@@ -3466,6 +3635,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
34663635 registry->Register (WriteFileUtf8);
34673636 registry->Register (RealPath);
34683637 registry->Register (CopyFile);
3638+ registry->Register (CpSync);
34693639
34703640 registry->Register (Chmod);
34713641 registry->Register (FChmod);
0 commit comments