|
| 1 | +/* fcopyfile on macOS is slower than a simple read/write loop? */ |
| 2 | +#undef USE_FCOPYFILE |
| 3 | + |
1 | 4 | #ifdef _WIN32 |
2 | 5 |
|
3 | 6 | #define RM_RETRY_COUNT 5 |
@@ -254,6 +257,16 @@ cl_fs_cleanup(void) |
254 | 257 |
|
255 | 258 | #include <errno.h> |
256 | 259 | #include <string.h> |
| 260 | +#include <limits.h> |
| 261 | +#include <dirent.h> |
| 262 | +#include <fcntl.h> |
| 263 | +#include <unistd.h> |
| 264 | +#include <sys/types.h> |
| 265 | +#include <sys/stat.h> |
| 266 | + |
| 267 | +#if defined(__APPLE__) || defined(__FreeBSD__) |
| 268 | +# include <copyfile.h> |
| 269 | +#endif |
257 | 270 |
|
258 | 271 | static int |
259 | 272 | shell_out(char * const argv[]) |
@@ -281,31 +294,143 @@ shell_out(char * const argv[]) |
281 | 294 | return WEXITSTATUS(status); |
282 | 295 | } |
283 | 296 |
|
| 297 | +static void basename_r(const char **out, int *out_len, const char *in) |
| 298 | +{ |
| 299 | + size_t in_len = strlen(in), start_pos; |
| 300 | + |
| 301 | + for (in_len = strlen(in); in_len; in_len--) { |
| 302 | + if (in[in_len - 1] != '/') |
| 303 | + break; |
| 304 | + } |
| 305 | + |
| 306 | + for (start_pos = in_len; start_pos; start_pos--) { |
| 307 | + if (in[start_pos - 1] == '/') |
| 308 | + break; |
| 309 | + } |
| 310 | + |
| 311 | + cl_assert(in_len - start_pos < INT_MAX); |
| 312 | + |
| 313 | + if (in_len - start_pos > 0) { |
| 314 | + *out = &in[start_pos]; |
| 315 | + *out_len = (in_len - start_pos); |
| 316 | + } else { |
| 317 | + *out = "/"; |
| 318 | + *out_len = 1; |
| 319 | + } |
| 320 | +} |
| 321 | + |
| 322 | +static char *joinpath(const char *dir, const char *base, int base_len) |
| 323 | +{ |
| 324 | + char *out; |
| 325 | + int len; |
| 326 | + |
| 327 | + if (base_len == -1) { |
| 328 | + size_t bl = strlen(base); |
| 329 | + |
| 330 | + cl_assert(bl < INT_MAX); |
| 331 | + base_len = (int)bl; |
| 332 | + } |
| 333 | + |
| 334 | + len = strlen(dir) + base_len + 2; |
| 335 | + cl_assert(len > 0); |
| 336 | + |
| 337 | + cl_assert(out = malloc(len)); |
| 338 | + cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len); |
| 339 | + |
| 340 | + return out; |
| 341 | +} |
| 342 | + |
284 | 343 | static void |
285 | | -fs_copy(const char *_source, const char *dest) |
| 344 | +fs_copydir_helper(const char *source, const char *dest, int dmode) |
286 | 345 | { |
287 | | - char *argv[5]; |
288 | | - char *source; |
289 | | - size_t source_len; |
| 346 | + DIR *source_dir; |
| 347 | + struct dirent *d; |
290 | 348 |
|
291 | | - source = strdup(_source); |
292 | | - source_len = strlen(source); |
| 349 | + mkdir(dest, dmode); |
293 | 350 |
|
294 | | - if (source[source_len - 1] == '/') |
295 | | - source[source_len - 1] = 0; |
| 351 | + cl_assert_(source_dir = opendir(source), "Could not open source dir"); |
| 352 | + while ((d = (errno = 0, readdir(source_dir))) != NULL) { |
| 353 | + char *child; |
296 | 354 |
|
297 | | - argv[0] = "/bin/cp"; |
298 | | - argv[1] = "-R"; |
299 | | - argv[2] = source; |
300 | | - argv[3] = (char *)dest; |
301 | | - argv[4] = NULL; |
| 355 | + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) |
| 356 | + continue; |
302 | 357 |
|
303 | | - cl_must_pass_( |
304 | | - shell_out(argv), |
305 | | - "Failed to copy test fixtures to sandbox" |
306 | | - ); |
| 358 | + child = joinpath(source, d->d_name, -1); |
| 359 | + fs_copy(child, dest); |
| 360 | + free(child); |
| 361 | + } |
| 362 | + |
| 363 | + cl_assert_(errno == 0, "Failed to iterate source dir"); |
| 364 | + |
| 365 | + closedir(source_dir); |
| 366 | +} |
| 367 | + |
| 368 | +static void |
| 369 | +fs_copyfile_helper(const char *source, const char *dest, int dmode) |
| 370 | +{ |
| 371 | + int in, out; |
| 372 | + |
| 373 | + cl_must_pass((in = open(source, O_RDONLY))); |
| 374 | + cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dmode))); |
| 375 | + |
| 376 | +#if USE_FCOPYFILE && (defined(__APPLE__) || defined(__FreeBSD__)) |
| 377 | + cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA)); |
| 378 | +#else |
| 379 | + { |
| 380 | + char buf[131072]; |
| 381 | + ssize_t ret; |
| 382 | + |
| 383 | + while ((ret = read(in, buf, sizeof(buf))) > 0) { |
| 384 | + size_t len = (size_t)ret; |
| 385 | + |
| 386 | + while (len && (ret = write(out, buf, len)) > 0) { |
| 387 | + cl_assert(ret <= (ssize_t)len); |
| 388 | + len -= ret; |
| 389 | + } |
| 390 | + cl_assert(ret >= 0); |
| 391 | + } |
| 392 | + cl_assert(ret == 0); |
| 393 | + } |
| 394 | +#endif |
| 395 | + |
| 396 | + close(in); |
| 397 | + close(out); |
| 398 | +} |
| 399 | + |
| 400 | +static void |
| 401 | +fs_copy(const char *source, const char *_dest) |
| 402 | +{ |
| 403 | + char *dbuf = NULL; |
| 404 | + const char *dest; |
| 405 | + struct stat source_st, dest_st; |
| 406 | + |
| 407 | + cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source"); |
| 408 | + |
| 409 | + if (lstat(_dest, &dest_st) == 0) { |
| 410 | + const char *base; |
| 411 | + int base_len; |
| 412 | + |
| 413 | + /* Target exists and is directory; append basename */ |
| 414 | + cl_assert(S_ISDIR(dest_st.st_mode)); |
| 415 | + |
| 416 | + basename_r(&base, &base_len, source); |
| 417 | + cl_assert(base_len < INT_MAX); |
| 418 | + |
| 419 | + dbuf = joinpath(_dest, base, base_len); |
| 420 | + dest = dbuf; |
| 421 | + } else if (errno != ENOENT) { |
| 422 | + cl_fail("Cannot copy; cannot stat destination"); |
| 423 | + } else { |
| 424 | + dest = _dest; |
| 425 | + } |
| 426 | + |
| 427 | + if (S_ISDIR(source_st.st_mode)) { |
| 428 | + fs_copydir_helper(source, dest, source_st.st_mode); |
| 429 | + } else { |
| 430 | + fs_copyfile_helper(source, dest, source_st.st_mode); |
| 431 | + } |
307 | 432 |
|
308 | | - free(source); |
| 433 | + free(dbuf); |
309 | 434 | } |
310 | 435 |
|
311 | 436 | static void |
|
0 commit comments