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 #ifdef _WIN32
#define RM_RETRY_COUNT 5 #define RM_RETRY_COUNT 5
...@@ -254,74 +262,213 @@ cl_fs_cleanup(void) ...@@ -254,74 +262,213 @@ 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(__linux__)
# include <sys/sendfile.h>
#endif
static int #if defined(__APPLE__) || defined(__FreeBSD__)
shell_out(char * const argv[]) # include <copyfile.h>
#endif
static void basename_r(const char **out, int *out_len, const char *in)
{ {
int status, piderr; size_t in_len = strlen(in), start_pos;
pid_t pid;
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) { if (in_len - start_pos > 0) {
fprintf(stderr, *out = &in[start_pos];
"System error: `fork()` call failed (%d) - %s\n", *out_len = (in_len - start_pos);
errno, strerror(errno)); } else {
exit(-1); *out = "/";
*out_len = 1;
} }
}
static char *joinpath(const char *dir, const char *base, int base_len)
{
char *out;
int len;
if (pid == 0) { if (base_len == -1) {
execv(argv[0], argv); size_t bl = strlen(base);
cl_assert(bl < INT_MAX);
base_len = (int)bl;
} }
do { len = strlen(dir) + base_len + 2;
piderr = waitpid(pid, &status, WUNTRACED); cl_assert(len > 0);
} while (piderr < 0 && (errno == EAGAIN || errno == EINTR));
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 static void
fs_copy(const char *_source, const char *dest) fs_copy(const char *source, const char *_dest)
{ {
char *argv[5]; char *dbuf = NULL;
char *source; const char *dest;
size_t source_len; struct stat source_st, dest_st;
cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source");
source = strdup(_source); if (lstat(_dest, &dest_st) == 0) {
source_len = strlen(source); const char *base;
int base_len;
if (source[source_len - 1] == '/') /* Target exists and is directory; append basename */
source[source_len - 1] = 0; cl_assert(S_ISDIR(dest_st.st_mode));
argv[0] = "/bin/cp"; basename_r(&base, &base_len, source);
argv[1] = "-R"; cl_assert(base_len < INT_MAX);
argv[2] = source;
argv[3] = (char *)dest;
argv[4] = NULL;
cl_must_pass_( dbuf = joinpath(_dest, base, base_len);
shell_out(argv), dest = dbuf;
"Failed to copy test fixtures to sandbox" } 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 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"; child = joinpath(path, d->d_name, -1);
argv[1] = "-Rf"; fs_rm(child);
argv[2] = (char *)source; free(child);
argv[3] = NULL; }
cl_assert_(errno == 0, "Failed to iterate source dir");
closedir(dir);
cl_must_pass_(rmdir(path), "Could not remove directory");
}
cl_must_pass_( static void
shell_out(argv), fs_rm(const char *path)
"Failed to cleanup the sandbox" {
); 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 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