coreutils / tests /ln /tests_for_do_link.c
AryaWu's picture
Upload folder using huggingface_hub
78d2150 verified
#include "../../unity/unity.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdbool.h>
#include <dirent.h>
/* The following globals and function are defined in the ln source prior to this include:
- static bool do_link(...)
- static enum backup_type backup_type;
- static bool symbolic_link, relative, logical, interactive, remove_existing_files,
verbose, hard_dir_link, beware_hard_dir_link, dereference_dest_dir_symlinks;
- static Hash_table *dest_set;
They are accessible here since this file is included into the same translation unit.
*/
static char *path_join2(const char *a, const char *b) {
size_t la = strlen(a);
size_t lb = strlen(b);
int need_slash = (la > 0 && a[la-1] != '/');
size_t len = la + (need_slash ? 1 : 0) + lb + 1;
char *out = (char*)malloc(len);
if (!out) return NULL;
memcpy(out, a, la);
if (need_slash) out[la++] = '/';
memcpy(out + la, b, lb);
out[la + lb] = '\0';
return out;
}
static char *mkdtemp_wrap(void) {
char tmpl[PATH_MAX];
const char *tmpdir = getenv("TMPDIR");
if (!tmpdir || !*tmpdir) tmpdir = "/tmp";
snprintf(tmpl, sizeof(tmpl), "%s/ln_do_link_test_%ld_XXXXXX", tmpdir, (long)getpid());
char *buf = strdup(tmpl);
if (!buf) return NULL;
if (!mkdtemp(buf)) {
free(buf);
return NULL;
}
return buf;
}
static int write_file(const char *path, const char *data) {
int fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd < 0) return -1;
size_t len = strlen(data);
ssize_t wr = write(fd, data, len);
int e = 0;
if (wr < 0 || (size_t)wr != len) e = -1;
if (close(fd) != 0) e = -1;
return e;
}
static int open_dirfd(const char *dir) {
int fd = open(dir, O_RDONLY
#ifdef O_DIRECTORY
| O_DIRECTORY
#endif
#ifdef O_CLOEXEC
| O_CLOEXEC
#endif
);
return fd;
}
static char *realpath_dup(const char *p) {
char buf[PATH_MAX];
if (!realpath(p, buf)) return NULL;
return strdup(buf);
}
/* Resolve the symlink text as it would be interpreted from dest_dir. */
static char *resolve_link_from_dir(const char *dest_dir, const char *link_text) {
char *abs = NULL;
if (link_text[0] == '/') {
abs = realpath_dup(link_text);
} else {
char *joined = path_join2(dest_dir, link_text);
if (!joined) return NULL;
abs = realpath_dup(joined);
free(joined);
}
return abs;
}
/* Reset all relevant ln flags and state to a known baseline. */
static void reset_ln_flags(void) {
symbolic_link = false;
relative = false;
logical = !!LINK_FOLLOWS_SYMLINKS; /* use default as baseline; individual tests can set explicitly */
interactive = false;
remove_existing_files = false;
verbose = false;
hard_dir_link = false;
beware_hard_dir_link = false;
dereference_dest_dir_symlinks = true;
backup_type = no_backups;
dest_set = NULL;
}
void setUp(void) {
/* Nothing global to set here; tests call reset_ln_flags explicitly. */
}
void tearDown(void) {
/* Each test cleans up its own files/directories. */
}
/* Test: simple hard link creation when destination does not exist. */
void test_do_link_hardlink_simple(void) {
reset_ln_flags();
logical = 0; /* physical by default */
char *td = mkdtemp_wrap();
TEST_ASSERT_NOT_NULL(td);
char *src = path_join2(td, "src.txt");
TEST_ASSERT_NOT_NULL(src);
TEST_ASSERT_EQUAL_INT(0, write_file(src, "hello"));
char *dest_dir = td; /* place link in same directory */
char *dest_full = path_join2(dest_dir, "dest.hard");
TEST_ASSERT_NOT_NULL(dest_full);
char *slash = strrchr(dest_full, '/');
TEST_ASSERT_NOT_NULL(slash);
char *dest_base = slash + 1;
int dfd = open_dirfd(dest_dir);
TEST_ASSERT_TRUE(dfd >= 0);
bool ok = do_link(src, dfd, dest_base, dest_full, -1);
TEST_ASSERT_TRUE(ok);
struct stat ss, ds;
TEST_ASSERT_EQUAL_INT(0, stat(src, &ss));
TEST_ASSERT_EQUAL_INT(0, stat(dest_full, &ds));
TEST_ASSERT_EQUAL_UINT64(ss.st_ino, ds.st_ino);
TEST_ASSERT_EQUAL_INT(ss.st_dev, ds.st_dev);
close(dfd);
unlink(dest_full);
unlink(src);
rmdir(td);
free(dest_full);
free(src);
free(td);
}
/* Test: simple symlink creation with absolute target (non-relative). */
void test_do_link_symlink_absolute(void) {
reset_ln_flags();
symbolic_link = true;
relative = false;
char *td = mkdtemp_wrap();
TEST_ASSERT_NOT_NULL(td);
char *src = path_join2(td, "src.txt");
TEST_ASSERT_NOT_NULL(src);
TEST_ASSERT_EQUAL_INT(0, write_file(src, "world"));
char *dest_full = path_join2(td, "dest.sym");
TEST_ASSERT_NOT_NULL(dest_full);
char *slash = strrchr(dest_full, '/');
TEST_ASSERT_NOT_NULL(slash);
char *dest_base = slash + 1;
int dfd = open_dirfd(td);
TEST_ASSERT_TRUE(dfd >= 0);
bool ok = do_link(src, dfd, dest_base, dest_full, -1);
TEST_ASSERT_TRUE(ok);
char buf[PATH_MAX];
ssize_t n = readlink(dest_full, buf, sizeof(buf)-1);
TEST_ASSERT_TRUE(n >= 0);
buf[n] = '\0';
TEST_ASSERT_EQUAL_STRING(src, buf);
close(dfd);
unlink(dest_full);
unlink(src);
rmdir(td);
free(dest_full);
free(src);
free(td);
}
/* Test: relative symlink creation (-r) and that it resolves to the correct absolute path. */
void test_do_link_symlink_relative_resolves(void) {
reset_ln_flags();
symbolic_link = true;
relative = true;
char *td = mkdtemp_wrap();
TEST_ASSERT_NOT_NULL(td);
/* Create source in td/a/srcdir/s.txt and dest in td/b/destdir/l.lnk */
char *a = path_join2(td, "a");
char *b = path_join2(td, "b");
char *srcdir = path_join2(a, "srcdir");
char *destdir = path_join2(b, "destdir");
TEST_ASSERT_EQUAL_INT(0, mkdir(a, 0700));
TEST_ASSERT_EQUAL_INT(0, mkdir(b, 0700));
TEST_ASSERT_EQUAL_INT(0, mkdir(srcdir, 0700));
TEST_ASSERT_EQUAL_INT(0, mkdir(destdir, 0700));
char *src = path_join2(srcdir, "s.txt");
TEST_ASSERT_NOT_NULL(src);
TEST_ASSERT_EQUAL_INT(0, write_file(src, "data"));
char *dest_full = path_join2(destdir, "l.lnk");
TEST_ASSERT_NOT_NULL(dest_full);
char *slash = strrchr(dest_full, '/'); TEST_ASSERT_NOT_NULL(slash);
char *dest_base = slash + 1;
int dfd = open_dirfd(destdir);
TEST_ASSERT_TRUE(dfd >= 0);
bool ok = do_link(src, dfd, dest_base, dest_full, -1);
TEST_ASSERT_TRUE(ok);
/* Read link text and ensure it resolves to the same abs path as src */
char linkbuf[PATH_MAX];
ssize_t n = readlink(dest_full, linkbuf, sizeof(linkbuf)-1);
TEST_ASSERT_TRUE(n >= 0);
linkbuf[n] = '\0';
char *src_abs = realpath_dup(src);
TEST_ASSERT_NOT_NULL(src_abs);
char *resolved = resolve_link_from_dir(destdir, linkbuf);
TEST_ASSERT_NOT_NULL(resolved);
TEST_ASSERT_EQUAL_STRING(src_abs, resolved);
close(dfd);
/* Cleanup */
unlink(dest_full);
unlink(src);
rmdir(destdir);
rmdir(srcdir);
rmdir(b);
rmdir(a);
rmdir(td);
free(resolved);
free(src_abs);
free(dest_full);
free(src);
free(destdir);
free(srcdir);
free(b);
free(a);
free(td);
}
/* Test: force overwrite existing destination (-f). */
void test_do_link_force_overwrite(void) {
reset_ln_flags();
symbolic_link = false;
remove_existing_files = true;
logical = 0;
char *td = mkdtemp_wrap();
TEST_ASSERT_NOT_NULL(td);
char *src = path_join2(td, "src.txt");
char *dest_full = path_join2(td, "dest.txt");
TEST_ASSERT_NOT_NULL(src && dest_full);
TEST_ASSERT_EQUAL_INT(0, write_file(src, "new"));
TEST_ASSERT_EQUAL_INT(0, write_file(dest_full, "old"));
char *slash = strrchr(dest_full, '/'); TEST_ASSERT_NOT_NULL(slash);
char *dest_base = slash + 1;
int dfd = open_dirfd(td);
TEST_ASSERT_TRUE(dfd >= 0);
bool ok = do_link(src, dfd, dest_base, dest_full, -1);
TEST_ASSERT_TRUE(ok);
struct stat ss, ds;
TEST_ASSERT_EQUAL_INT(0, stat(src, &ss));
TEST_ASSERT_EQUAL_INT(0, stat(dest_full, &ds));
TEST_ASSERT_EQUAL_UINT64(ss.st_ino, ds.st_ino);
TEST_ASSERT_EQUAL_INT(ss.st_dev, ds.st_dev);
close(dfd);
unlink(dest_full);
unlink(src);
rmdir(td);
free(dest_full);
free(src);
free(td);
}
/* Test: hard link to a directory is disallowed unless hard_dir_link is true. */
void test_do_link_hardlink_directory_disallowed(void) {
reset_ln_flags();
symbolic_link = false;
hard_dir_link = false;
char *td = mkdtemp_wrap();
TEST_ASSERT_NOT_NULL(td);
char *srcdir = path_join2(td, "adir");
char *dest_full = path_join2(td, "dest_dir_hard");
TEST_ASSERT_NOT_NULL(srcdir && dest_full);
TEST_ASSERT_EQUAL_INT(0, mkdir(srcdir, 0700));
char *slash = strrchr(dest_full, '/'); TEST_ASSERT_NOT_NULL(slash);
char *dest_base = slash + 1;
int dfd = open_dirfd(td);
TEST_ASSERT_TRUE(dfd >= 0);
bool ok = do_link(srcdir, dfd, dest_base, dest_full, -1);
TEST_ASSERT_FALSE(ok);
/* Ensure destination was not created (should not exist) */
struct stat st;
TEST_ASSERT_EQUAL_INT(-1, lstat(dest_full, &st));
TEST_ASSERT_EQUAL_INT(ENOENT, errno);
close(dfd);
rmdir(srcdir);
rmdir(td);
free(dest_full);
free(srcdir);
free(td);
}
/* Test: prevent self-overwrite when source and dest are the same entry with -f. */
void test_do_link_same_file_protection(void) {
reset_ln_flags();
symbolic_link = false;
remove_existing_files = true; /* required to trigger the self-same check */
char *td = mkdtemp_wrap();
TEST_ASSERT_NOT_NULL(td);
char *path = path_join2(td, "same.txt");
TEST_ASSERT_NOT_NULL(path);
TEST_ASSERT_EQUAL_INT(0, write_file(path, "x"));
/* dest is the same as source */
char *dest_full = strdup(path);
TEST_ASSERT_NOT_NULL(dest_full);
char *slash = strrchr(dest_full, '/'); TEST_ASSERT_NOT_NULL(slash);
char *dest_base = slash + 1;
int dfd = open_dirfd(td);
TEST_ASSERT_TRUE(dfd >= 0);
bool ok = do_link(path, dfd, dest_base, dest_full, -1);
TEST_ASSERT_FALSE(ok);
close(dfd);
unlink(path);
rmdir(td);
free(dest_full);
free(path);
free(td);
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_do_link_hardlink_simple);
RUN_TEST(test_do_link_symlink_absolute);
RUN_TEST(test_do_link_symlink_relative_resolves);
RUN_TEST(test_do_link_force_overwrite);
RUN_TEST(test_do_link_hardlink_directory_disallowed);
RUN_TEST(test_do_link_same_file_protection);
return UNITY_END();
}