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