|
| 1 | +/* |
| 2 | + * By default, use a read/write loop to copy files on POSIX systems. |
| 3 | + * On Linux, use sendfile by default as it's slightly faster. On |
| 4 | + * macOS, we avoid fcopyfile by default because it's slightly slower. |
| 5 | + */ |
| 6 | +#undef USE_FCOPYFILE |
| 7 | +#define USE_SENDFILE 1 |
| 8 | + |
1 | 9 | #ifdef _WIN32 |
2 | 10 |
|
3 | 11 | #define RM_RETRY_COUNT 5 |
@@ -254,74 +262,213 @@ cl_fs_cleanup(void) |
254 | 262 |
|
255 | 263 | #include <errno.h> |
256 | 264 | #include <string.h> |
| 265 | +#include <limits.h> |
| 266 | +#include <dirent.h> |
| 267 | +#include <fcntl.h> |
| 268 | +#include <unistd.h> |
| 269 | +#include <sys/types.h> |
| 270 | +#include <sys/stat.h> |
| 271 | + |
| 272 | +#if defined(__linux__) |
| 273 | +# include <sys/sendfile.h> |
| 274 | +#endif |
257 | 275 |
|
258 | | -static int |
259 | | -shell_out(char * const argv[]) |
| 276 | +#if defined(__APPLE__) || defined(__FreeBSD__) |
| 277 | +# include <copyfile.h> |
| 278 | +#endif |
| 279 | + |
| 280 | +static void basename_r(const char **out, int *out_len, const char *in) |
260 | 281 | { |
261 | | - int status, piderr; |
262 | | - pid_t pid; |
| 282 | + size_t in_len = strlen(in), start_pos; |
| 283 | + |
| 284 | + for (in_len = strlen(in); in_len; in_len--) { |
| 285 | + if (in[in_len - 1] != '/') |
| 286 | + break; |
| 287 | + } |
| 288 | + |
| 289 | + for (start_pos = in_len; start_pos; start_pos--) { |
| 290 | + if (in[start_pos - 1] == '/') |
| 291 | + break; |
| 292 | + } |
263 | 293 |
|
264 | | - pid = fork(); |
| 294 | + cl_assert(in_len - start_pos < INT_MAX); |
265 | 295 |
|
266 | | - if (pid < 0) { |
267 | | - fprintf(stderr, |
268 | | - "System error: `fork()` call failed (%d) - %s\n", |
269 | | - errno, strerror(errno)); |
270 | | - exit(-1); |
| 296 | + if (in_len - start_pos > 0) { |
| 297 | + *out = &in[start_pos]; |
| 298 | + *out_len = (in_len - start_pos); |
| 299 | + } else { |
| 300 | + *out = "/"; |
| 301 | + *out_len = 1; |
271 | 302 | } |
| 303 | +} |
| 304 | + |
| 305 | +static char *joinpath(const char *dir, const char *base, int base_len) |
| 306 | +{ |
| 307 | + char *out; |
| 308 | + int len; |
272 | 309 |
|
273 | | - if (pid == 0) { |
274 | | - execv(argv[0], argv); |
| 310 | + if (base_len == -1) { |
| 311 | + size_t bl = strlen(base); |
| 312 | + |
| 313 | + cl_assert(bl < INT_MAX); |
| 314 | + base_len = (int)bl; |
275 | 315 | } |
276 | 316 |
|
277 | | - do { |
278 | | - piderr = waitpid(pid, &status, WUNTRACED); |
279 | | - } while (piderr < 0 && (errno == EAGAIN || errno == EINTR)); |
| 317 | + len = strlen(dir) + base_len + 2; |
| 318 | + cl_assert(len > 0); |
| 319 | + |
| 320 | + cl_assert(out = malloc(len)); |
| 321 | + cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len); |
| 322 | + |
| 323 | + return out; |
| 324 | +} |
| 325 | + |
| 326 | +static void |
| 327 | +fs_copydir_helper(const char *source, const char *dest, int dest_mode) |
| 328 | +{ |
| 329 | + DIR *source_dir; |
| 330 | + struct dirent *d; |
| 331 | + |
| 332 | + mkdir(dest, dest_mode); |
| 333 | + |
| 334 | + cl_assert_(source_dir = opendir(source), "Could not open source dir"); |
| 335 | + while ((d = (errno = 0, readdir(source_dir))) != NULL) { |
| 336 | + char *child; |
| 337 | + |
| 338 | + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) |
| 339 | + continue; |
| 340 | + |
| 341 | + child = joinpath(source, d->d_name, -1); |
| 342 | + fs_copy(child, dest); |
| 343 | + free(child); |
| 344 | + } |
| 345 | + |
| 346 | + cl_assert_(errno == 0, "Failed to iterate source dir"); |
| 347 | + |
| 348 | + closedir(source_dir); |
| 349 | +} |
| 350 | + |
| 351 | +static void |
| 352 | +fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode) |
| 353 | +{ |
| 354 | + int in, out; |
| 355 | + |
| 356 | + cl_must_pass((in = open(source, O_RDONLY))); |
| 357 | + cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode))); |
| 358 | + |
| 359 | +#if USE_FCOPYFILE && (defined(__APPLE__) || defined(__FreeBSD__)) |
| 360 | + ((void)(source_len)); /* unused */ |
| 361 | + cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA)); |
| 362 | +#elif USE_SENDFILE && defined(__linux__) |
| 363 | + { |
| 364 | + ssize_t ret = 0; |
| 365 | + |
| 366 | + while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) { |
| 367 | + source_len -= (size_t)ret; |
| 368 | + } |
| 369 | + cl_assert(ret >= 0); |
| 370 | + } |
| 371 | +#else |
| 372 | + { |
| 373 | + char buf[131072]; |
| 374 | + ssize_t ret; |
| 375 | + |
| 376 | + ((void)(source_len)); /* unused */ |
280 | 377 |
|
281 | | - return WEXITSTATUS(status); |
| 378 | + while ((ret = read(in, buf, sizeof(buf))) > 0) { |
| 379 | + size_t len = (size_t)ret; |
| 380 | + |
| 381 | + while (len && (ret = write(out, buf, len)) > 0) { |
| 382 | + cl_assert(ret <= (ssize_t)len); |
| 383 | + len -= ret; |
| 384 | + } |
| 385 | + cl_assert(ret >= 0); |
| 386 | + } |
| 387 | + cl_assert(ret == 0); |
| 388 | + } |
| 389 | +#endif |
| 390 | + |
| 391 | + close(in); |
| 392 | + close(out); |
282 | 393 | } |
283 | 394 |
|
284 | 395 | static void |
285 | | -fs_copy(const char *_source, const char *dest) |
| 396 | +fs_copy(const char *source, const char *_dest) |
286 | 397 | { |
287 | | - char *argv[5]; |
288 | | - char *source; |
289 | | - size_t source_len; |
| 398 | + char *dbuf = NULL; |
| 399 | + const char *dest; |
| 400 | + struct stat source_st, dest_st; |
| 401 | + |
| 402 | + cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source"); |
290 | 403 |
|
291 | | - source = strdup(_source); |
292 | | - source_len = strlen(source); |
| 404 | + if (lstat(_dest, &dest_st) == 0) { |
| 405 | + const char *base; |
| 406 | + int base_len; |
293 | 407 |
|
294 | | - if (source[source_len - 1] == '/') |
295 | | - source[source_len - 1] = 0; |
| 408 | + /* Target exists and is directory; append basename */ |
| 409 | + cl_assert(S_ISDIR(dest_st.st_mode)); |
296 | 410 |
|
297 | | - argv[0] = "/bin/cp"; |
298 | | - argv[1] = "-R"; |
299 | | - argv[2] = source; |
300 | | - argv[3] = (char *)dest; |
301 | | - argv[4] = NULL; |
| 411 | + basename_r(&base, &base_len, source); |
| 412 | + cl_assert(base_len < INT_MAX); |
302 | 413 |
|
303 | | - cl_must_pass_( |
304 | | - shell_out(argv), |
305 | | - "Failed to copy test fixtures to sandbox" |
306 | | - ); |
| 414 | + dbuf = joinpath(_dest, base, base_len); |
| 415 | + dest = dbuf; |
| 416 | + } else if (errno != ENOENT) { |
| 417 | + cl_fail("Cannot copy; cannot stat destination"); |
| 418 | + } else { |
| 419 | + dest = _dest; |
| 420 | + } |
307 | 421 |
|
308 | | - free(source); |
| 422 | + if (S_ISDIR(source_st.st_mode)) { |
| 423 | + fs_copydir_helper(source, dest, source_st.st_mode); |
| 424 | + } else { |
| 425 | + fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode); |
| 426 | + } |
| 427 | + |
| 428 | + free(dbuf); |
309 | 429 | } |
310 | 430 |
|
311 | 431 | static void |
312 | | -fs_rm(const char *source) |
| 432 | +fs_rmdir_helper(const char *path) |
313 | 433 | { |
314 | | - char *argv[4]; |
| 434 | + DIR *dir; |
| 435 | + struct dirent *d; |
| 436 | + |
| 437 | + cl_assert_(dir = opendir(path), "Could not open dir"); |
| 438 | + while ((d = (errno = 0, readdir(dir))) != NULL) { |
| 439 | + char *child; |
| 440 | + |
| 441 | + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) |
| 442 | + continue; |
315 | 443 |
|
316 | | - argv[0] = "/bin/rm"; |
317 | | - argv[1] = "-Rf"; |
318 | | - argv[2] = (char *)source; |
319 | | - argv[3] = NULL; |
| 444 | + child = joinpath(path, d->d_name, -1); |
| 445 | + fs_rm(child); |
| 446 | + free(child); |
| 447 | + } |
| 448 | + |
| 449 | + cl_assert_(errno == 0, "Failed to iterate source dir"); |
| 450 | + closedir(dir); |
| 451 | + |
| 452 | + cl_must_pass_(rmdir(path), "Could not remove directory"); |
| 453 | +} |
320 | 454 |
|
321 | | - cl_must_pass_( |
322 | | - shell_out(argv), |
323 | | - "Failed to cleanup the sandbox" |
324 | | - ); |
| 455 | +static void |
| 456 | +fs_rm(const char *path) |
| 457 | +{ |
| 458 | + struct stat st; |
| 459 | + |
| 460 | + if (lstat(path, &st)) { |
| 461 | + if (errno == ENOENT) |
| 462 | + return; |
| 463 | + |
| 464 | + cl_fail("Cannot copy; cannot stat destination"); |
| 465 | + } |
| 466 | + |
| 467 | + if (S_ISDIR(st.st_mode)) { |
| 468 | + fs_rmdir_helper(path); |
| 469 | + } else { |
| 470 | + cl_must_pass(unlink(path)); |
| 471 | + } |
325 | 472 | } |
326 | 473 |
|
327 | 474 | void |
|
0 commit comments