diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2020-06-02 09:26:11 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-02 09:26:11 +0100 |
commit | d4b953f80005bc08c790ffe3ef0776ced370706c (patch) | |
tree | 9aa8bcb730c0dce3357bfc5f2f6a0d63ae78c538 | |
parent | 849f371ea89257512ef505d0094d77b1ec812c53 (diff) | |
parent | 2a2c5b40274eeea55b049d3c3d7f2d733a0b137a (diff) | |
download | libgit2-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.h | 235 |
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 |