@@ -2106,6 +2106,177 @@ 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_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
+
2109
2280
static void CopyFile (const FunctionCallbackInfo<Value>& args) {
2110
2281
Environment* env = Environment::GetCurrent (args);
2111
2282
Isolate* isolate = env->isolate ();
@@ -3344,6 +3515,7 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
3344
3515
SetMethod (isolate, target, " writeFileUtf8" , WriteFileUtf8);
3345
3516
SetMethod (isolate, target, " realpath" , RealPath);
3346
3517
SetMethod (isolate, target, " copyFile" , CopyFile);
3518
+ SetMethod (isolate, target, " cpSync" , CpSync);
3347
3519
3348
3520
SetMethod (isolate, target, " chmod" , Chmod);
3349
3521
SetMethod (isolate, target, " fchmod" , FChmod);
@@ -3466,6 +3638,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
3466
3638
registry->Register (WriteFileUtf8);
3467
3639
registry->Register (RealPath);
3468
3640
registry->Register (CopyFile);
3641
+ registry->Register (CpSync);
3469
3642
3470
3643
registry->Register (Chmod);
3471
3644
registry->Register (FChmod);
0 commit comments