| #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(); |
| } |