|
|
#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> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
static void reset_ln_flags(void) { |
|
|
symbolic_link = false; |
|
|
relative = false; |
|
|
logical = !!LINK_FOLLOWS_SYMLINKS; |
|
|
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) { |
|
|
|
|
|
} |
|
|
void tearDown(void) { |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
void test_do_link_hardlink_simple(void) { |
|
|
reset_ln_flags(); |
|
|
logical = 0; |
|
|
|
|
|
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; |
|
|
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); |
|
|
} |
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
|
|
|
|
|
|
void test_do_link_same_file_protection(void) { |
|
|
reset_ln_flags(); |
|
|
symbolic_link = false; |
|
|
remove_existing_files = true; |
|
|
|
|
|
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")); |
|
|
|
|
|
|
|
|
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(); |
|
|
} |