| | #include "../../unity/unity.h" |
| | #include <stdio.h> |
| | #include <stdlib.h> |
| | #include <string.h> |
| | #include <unistd.h> |
| | #include <sys/stat.h> |
| | #include <fcntl.h> |
| | #include <errno.h> |
| | #include <limits.h> |
| | #include <stdbool.h> |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | static mode_t saved_umask; |
| |
|
| | |
| | static char* make_temp_dir(void) |
| | { |
| | char tmpl[] = "/tmp/chmod_ut_XXXXXX"; |
| | char *path = malloc(sizeof(tmpl)); |
| | if (!path) return NULL; |
| | memcpy(path, tmpl, sizeof(tmpl)); |
| | if (!mkdtemp(path)) |
| | { |
| | free(path); |
| | return NULL; |
| | } |
| | return path; |
| | } |
| |
|
| | |
| | static char* path_join(const char* dir, const char* name) |
| | { |
| | size_t ld = strlen(dir), ln = strlen(name); |
| | size_t need = ld + 1 + ln + 1; |
| | char *res = malloc(need); |
| | if (!res) return NULL; |
| | memcpy(res, dir, ld); |
| | res[ld] = '/'; |
| | memcpy(res + ld + 1, name, ln); |
| | res[ld + 1 + ln] = '\0'; |
| | return res; |
| | } |
| |
|
| | |
| | static int create_file_with_mode(const char* path, mode_t mode) |
| | { |
| | int fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, mode); |
| | if (fd < 0) return -1; |
| | if (close(fd) != 0) return -1; |
| | |
| | if (chmod(path, mode) != 0) return -1; |
| | return 0; |
| | } |
| |
|
| | |
| | static int create_symlink_to(const char* target, const char* linkpath) |
| | { |
| | |
| | unlink(linkpath); |
| | return symlink(target, linkpath); |
| | } |
| |
|
| | |
| | static mode_t get_mode_stat(const char* path) |
| | { |
| | struct stat st; |
| | if (stat(path, &st) != 0) return (mode_t)~0; |
| | return st.st_mode; |
| | } |
| |
|
| | |
| | static mode_t get_mode_lstat(const char* path) |
| | { |
| | struct stat st; |
| | if (lstat(path, &st) != 0) return (mode_t)~0; |
| | return st.st_mode; |
| | } |
| |
|
| | |
| | static FTSENT* open_single_fts(const char* path, FTS **out_fts, int flags) |
| | { |
| | char *paths[2]; |
| | paths[0] = (char*)path; |
| | paths[1] = NULL; |
| | FTS *fts = xfts_open(paths, flags, NULL); |
| | if (!fts) |
| | { |
| | *out_fts = NULL; |
| | return NULL; |
| | } |
| | FTSENT *ent = fts_read(fts); |
| | *out_fts = fts; |
| | return ent; |
| | } |
| |
|
| | void setUp(void) { |
| | |
| | setenv("LC_ALL", "C", 1); |
| | setenv("LANG", "C", 1); |
| | |
| | saved_umask = umask(0); |
| | umask_value = 0; |
| |
|
| | |
| | recurse = false; |
| | dereference = -1; |
| | force_silent = false; |
| | diagnose_surprises = false; |
| | verbosity = V_off; |
| | } |
| |
|
| | void tearDown(void) { |
| | |
| | umask(saved_umask); |
| | } |
| |
|
| | |
| | void test_process_file_regular_add_exec(void) |
| | { |
| | char *dir = make_temp_dir(); |
| | TEST_ASSERT_NOT_NULL(dir); |
| |
|
| | char *file = path_join(dir, "f1"); |
| | TEST_ASSERT_NOT_NULL(file); |
| |
|
| | TEST_ASSERT_EQUAL_INT(0, create_file_with_mode(file, 0644)); |
| |
|
| | |
| | change = mode_compile("u+x", 0); |
| |
|
| | FTS *fts = NULL; |
| | FTSENT *ent = open_single_fts(file, &fts, FTS_PHYSICAL); |
| | TEST_ASSERT_NOT_NULL_MESSAGE(ent, "fts_read should return an entry for the file"); |
| | TEST_ASSERT_NOT_NULL(fts); |
| |
|
| | bool ok = process_file(fts, ent); |
| | |
| | TEST_ASSERT_EQUAL_INT(0, fts_close(fts)); |
| | TEST_ASSERT_TRUE(ok); |
| |
|
| | mode_t m = get_mode_stat(file); |
| | TEST_ASSERT_NOT_EQUAL((mode_t)~0, m); |
| | TEST_ASSERT_TRUE(m & S_IXUSR); |
| |
|
| | unlink(file); |
| | rmdir(dir); |
| | free(file); |
| | free(dir); |
| | } |
| |
|
| | |
| | void test_process_file_symlink_no_deref_noop(void) |
| | { |
| | char *dir = make_temp_dir(); |
| | TEST_ASSERT_NOT_NULL(dir); |
| |
|
| | char *tgt = path_join(dir, "target"); |
| | char *lnk = path_join(dir, "link"); |
| | TEST_ASSERT_NOT_NULL(tgt); |
| | TEST_ASSERT_NOT_NULL(lnk); |
| |
|
| | TEST_ASSERT_EQUAL_INT(0, create_file_with_mode(tgt, 0644)); |
| | TEST_ASSERT_EQUAL_INT(0, create_symlink_to(tgt, lnk)); |
| |
|
| | mode_t before = get_mode_stat(tgt); |
| | TEST_ASSERT_NOT_EQUAL((mode_t)~0, before); |
| |
|
| | |
| | change = mode_compile("a+x", 0); |
| | dereference = 0; |
| |
|
| | FTS *fts = NULL; |
| | FTSENT *ent = open_single_fts(lnk, &fts, FTS_PHYSICAL); |
| | TEST_ASSERT_NOT_NULL(ent); |
| | TEST_ASSERT_NOT_NULL(fts); |
| |
|
| | bool ok = process_file(fts, ent); |
| | TEST_ASSERT_EQUAL_INT(0, fts_close(fts)); |
| | TEST_ASSERT_TRUE(ok); |
| |
|
| | mode_t after = get_mode_stat(tgt); |
| | TEST_ASSERT_NOT_EQUAL((mode_t)~0, after); |
| | TEST_ASSERT_EQUAL_HEX32(before & 07777, after & 07777); |
| |
|
| | unlink(lnk); |
| | unlink(tgt); |
| | rmdir(dir); |
| | free(lnk); |
| | free(tgt); |
| | free(dir); |
| | } |
| |
|
| | |
| | void test_process_file_symlink_with_deref_changes_target(void) |
| | { |
| | char *dir = make_temp_dir(); |
| | TEST_ASSERT_NOT_NULL(dir); |
| |
|
| | char *tgt = path_join(dir, "target2"); |
| | char *lnk = path_join(dir, "link2"); |
| | TEST_ASSERT_NOT_NULL(tgt); |
| | TEST_ASSERT_NOT_NULL(lnk); |
| |
|
| | TEST_ASSERT_EQUAL_INT(0, create_file_with_mode(tgt, 0644)); |
| | TEST_ASSERT_EQUAL_INT(0, create_symlink_to(tgt, lnk)); |
| |
|
| | change = mode_compile("a+x", 0); |
| | dereference = 1; |
| | verbosity = V_off; |
| |
|
| | FTS *fts = NULL; |
| | FTSENT *ent = open_single_fts(lnk, &fts, FTS_PHYSICAL); |
| | TEST_ASSERT_NOT_NULL(ent); |
| | TEST_ASSERT_NOT_NULL(fts); |
| |
|
| | bool ok = process_file(fts, ent); |
| | TEST_ASSERT_EQUAL_INT(0, fts_close(fts)); |
| | TEST_ASSERT_TRUE(ok); |
| |
|
| | mode_t m = get_mode_stat(tgt); |
| | TEST_ASSERT_NOT_EQUAL((mode_t)~0, m); |
| | TEST_ASSERT_TRUE(m & S_IXUSR); |
| |
|
| | unlink(lnk); |
| | unlink(tgt); |
| | rmdir(dir); |
| | free(lnk); |
| | free(tgt); |
| | free(dir); |
| | } |
| |
|
| | |
| | void test_process_file_no_change_needed(void) |
| | { |
| | char *dir = make_temp_dir(); |
| | TEST_ASSERT_NOT_NULL(dir); |
| |
|
| | char *file = path_join(dir, "already_exec"); |
| | TEST_ASSERT_NOT_NULL(file); |
| |
|
| | TEST_ASSERT_EQUAL_INT(0, create_file_with_mode(file, 0755)); |
| | mode_t before = get_mode_stat(file); |
| | TEST_ASSERT_NOT_EQUAL((mode_t)~0, before); |
| |
|
| | change = mode_compile("u+x", 0); |
| | verbosity = V_off; |
| |
|
| | FTS *fts = NULL; |
| | FTSENT *ent = open_single_fts(file, &fts, FTS_PHYSICAL); |
| | TEST_ASSERT_NOT_NULL(ent); |
| | TEST_ASSERT_NOT_NULL(fts); |
| |
|
| | bool ok = process_file(fts, ent); |
| | TEST_ASSERT_EQUAL_INT(0, fts_close(fts)); |
| | TEST_ASSERT_TRUE(ok); |
| |
|
| | mode_t after = get_mode_stat(file); |
| | TEST_ASSERT_NOT_EQUAL((mode_t)~0, after); |
| | TEST_ASSERT_EQUAL_HEX32(before & 07777, after & 07777); |
| |
|
| | unlink(file); |
| | rmdir(dir); |
| | free(file); |
| | free(dir); |
| | } |
| |
|
| | |
| | void test_process_file_verbose_outputs_message(void) |
| | { |
| | char *dir = make_temp_dir(); |
| | TEST_ASSERT_NOT_NULL(dir); |
| |
|
| | char *file = path_join(dir, "verbose_file"); |
| | TEST_ASSERT_NOT_NULL(file); |
| |
|
| | TEST_ASSERT_EQUAL_INT(0, create_file_with_mode(file, 0644)); |
| |
|
| | change = mode_compile("u+x", 0); |
| | verbosity = V_high; |
| |
|
| | |
| | int saved_stdout = dup(STDOUT_FILENO); |
| | TEST_ASSERT_MESSAGE(saved_stdout >= 0, "dup stdout failed"); |
| |
|
| | int pipefd[2]; |
| | TEST_ASSERT_EQUAL_INT_MESSAGE(0, pipe(pipefd), "pipe failed"); |
| |
|
| | fflush(stdout); |
| | if (dup2(pipefd[1], STDOUT_FILENO) < 0) { |
| | |
| | dup2(saved_stdout, STDOUT_FILENO); |
| | close(saved_stdout); |
| | close(pipefd[0]); close(pipefd[1]); |
| | TEST_FAIL_MESSAGE("dup2 failed to redirect stdout"); |
| | } |
| |
|
| | FTS *fts = NULL; |
| | FTSENT *ent = open_single_fts(file, &fts, FTS_PHYSICAL); |
| | TEST_ASSERT_NOT_NULL(ent); |
| | TEST_ASSERT_NOT_NULL(fts); |
| |
|
| | |
| | |
| | bool ok_call = process_file(fts, ent); |
| | ignore_value(fts_close(fts)); |
| | fflush(stdout); |
| |
|
| | |
| | dup2(saved_stdout, STDOUT_FILENO); |
| | close(saved_stdout); |
| | close(pipefd[1]); |
| |
|
| | |
| | TEST_ASSERT_TRUE(ok_call); |
| |
|
| | char buf[1024]; |
| | ssize_t n = read(pipefd[0], buf, sizeof(buf) - 1); |
| | close(pipefd[0]); |
| | TEST_ASSERT_TRUE_MESSAGE(n > 0, "no output captured"); |
| | if (n > 0) buf[n] = '\0'; else buf[0] = '\0'; |
| |
|
| | |
| | TEST_ASSERT_NOT_NULL_MESSAGE(strstr(buf, "mode of"), "Verbose output missing 'mode of'"); |
| | TEST_ASSERT_NOT_NULL_MESSAGE(strstr(buf, "changed"), "Verbose output missing 'changed'"); |
| |
|
| | unlink(file); |
| | rmdir(dir); |
| | free(file); |
| | free(dir); |
| | } |
| |
|
| | int main(void) |
| | { |
| | UNITY_BEGIN(); |
| | RUN_TEST(test_process_file_regular_add_exec); |
| | RUN_TEST(test_process_file_symlink_no_deref_noop); |
| | RUN_TEST(test_process_file_symlink_with_deref_changes_target); |
| | RUN_TEST(test_process_file_no_change_needed); |
| | RUN_TEST(test_process_file_verbose_outputs_message); |
| | return UNITY_END(); |
| | } |