summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2020-06-02 09:26:11 +0100
committerGitHub <noreply@github.com>2020-06-02 09:26:11 +0100
commitd4b953f80005bc08c790ffe3ef0776ced370706c (patch)
tree9aa8bcb730c0dce3357bfc5f2f6a0d63ae78c538
parent849f371ea89257512ef505d0094d77b1ec812c53 (diff)
parent2a2c5b40274eeea55b049d3c3d7f2d733a0b137a (diff)
downloadlibgit2-d4b953f80005bc08c790ffe3ef0776ced370706c.tar.gz
Merge pull request #5528 from libgit2/ethomson/clar_internal
clar: use internal functions instead of /bin/cp and /bin/rm
-rw-r--r--tests/clar/fs.h235
1 files changed, 191 insertions, 44 deletions
diff --git a/tests/clar/fs.h b/tests/clar/fs.h
index 7c7dde6fc..87d345132 100644
--- a/tests/clar/fs.h
+++ b/tests/clar/fs.h
@@ -1,3 +1,11 @@
+/*
+ * By default, use a read/write loop to copy files on POSIX systems.
+ * On Linux, use sendfile by default as it's slightly faster. On
+ * macOS, we avoid fcopyfile by default because it's slightly slower.
+ */
+#undef USE_FCOPYFILE
+#define USE_SENDFILE 1
+
#ifdef _WIN32
#define RM_RETRY_COUNT 5
@@ -254,74 +262,213 @@ cl_fs_cleanup(void)
#include <errno.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(__linux__)
+# include <sys/sendfile.h>
+#endif
-static int
-shell_out(char * const argv[])
+#if defined(__APPLE__) || defined(__FreeBSD__)
+# include <copyfile.h>
+#endif
+
+static void basename_r(const char **out, int *out_len, const char *in)
{
- int status, piderr;
- pid_t pid;
+ 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;
+ }
- pid = fork();
+ cl_assert(in_len - start_pos < INT_MAX);
- if (pid < 0) {
- fprintf(stderr,
- "System error: `fork()` call failed (%d) - %s\n",
- errno, strerror(errno));
- exit(-1);
+ 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 (pid == 0) {
- execv(argv[0], argv);
+ if (base_len == -1) {
+ size_t bl = strlen(base);
+
+ cl_assert(bl < INT_MAX);
+ base_len = (int)bl;
}
- do {
- piderr = waitpid(pid, &status, WUNTRACED);
- } while (piderr < 0 && (errno == EAGAIN || errno == EINTR));
+ 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
+fs_copydir_helper(const char *source, const char *dest, int dest_mode)
+{
+ DIR *source_dir;
+ struct dirent *d;
+
+ mkdir(dest, dest_mode);
+
+ cl_assert_(source_dir = opendir(source), "Could not open source dir");
+ while ((d = (errno = 0, readdir(source_dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(source, d->d_name, -1);
+ fs_copy(child, dest);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+
+ closedir(source_dir);
+}
+
+static void
+fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode)
+{
+ int in, out;
+
+ cl_must_pass((in = open(source, O_RDONLY)));
+ cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode)));
+
+#if USE_FCOPYFILE && (defined(__APPLE__) || defined(__FreeBSD__))
+ ((void)(source_len)); /* unused */
+ cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA));
+#elif USE_SENDFILE && defined(__linux__)
+ {
+ ssize_t ret = 0;
+
+ while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) {
+ source_len -= (size_t)ret;
+ }
+ cl_assert(ret >= 0);
+ }
+#else
+ {
+ char buf[131072];
+ ssize_t ret;
+
+ ((void)(source_len)); /* unused */
- return WEXITSTATUS(status);
+ 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)
+fs_copy(const char *source, const char *_dest)
{
- char *argv[5];
- char *source;
- size_t source_len;
+ char *dbuf = NULL;
+ const char *dest;
+ struct stat source_st, dest_st;
+
+ cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source");
- source = strdup(_source);
- source_len = strlen(source);
+ if (lstat(_dest, &dest_st) == 0) {
+ const char *base;
+ int base_len;
- if (source[source_len - 1] == '/')
- source[source_len - 1] = 0;
+ /* Target exists and is directory; append basename */
+ cl_assert(S_ISDIR(dest_st.st_mode));
- argv[0] = "/bin/cp";
- argv[1] = "-R";
- argv[2] = source;
- argv[3] = (char *)dest;
- argv[4] = NULL;
+ basename_r(&base, &base_len, source);
+ cl_assert(base_len < INT_MAX);
- cl_must_pass_(
- shell_out(argv),
- "Failed to copy test fixtures to sandbox"
- );
+ dbuf = joinpath(_dest, base, base_len);
+ dest = dbuf;
+ } else if (errno != ENOENT) {
+ cl_fail("Cannot copy; cannot stat destination");
+ } else {
+ dest = _dest;
+ }
- free(source);
+ if (S_ISDIR(source_st.st_mode)) {
+ fs_copydir_helper(source, dest, source_st.st_mode);
+ } else {
+ fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode);
+ }
+
+ free(dbuf);
}
static void
-fs_rm(const char *source)
+fs_rmdir_helper(const char *path)
{
- char *argv[4];
+ DIR *dir;
+ struct dirent *d;
+
+ cl_assert_(dir = opendir(path), "Could not open dir");
+ while ((d = (errno = 0, readdir(dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
- argv[0] = "/bin/rm";
- argv[1] = "-Rf";
- argv[2] = (char *)source;
- argv[3] = NULL;
+ child = joinpath(path, d->d_name, -1);
+ fs_rm(child);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+ closedir(dir);
+
+ cl_must_pass_(rmdir(path), "Could not remove directory");
+}
- cl_must_pass_(
- shell_out(argv),
- "Failed to cleanup the sandbox"
- );
+static void
+fs_rm(const char *path)
+{
+ struct stat st;
+
+ if (lstat(path, &st)) {
+ if (errno == ENOENT)
+ return;
+
+ cl_fail("Cannot copy; cannot stat destination");
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ fs_rmdir_helper(path);
+ } else {
+ cl_must_pass(unlink(path));
+ }
}
void