Unverified Commit d4b953f8 by Edward Thomson Committed by GitHub

Merge pull request #5528 from libgit2/ethomson/clar_internal

clar: use internal functions instead of /bin/cp and /bin/rm
parents 849f371e 2a2c5b40
/*
* 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
......
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