coreutils / tests /ln /tests_for_atomic_link.c
AryaWu's picture
Upload folder using huggingface_hub
78d2150 verified
#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>
/* The tests are included into the same translation unit as ln.c,
so we can access atomic_link and the static flags:
- symbolic_link
- relative
- beware_hard_dir_link
- logical
*/
/* ---------- Test helpers ---------- */
static char *test_dir = NULL;
static int test_dfd = -1;
/* Join directory and base into a newly-allocated path string. */
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;
}
/* Create a temporary directory for each test. */
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; /* buf now contains the directory path */
}
/* Recursively remove a path. */
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);
}
}
/* Create a file with optional content. */
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);
}
/* Assert that a path does not exist. */
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);
}
/* ---------- Unity hooks ---------- */
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");
/* Ensure flags are in a known state before each test. */
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;
}
}
/* ---------- Tests ---------- */
/* Hard link success: should return 0 and create a hard link with same inode/dev. */
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);
}
/* Hard link failure when destination exists: expect positive errno EEXIST and no change. */
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);
}
/* Hard link when beware_hard_dir_link is true: should return -1 and not create destination. */
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; /* triggers -1 path */
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);
}
/* Symbolic link with relative=true: should return -1 and not create destination. */
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);
/* Source need not exist for symlinkat, but create it anyway. */
TEST_ASSERT_EQUAL_INT(0, create_file(src, "hello"));
symbolic_link = true;
relative = true; /* triggers -1 for atomic_link */
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);
}
/* Symbolic link success: should return 0 and create a symlink pointing to the given source string. */
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);
}
/* Symbolic link failure when destination exists: expect EEXIST and no change. */
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"));
/* Precreate destination as a regular file. */
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);
}
/* ---------- Unity main ---------- */
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();
}