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