#include <err.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
#include <fts.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <struct.h>
#include <darwintest.h>
#include <darwintest_utils.h>
static void
_create_random_file(int root_fd, char *path)
{
int fd = openat(root_fd, path, O_WRONLY | O_CREAT);
T_ASSERT_POSIX_SUCCESS(fd, NULL);
T_ASSERT_POSIX_SUCCESS(dprintf(fd, "Random File at: %s", path), NULL);
T_ASSERT_POSIX_SUCCESS(close(fd), NULL);
}
static void
_create_symlink(char *root, char *destination, char *source)
{
char *absolute_destination = NULL;
T_ASSERT_POSIX_SUCCESS(asprintf(&absolute_destination, "%s/%s", root, destination), NULL);
char *absolute_source = NULL;
T_ASSERT_POSIX_SUCCESS(asprintf(&absolute_source, "%s/%s", root, source), NULL);
T_ASSERT_POSIX_SUCCESS(symlink(absolute_destination, absolute_source), NULL);
free(absolute_destination);
free(absolute_source);
}
static char *
_remove_prefix(char *prefix, char *input) {
char *start = strstr(input, prefix);
T_QUIET;
T_ASSERT_NOTNULL(start, "prefix: %s input: %s", prefix, input);
char *end = start + strlen(prefix) + 1;
return end;
}
T_DECL(fts_simple, "Simple fts_read test")
{
T_LOG("prog: %s", getprogname());
char *tmp_path = NULL;
T_ASSERT_POSIX_SUCCESS(asprintf(&tmp_path, "%s/%s-XXXXXX", dt_tmpdir(), T_NAME), NULL);
T_ASSERT_NOTNULL(mktemp(tmp_path), NULL);
T_ASSERT_POSIX_SUCCESS(mkdir(tmp_path, 0777), NULL);
int tmp_fd = open(tmp_path, O_RDONLY | O_DIRECTORY);
T_LOG("tmp: %s", tmp_path);
T_ASSERT_POSIX_SUCCESS(tmp_fd, NULL);
T_ASSERT_POSIX_SUCCESS(mkdirat(tmp_fd, "A", 0777), NULL);
T_ASSERT_POSIX_SUCCESS(mkdirat(tmp_fd, "A/B", 0777), NULL);
T_ASSERT_POSIX_SUCCESS(mkdirat(tmp_fd, "A/C", 0777), NULL);
T_ASSERT_POSIX_SUCCESS(mkdirat(tmp_fd, "A/B/D", 0777), NULL);
T_ASSERT_POSIX_SUCCESS(mkdirat(tmp_fd, "A/C/empty", 0777), NULL);
_create_random_file(tmp_fd, "root");
_create_random_file(tmp_fd, "A/fileA1");
_create_random_file(tmp_fd, "A/fileA2");
_create_random_file(tmp_fd, "A/B/fileB1");
_create_random_file(tmp_fd, "A/C/fileC1");
_create_random_file(tmp_fd, "A/B/D/fileD1");
T_ASSERT_POSIX_SUCCESS(mkdirat(tmp_fd, "LINK", 0777), NULL);
T_ASSERT_POSIX_SUCCESS(mkdirat(tmp_fd, "LINK/Z", 0777), NULL);
_create_random_file(tmp_fd, "LINK/fileL1");
_create_random_file(tmp_fd, "LINK/Z/fileZ1");
_create_symlink(tmp_path, "LINK", "A/link");
char original_cwd[MAXPATHLEN];
T_ASSERT_NOTNULL(getcwd(original_cwd, sizeof(original_cwd)), NULL);
struct {
const char *cwd;
const char *path;
int info;
bool found;
} expected[] = {
{
.cwd = original_cwd,
.path = "A",
.info = FTS_D,
},
{
.cwd = "A",
.path = "A/fileA2",
.info = FTS_F,
},
{
.cwd = "A",
.path = "A/B",
.info = FTS_D,
},
{
.cwd = "A/B",
.path = "A/B/D",
.info = FTS_D,
},
{
.cwd = "A/B/D",
.path = "A/B/D/fileD1",
.info = FTS_F,
},
{
.cwd = "A/B",
.path = "A/B/D",
.info = FTS_DP,
},
{
.cwd = "A/B",
.path = "A/B/fileB1",
.info = FTS_F,
},
{
.cwd = "A",
.path = "A/B",
.info = FTS_DP,
},
{
.cwd = "A",
.path = "A/C",
.info = FTS_D,
},
{
.cwd = "A/C",
.path = "A/C/empty",
.info = FTS_D,
},
{
.cwd = "A/C",
.path = "A/C/empty",
.info = FTS_DP,
},
{
.cwd = "A/C",
.path = "A/C/fileC1",
.info = FTS_F,
},
{
.cwd = "A",
.path = "A/C",
.info = FTS_DP,
},
{
.cwd = "A",
.path = "A/link",
.info = FTS_SL,
},
{
.cwd = "A",
.path = "A/link",
.info = FTS_D,
},
{
.cwd = "LINK",
.path = "A/link/fileL1",
.info = FTS_F,
},
{
.cwd = "LINK",
.path = "A/link/Z",
.info = FTS_D,
},
{
.cwd = "LINK/Z",
.path = "A/link/Z/fileZ1",
.info = FTS_F,
},
{
.cwd = "LINK",
.path = "A/link/Z",
.info = FTS_DP,
},
{
.cwd = "A",
.path = "A/link",
.info = FTS_DP,
},
{
.cwd = "A",
.path = "A/fileA1",
.info = FTS_F,
},
{
.cwd = original_cwd,
.path = "A",
.info = FTS_DP,
},
};
const char *LABELS[] = {
[0] = "None",
[FTS_D] = "FTS_D",
[FTS_DC] = "FTS_DC",
[FTS_DEFAULT] = "FTS_DEFAULT",
[FTS_DNR] = "FTS_DNR",
[FTS_DOT] = "FTS_DOT",
[FTS_DP] = "FTS_DP",
[FTS_ERR] = "FTS_ERR",
[FTS_F] = "FTS_F",
[FTS_INIT] = "FTS_INIT",
[FTS_NS] = "FTS_NS",
[FTS_NSOK] = "FTS_NSOK",
[FTS_SL] = "FTS_SL",
[FTS_SLNONE] = "FTS_SLNONE",
};
char *root_path = NULL;
T_ASSERT_POSIX_SUCCESS(asprintf(&root_path, "%s/A", tmp_path), NULL);
const char *paths[] = {
root_path,
NULL,
};
FTS *tree = fts_open(paths, FTS_PHYSICAL, NULL);
T_ASSERT_NOTNULL(tree, NULL);
FTSENT *node;
int found_count = 0;
while ((node = fts_read(tree))) {
char cwd[MAXPATHLEN];
T_QUIET;
T_ASSERT_NOTNULL(getcwd(cwd, sizeof(cwd)), NULL);
switch (node->fts_info) {
case FTS_ERR:
T_FAIL("FTS_ERR(%d)", node->fts_errno);
break;
case FTS_SL:
fts_set(tree, node, FTS_FOLLOW);
default: {
bool found = false;
for (size_t index = 0; index < countof(expected) && !found; index++) {
if (expected[index].found) {
continue;
}
if (expected[index].info != node->fts_info) {
continue;
}
char *expected_path = expected[index].path;
char *actual_path = node->fts_path;
if (expected_path[0] != '/') {
actual_path = _remove_prefix(tmp_path, actual_path);
}
if (strcmp(actual_path, expected_path) == 0) {
char *expected_cwd = expected[index].cwd;
char *actual_cwd = cwd;
if (expected_cwd[0] != '/') {
actual_cwd = _remove_prefix(tmp_path, actual_cwd);
}
T_QUIET;
T_EXPECT_EQ_STR(actual_cwd, expected_cwd, NULL);
found = true;
expected[index].found = true;
found_count++;
}
}
T_EXPECT_TRUE(found, "path: %s info: %d [%s] cwd: %s", node->fts_path, node->fts_info, LABELS[node->fts_info], cwd);
}
}
}
T_QUIET;
T_EXPECT_EQ(found_count, countof(expected), NULL);
for (size_t index = 0; index < countof(expected); index++) {
T_QUIET;
T_EXPECT_TRUE(expected[index].found, "missing: path: %s info %d [%s]", expected[index].path, expected[index].info, LABELS[expected[index].info]);
}
fts_close(tree);
free(tmp_path);
free(root_path);
}