Skip to content

Commit 8df4f51

Browse files
committed
clar: copy files internally instead of /bin/cp
clar has historically shelled out to `/bin/cp` to copy test fixtures into a sandbox. This has two deficiencies: 1. It's slower than simply opening the source and destination and copying them in a read/write loop. On my Mac, the `/bin/cp` based approach takes ~2:40 for a full test pass. Using a read/write loop to copy the files ourselves takes ~1:50. 2. It's noisy. Since the leak detector follows fork/exec, we'll end up running the leak detector on `/bin/cp`. This would be fine, except that the leak detector spams the console on startup and shutdown, so it adds a _lot_ of additional information to the test runs that is useless. By not forking and using this internal system, we see much less output.
1 parent 849f371 commit 8df4f51

File tree

1 file changed

+143
-18
lines changed

1 file changed

+143
-18
lines changed

tests/clar/fs.h

Lines changed: 143 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/* fcopyfile on macOS is slower than a simple read/write loop? */
2+
#undef USE_FCOPYFILE
3+
14
#ifdef _WIN32
25

36
#define RM_RETRY_COUNT 5
@@ -254,6 +257,16 @@ cl_fs_cleanup(void)
254257

255258
#include <errno.h>
256259
#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
257270

258271
static int
259272
shell_out(char * const argv[])
@@ -281,31 +294,143 @@ shell_out(char * const argv[])
281294
return WEXITSTATUS(status);
282295
}
283296

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+
284343
static void
285-
fs_copy(const char *_source, const char *dest)
344+
fs_copydir_helper(const char *source, const char *dest, int dmode)
286345
{
287-
char *argv[5];
288-
char *source;
289-
size_t source_len;
346+
DIR *source_dir;
347+
struct dirent *d;
290348

291-
source = strdup(_source);
292-
source_len = strlen(source);
349+
mkdir(dest, dmode);
293350

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;
296354

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;
302357

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+
}
307432

308-
free(source);
433+
free(dbuf);
309434
}
310435

311436
static void

0 commit comments

Comments
 (0)