Skip to content

Commit d4b953f

Browse files
authored
Merge pull request libgit2#5528 from libgit2/ethomson/clar_internal
clar: use internal functions instead of /bin/cp and /bin/rm
2 parents 849f371 + 2a2c5b4 commit d4b953f

File tree

1 file changed

+191
-44
lines changed

1 file changed

+191
-44
lines changed

tests/clar/fs.h

Lines changed: 191 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
/*
2+
* By default, use a read/write loop to copy files on POSIX systems.
3+
* On Linux, use sendfile by default as it's slightly faster. On
4+
* macOS, we avoid fcopyfile by default because it's slightly slower.
5+
*/
6+
#undef USE_FCOPYFILE
7+
#define USE_SENDFILE 1
8+
19
#ifdef _WIN32
210

311
#define RM_RETRY_COUNT 5
@@ -254,74 +262,213 @@ cl_fs_cleanup(void)
254262

255263
#include <errno.h>
256264
#include <string.h>
265+
#include <limits.h>
266+
#include <dirent.h>
267+
#include <fcntl.h>
268+
#include <unistd.h>
269+
#include <sys/types.h>
270+
#include <sys/stat.h>
271+
272+
#if defined(__linux__)
273+
# include <sys/sendfile.h>
274+
#endif
257275

258-
static int
259-
shell_out(char * const argv[])
276+
#if defined(__APPLE__) || defined(__FreeBSD__)
277+
# include <copyfile.h>
278+
#endif
279+
280+
static void basename_r(const char **out, int *out_len, const char *in)
260281
{
261-
int status, piderr;
262-
pid_t pid;
282+
size_t in_len = strlen(in), start_pos;
283+
284+
for (in_len = strlen(in); in_len; in_len--) {
285+
if (in[in_len - 1] != '/')
286+
break;
287+
}
288+
289+
for (start_pos = in_len; start_pos; start_pos--) {
290+
if (in[start_pos - 1] == '/')
291+
break;
292+
}
263293

264-
pid = fork();
294+
cl_assert(in_len - start_pos < INT_MAX);
265295

266-
if (pid < 0) {
267-
fprintf(stderr,
268-
"System error: `fork()` call failed (%d) - %s\n",
269-
errno, strerror(errno));
270-
exit(-1);
296+
if (in_len - start_pos > 0) {
297+
*out = &in[start_pos];
298+
*out_len = (in_len - start_pos);
299+
} else {
300+
*out = "/";
301+
*out_len = 1;
271302
}
303+
}
304+
305+
static char *joinpath(const char *dir, const char *base, int base_len)
306+
{
307+
char *out;
308+
int len;
272309

273-
if (pid == 0) {
274-
execv(argv[0], argv);
310+
if (base_len == -1) {
311+
size_t bl = strlen(base);
312+
313+
cl_assert(bl < INT_MAX);
314+
base_len = (int)bl;
275315
}
276316

277-
do {
278-
piderr = waitpid(pid, &status, WUNTRACED);
279-
} while (piderr < 0 && (errno == EAGAIN || errno == EINTR));
317+
len = strlen(dir) + base_len + 2;
318+
cl_assert(len > 0);
319+
320+
cl_assert(out = malloc(len));
321+
cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len);
322+
323+
return out;
324+
}
325+
326+
static void
327+
fs_copydir_helper(const char *source, const char *dest, int dest_mode)
328+
{
329+
DIR *source_dir;
330+
struct dirent *d;
331+
332+
mkdir(dest, dest_mode);
333+
334+
cl_assert_(source_dir = opendir(source), "Could not open source dir");
335+
while ((d = (errno = 0, readdir(source_dir))) != NULL) {
336+
char *child;
337+
338+
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
339+
continue;
340+
341+
child = joinpath(source, d->d_name, -1);
342+
fs_copy(child, dest);
343+
free(child);
344+
}
345+
346+
cl_assert_(errno == 0, "Failed to iterate source dir");
347+
348+
closedir(source_dir);
349+
}
350+
351+
static void
352+
fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode)
353+
{
354+
int in, out;
355+
356+
cl_must_pass((in = open(source, O_RDONLY)));
357+
cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode)));
358+
359+
#if USE_FCOPYFILE && (defined(__APPLE__) || defined(__FreeBSD__))
360+
((void)(source_len)); /* unused */
361+
cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA));
362+
#elif USE_SENDFILE && defined(__linux__)
363+
{
364+
ssize_t ret = 0;
365+
366+
while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) {
367+
source_len -= (size_t)ret;
368+
}
369+
cl_assert(ret >= 0);
370+
}
371+
#else
372+
{
373+
char buf[131072];
374+
ssize_t ret;
375+
376+
((void)(source_len)); /* unused */
280377

281-
return WEXITSTATUS(status);
378+
while ((ret = read(in, buf, sizeof(buf))) > 0) {
379+
size_t len = (size_t)ret;
380+
381+
while (len && (ret = write(out, buf, len)) > 0) {
382+
cl_assert(ret <= (ssize_t)len);
383+
len -= ret;
384+
}
385+
cl_assert(ret >= 0);
386+
}
387+
cl_assert(ret == 0);
388+
}
389+
#endif
390+
391+
close(in);
392+
close(out);
282393
}
283394

284395
static void
285-
fs_copy(const char *_source, const char *dest)
396+
fs_copy(const char *source, const char *_dest)
286397
{
287-
char *argv[5];
288-
char *source;
289-
size_t source_len;
398+
char *dbuf = NULL;
399+
const char *dest;
400+
struct stat source_st, dest_st;
401+
402+
cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source");
290403

291-
source = strdup(_source);
292-
source_len = strlen(source);
404+
if (lstat(_dest, &dest_st) == 0) {
405+
const char *base;
406+
int base_len;
293407

294-
if (source[source_len - 1] == '/')
295-
source[source_len - 1] = 0;
408+
/* Target exists and is directory; append basename */
409+
cl_assert(S_ISDIR(dest_st.st_mode));
296410

297-
argv[0] = "/bin/cp";
298-
argv[1] = "-R";
299-
argv[2] = source;
300-
argv[3] = (char *)dest;
301-
argv[4] = NULL;
411+
basename_r(&base, &base_len, source);
412+
cl_assert(base_len < INT_MAX);
302413

303-
cl_must_pass_(
304-
shell_out(argv),
305-
"Failed to copy test fixtures to sandbox"
306-
);
414+
dbuf = joinpath(_dest, base, base_len);
415+
dest = dbuf;
416+
} else if (errno != ENOENT) {
417+
cl_fail("Cannot copy; cannot stat destination");
418+
} else {
419+
dest = _dest;
420+
}
307421

308-
free(source);
422+
if (S_ISDIR(source_st.st_mode)) {
423+
fs_copydir_helper(source, dest, source_st.st_mode);
424+
} else {
425+
fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode);
426+
}
427+
428+
free(dbuf);
309429
}
310430

311431
static void
312-
fs_rm(const char *source)
432+
fs_rmdir_helper(const char *path)
313433
{
314-
char *argv[4];
434+
DIR *dir;
435+
struct dirent *d;
436+
437+
cl_assert_(dir = opendir(path), "Could not open dir");
438+
while ((d = (errno = 0, readdir(dir))) != NULL) {
439+
char *child;
440+
441+
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
442+
continue;
315443

316-
argv[0] = "/bin/rm";
317-
argv[1] = "-Rf";
318-
argv[2] = (char *)source;
319-
argv[3] = NULL;
444+
child = joinpath(path, d->d_name, -1);
445+
fs_rm(child);
446+
free(child);
447+
}
448+
449+
cl_assert_(errno == 0, "Failed to iterate source dir");
450+
closedir(dir);
451+
452+
cl_must_pass_(rmdir(path), "Could not remove directory");
453+
}
320454

321-
cl_must_pass_(
322-
shell_out(argv),
323-
"Failed to cleanup the sandbox"
324-
);
455+
static void
456+
fs_rm(const char *path)
457+
{
458+
struct stat st;
459+
460+
if (lstat(path, &st)) {
461+
if (errno == ENOENT)
462+
return;
463+
464+
cl_fail("Cannot copy; cannot stat destination");
465+
}
466+
467+
if (S_ISDIR(st.st_mode)) {
468+
fs_rmdir_helper(path);
469+
} else {
470+
cl_must_pass(unlink(path));
471+
}
325472
}
326473

327474
void

0 commit comments

Comments
 (0)