|
|
#include "../../unity/unity.h" |
|
|
#include <errno.h> |
|
|
#include <fcntl.h> |
|
|
#include <limits.h> |
|
|
#include <stdarg.h> |
|
|
#include <stdbool.h> |
|
|
#include <stddef.h> |
|
|
#include <stdio.h> |
|
|
#include <stdlib.h> |
|
|
#include <string.h> |
|
|
#include <unistd.h> |
|
|
#include <dirent.h> |
|
|
#include <sys/stat.h> |
|
|
#include <sys/types.h> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static char *test_dir = NULL; |
|
|
static int test_dfd = -1; |
|
|
|
|
|
|
|
|
static char *test_join(const char *dir, const char *base) |
|
|
{ |
|
|
size_t len_dir = strlen(dir); |
|
|
size_t len_base = strlen(base); |
|
|
int need_slash = (len_dir > 0 && dir[len_dir - 1] != '/'); |
|
|
size_t total = len_dir + need_slash + len_base + 1; |
|
|
char *res = (char *)malloc(total); |
|
|
if (!res) return NULL; |
|
|
memcpy(res, dir, len_dir); |
|
|
if (need_slash) res[len_dir++] = '/'; |
|
|
memcpy(res + len_dir, base, len_base); |
|
|
res[len_dir + len_base] = '\0'; |
|
|
return res; |
|
|
} |
|
|
|
|
|
|
|
|
static char *make_temp_dir(void) |
|
|
{ |
|
|
char tmpl[] = "ln_atomic_link_test.XXXXXX"; |
|
|
char *buf = (char *)malloc(sizeof(tmpl)); |
|
|
if (!buf) return NULL; |
|
|
memcpy(buf, tmpl, sizeof(tmpl)); |
|
|
char *d = mkdtemp(buf); |
|
|
if (!d) |
|
|
{ |
|
|
free(buf); |
|
|
return NULL; |
|
|
} |
|
|
return buf; |
|
|
} |
|
|
|
|
|
|
|
|
static int rm_rf(const char *path) |
|
|
{ |
|
|
struct stat st; |
|
|
if (lstat(path, &st) != 0) |
|
|
return (errno == ENOENT) ? 0 : -1; |
|
|
|
|
|
if (S_ISDIR(st.st_mode)) |
|
|
{ |
|
|
DIR *dir = opendir(path); |
|
|
if (!dir) return -1; |
|
|
struct dirent *de; |
|
|
while ((de = readdir(dir)) != NULL) |
|
|
{ |
|
|
if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) |
|
|
continue; |
|
|
char *child = test_join(path, de->d_name); |
|
|
if (!child) |
|
|
{ |
|
|
closedir(dir); |
|
|
return -1; |
|
|
} |
|
|
if (rm_rf(child) != 0) |
|
|
{ |
|
|
free(child); |
|
|
closedir(dir); |
|
|
return -1; |
|
|
} |
|
|
free(child); |
|
|
} |
|
|
closedir(dir); |
|
|
return rmdir(path); |
|
|
} |
|
|
else |
|
|
{ |
|
|
return unlink(path); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
static int create_file(const char *path, const char *content) |
|
|
{ |
|
|
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600); |
|
|
if (fd < 0) return -1; |
|
|
if (content) |
|
|
{ |
|
|
size_t len = strlen(content); |
|
|
ssize_t wr = write(fd, content, len); |
|
|
if (wr < 0 || (size_t)wr != len) |
|
|
{ |
|
|
int e = errno; |
|
|
close(fd); |
|
|
errno = e; |
|
|
return -1; |
|
|
} |
|
|
} |
|
|
return close(fd); |
|
|
} |
|
|
|
|
|
|
|
|
static void assert_path_not_exists(const char *path) |
|
|
{ |
|
|
struct stat st; |
|
|
int r = lstat(path, &st); |
|
|
TEST_ASSERT_TRUE_MESSAGE(r != 0, "lstat unexpectedly succeeded"); |
|
|
TEST_ASSERT_EQUAL_INT(ENOENT, errno); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void setUp(void) |
|
|
{ |
|
|
test_dir = make_temp_dir(); |
|
|
TEST_ASSERT_NOT_NULL_MESSAGE(test_dir, "Failed to create temp directory"); |
|
|
|
|
|
test_dfd = open(test_dir, O_RDONLY | O_DIRECTORY); |
|
|
TEST_ASSERT_TRUE_MESSAGE(test_dfd >= 0, "Failed to open temp directory"); |
|
|
|
|
|
|
|
|
symbolic_link = false; |
|
|
relative = false; |
|
|
beware_hard_dir_link = false; |
|
|
logical = false; |
|
|
} |
|
|
|
|
|
void tearDown(void) |
|
|
{ |
|
|
if (test_dfd >= 0) |
|
|
close(test_dfd); |
|
|
test_dfd = -1; |
|
|
|
|
|
if (test_dir) |
|
|
{ |
|
|
rm_rf(test_dir); |
|
|
free(test_dir); |
|
|
test_dir = NULL; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void test_atomic_link_hard_success(void) |
|
|
{ |
|
|
char *src = test_join(test_dir, "src1"); |
|
|
char *dst_full = test_join(test_dir, "dst1"); |
|
|
TEST_ASSERT_NOT_NULL(src); |
|
|
TEST_ASSERT_NOT_NULL(dst_full); |
|
|
|
|
|
TEST_ASSERT_EQUAL_INT(0, create_file(src, "hello")); |
|
|
|
|
|
symbolic_link = false; |
|
|
beware_hard_dir_link = false; |
|
|
logical = false; |
|
|
|
|
|
int ret = atomic_link(src, test_dfd, "dst1"); |
|
|
TEST_ASSERT_EQUAL_INT(0, ret); |
|
|
|
|
|
struct stat s1, s2; |
|
|
TEST_ASSERT_EQUAL_INT(0, stat(src, &s1)); |
|
|
TEST_ASSERT_EQUAL_INT(0, stat(dst_full, &s2)); |
|
|
|
|
|
TEST_ASSERT_EQUAL_INT(s1.st_dev, s2.st_dev); |
|
|
TEST_ASSERT_EQUAL_INT(s1.st_ino, s2.st_ino); |
|
|
|
|
|
free(src); |
|
|
free(dst_full); |
|
|
} |
|
|
|
|
|
|
|
|
static void test_atomic_link_hard_eexist(void) |
|
|
{ |
|
|
char *src = test_join(test_dir, "src2"); |
|
|
char *dst_full = test_join(test_dir, "dst2"); |
|
|
TEST_ASSERT_NOT_NULL(src); |
|
|
TEST_ASSERT_NOT_NULL(dst_full); |
|
|
|
|
|
TEST_ASSERT_EQUAL_INT(0, create_file(src, "hello")); |
|
|
TEST_ASSERT_EQUAL_INT(0, create_file(dst_full, "preexisting")); |
|
|
|
|
|
struct stat before; |
|
|
TEST_ASSERT_EQUAL_INT(0, lstat(dst_full, &before)); |
|
|
|
|
|
symbolic_link = false; |
|
|
beware_hard_dir_link = false; |
|
|
logical = false; |
|
|
|
|
|
int ret = atomic_link(src, test_dfd, "dst2"); |
|
|
TEST_ASSERT_EQUAL_INT(EEXIST, ret); |
|
|
|
|
|
struct stat after; |
|
|
TEST_ASSERT_EQUAL_INT(0, lstat(dst_full, &after)); |
|
|
TEST_ASSERT_EQUAL_INT(before.st_dev, after.st_dev); |
|
|
TEST_ASSERT_EQUAL_INT(before.st_ino, after.st_ino); |
|
|
|
|
|
free(src); |
|
|
free(dst_full); |
|
|
} |
|
|
|
|
|
|
|
|
static void test_atomic_link_beware_returns_minus1(void) |
|
|
{ |
|
|
char *src = test_join(test_dir, "src3"); |
|
|
char *dst_full = test_join(test_dir, "dst3"); |
|
|
TEST_ASSERT_NOT_NULL(src); |
|
|
TEST_ASSERT_NOT_NULL(dst_full); |
|
|
|
|
|
TEST_ASSERT_EQUAL_INT(0, create_file(src, "hello")); |
|
|
|
|
|
symbolic_link = false; |
|
|
beware_hard_dir_link = true; |
|
|
logical = false; |
|
|
|
|
|
int ret = atomic_link(src, test_dfd, "dst3"); |
|
|
TEST_ASSERT_EQUAL_INT(-1, ret); |
|
|
|
|
|
assert_path_not_exists(dst_full); |
|
|
|
|
|
free(src); |
|
|
free(dst_full); |
|
|
} |
|
|
|
|
|
|
|
|
static void test_atomic_link_symbolic_relative_returns_minus1(void) |
|
|
{ |
|
|
char *src = test_join(test_dir, "src4"); |
|
|
char *dst_full = test_join(test_dir, "symrel"); |
|
|
TEST_ASSERT_NOT_NULL(src); |
|
|
TEST_ASSERT_NOT_NULL(dst_full); |
|
|
|
|
|
|
|
|
TEST_ASSERT_EQUAL_INT(0, create_file(src, "hello")); |
|
|
|
|
|
symbolic_link = true; |
|
|
relative = true; |
|
|
|
|
|
int ret = atomic_link(src, test_dfd, "symrel"); |
|
|
TEST_ASSERT_EQUAL_INT(-1, ret); |
|
|
|
|
|
assert_path_not_exists(dst_full); |
|
|
|
|
|
free(src); |
|
|
free(dst_full); |
|
|
} |
|
|
|
|
|
|
|
|
static void test_atomic_link_symbolic_success(void) |
|
|
{ |
|
|
char *src = test_join(test_dir, "src5"); |
|
|
char *dst_full = test_join(test_dir, "sym1"); |
|
|
TEST_ASSERT_NOT_NULL(src); |
|
|
TEST_ASSERT_NOT_NULL(dst_full); |
|
|
|
|
|
TEST_ASSERT_EQUAL_INT(0, create_file(src, "world")); |
|
|
|
|
|
symbolic_link = true; |
|
|
relative = false; |
|
|
|
|
|
int ret = atomic_link(src, test_dfd, "sym1"); |
|
|
TEST_ASSERT_EQUAL_INT(0, ret); |
|
|
|
|
|
struct stat lst; |
|
|
TEST_ASSERT_EQUAL_INT(0, lstat(dst_full, &lst)); |
|
|
TEST_ASSERT_TRUE_MESSAGE(S_ISLNK(lst.st_mode), "Destination is not a symlink"); |
|
|
|
|
|
char buf[PATH_MAX]; |
|
|
ssize_t n = readlink(dst_full, buf, sizeof(buf) - 1); |
|
|
TEST_ASSERT_TRUE_MESSAGE(n >= 0, "readlink failed"); |
|
|
buf[n] = '\0'; |
|
|
TEST_ASSERT_EQUAL_STRING(src, buf); |
|
|
|
|
|
free(src); |
|
|
free(dst_full); |
|
|
} |
|
|
|
|
|
|
|
|
static void test_atomic_link_symbolic_eexist(void) |
|
|
{ |
|
|
char *src = test_join(test_dir, "src6"); |
|
|
char *dst_full = test_join(test_dir, "sym2"); |
|
|
TEST_ASSERT_NOT_NULL(src); |
|
|
TEST_ASSERT_NOT_NULL(dst_full); |
|
|
|
|
|
TEST_ASSERT_EQUAL_INT(0, create_file(src, "data")); |
|
|
|
|
|
TEST_ASSERT_EQUAL_INT(0, create_file(dst_full, "preexisting")); |
|
|
|
|
|
struct stat before; |
|
|
TEST_ASSERT_EQUAL_INT(0, lstat(dst_full, &before)); |
|
|
|
|
|
symbolic_link = true; |
|
|
relative = false; |
|
|
|
|
|
int ret = atomic_link(src, test_dfd, "sym2"); |
|
|
TEST_ASSERT_EQUAL_INT(EEXIST, ret); |
|
|
|
|
|
struct stat after; |
|
|
TEST_ASSERT_EQUAL_INT(0, lstat(dst_full, &after)); |
|
|
TEST_ASSERT_EQUAL_INT(before.st_dev, after.st_dev); |
|
|
TEST_ASSERT_EQUAL_INT(before.st_ino, after.st_ino); |
|
|
|
|
|
free(src); |
|
|
free(dst_full); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int main(void) |
|
|
{ |
|
|
UNITY_BEGIN(); |
|
|
|
|
|
RUN_TEST(test_atomic_link_hard_success); |
|
|
RUN_TEST(test_atomic_link_hard_eexist); |
|
|
RUN_TEST(test_atomic_link_beware_returns_minus1); |
|
|
RUN_TEST(test_atomic_link_symbolic_relative_returns_minus1); |
|
|
RUN_TEST(test_atomic_link_symbolic_success); |
|
|
RUN_TEST(test_atomic_link_symbolic_eexist); |
|
|
|
|
|
return UNITY_END(); |
|
|
} |