Commit 8df4f519 by Edward Thomson

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.
parent 849f371e
/* fcopyfile on macOS is slower than a simple read/write loop? */
#undef USE_FCOPYFILE
#ifdef _WIN32 #ifdef _WIN32
#define RM_RETRY_COUNT 5 #define RM_RETRY_COUNT 5
...@@ -254,6 +257,16 @@ cl_fs_cleanup(void) ...@@ -254,6 +257,16 @@ cl_fs_cleanup(void)
#include <errno.h> #include <errno.h>
#include <string.h> #include <string.h>
#include <limits.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#if defined(__APPLE__) || defined(__FreeBSD__)
# include <copyfile.h>
#endif
static int static int
shell_out(char * const argv[]) shell_out(char * const argv[])
...@@ -281,31 +294,143 @@ shell_out(char * const argv[]) ...@@ -281,31 +294,143 @@ shell_out(char * const argv[])
return WEXITSTATUS(status); return WEXITSTATUS(status);
} }
static void basename_r(const char **out, int *out_len, const char *in)
{
size_t in_len = strlen(in), start_pos;
for (in_len = strlen(in); in_len; in_len--) {
if (in[in_len - 1] != '/')
break;
}
for (start_pos = in_len; start_pos; start_pos--) {
if (in[start_pos - 1] == '/')
break;
}
cl_assert(in_len - start_pos < INT_MAX);
if (in_len - start_pos > 0) {
*out = &in[start_pos];
*out_len = (in_len - start_pos);
} else {
*out = "/";
*out_len = 1;
}
}
static char *joinpath(const char *dir, const char *base, int base_len)
{
char *out;
int len;
if (base_len == -1) {
size_t bl = strlen(base);
cl_assert(bl < INT_MAX);
base_len = (int)bl;
}
len = strlen(dir) + base_len + 2;
cl_assert(len > 0);
cl_assert(out = malloc(len));
cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len);
return out;
}
static void static void
fs_copy(const char *_source, const char *dest) fs_copydir_helper(const char *source, const char *dest, int dmode)
{ {
char *argv[5]; DIR *source_dir;
char *source; struct dirent *d;
size_t source_len;
source = strdup(_source); mkdir(dest, dmode);
source_len = strlen(source);
if (source[source_len - 1] == '/') cl_assert_(source_dir = opendir(source), "Could not open source dir");
source[source_len - 1] = 0; while ((d = (errno = 0, readdir(source_dir))) != NULL) {
char *child;
argv[0] = "/bin/cp"; if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
argv[1] = "-R"; continue;
argv[2] = source;
argv[3] = (char *)dest;
argv[4] = NULL;
cl_must_pass_( child = joinpath(source, d->d_name, -1);
shell_out(argv), fs_copy(child, dest);
"Failed to copy test fixtures to sandbox" free(child);
); }
cl_assert_(errno == 0, "Failed to iterate source dir");
closedir(source_dir);
}
static void
fs_copyfile_helper(const char *source, const char *dest, int dmode)
{
int in, out;
cl_must_pass((in = open(source, O_RDONLY)));
cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dmode)));
#if USE_FCOPYFILE && (defined(__APPLE__) || defined(__FreeBSD__))
cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA));
#else
{
char buf[131072];
ssize_t ret;
while ((ret = read(in, buf, sizeof(buf))) > 0) {
size_t len = (size_t)ret;
while (len && (ret = write(out, buf, len)) > 0) {
cl_assert(ret <= (ssize_t)len);
len -= ret;
}
cl_assert(ret >= 0);
}
cl_assert(ret == 0);
}
#endif
close(in);
close(out);
}
static void
fs_copy(const char *source, const char *_dest)
{
char *dbuf = NULL;
const char *dest;
struct stat source_st, dest_st;
cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source");
if (lstat(_dest, &dest_st) == 0) {
const char *base;
int base_len;
/* Target exists and is directory; append basename */
cl_assert(S_ISDIR(dest_st.st_mode));
basename_r(&base, &base_len, source);
cl_assert(base_len < INT_MAX);
dbuf = joinpath(_dest, base, base_len);
dest = dbuf;
} else if (errno != ENOENT) {
cl_fail("Cannot copy; cannot stat destination");
} else {
dest = _dest;
}
if (S_ISDIR(source_st.st_mode)) {
fs_copydir_helper(source, dest, source_st.st_mode);
} else {
fs_copyfile_helper(source, dest, source_st.st_mode);
}
free(source); free(dbuf);
} }
static void static void
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment