#include "../../unity/unity.h" #include #include #include #include #include #include #include #include /* The program includes the definitions we need (process_files, globals, and modechange.h declarations) before including this test file. */ /* Helper: create a unique temporary directory. Returns malloc'ed path. */ static char *make_temp_dir(void) { char *tmpl = malloc(64); TEST_ASSERT_NOT_NULL_MESSAGE(tmpl, "malloc failed for tempdir"); snprintf(tmpl, 64, "/tmp/chmod_pf_%ld_XXXXXX", (long)getpid()); if (!mkdtemp(tmpl)) { free(tmpl); TEST_FAIL_MESSAGE("mkdtemp failed"); } return tmpl; /* Caller must free and rmdir */ } /* Helper: join directory and name into a newly-allocated path */ static char *join_path(const char *dir, const char *name) { size_t dl = strlen(dir), nl = strlen(name); size_t need = dl + 1 + nl + 1; char *p = malloc(need); TEST_ASSERT_NOT_NULL_MESSAGE(p, "malloc failed in join_path"); memcpy(p, dir, dl); p[dl] = '/'; memcpy(p + dl + 1, name, nl); p[dl + 1 + nl] = '\0'; return p; } /* Helper: create an empty file with specified mode. Returns malloc'ed path. */ static char *create_file_with_mode(const char *dir, const char *name, mode_t mode) { char *p = join_path(dir, name); int fd = open(p, O_WRONLY | O_CREAT | O_TRUNC, mode); if (fd < 0) { free(p); TEST_FAIL_MESSAGE("open/create file failed"); } close(fd); return p; } /* Helper: create a directory with specified mode. Returns malloc'ed path. */ static char *create_dir_with_mode(const char *parent, const char *name, mode_t mode) { char *p = join_path(parent, name); if (mkdir(p, mode) != 0) { free(p); TEST_FAIL_MESSAGE("mkdir failed"); } return p; } /* Helper: create symlink. Returns malloc'ed link path. */ static char *create_symlink(const char *dir, const char *linkname, const char *target_relative) { char *linkpath = join_path(dir, linkname); if (symlink(target_relative, linkpath) != 0) { free(linkpath); TEST_FAIL_MESSAGE("symlink failed"); } return linkpath; } /* Remember the process umask to restore after each test */ static mode_t saved_process_umask; /* Unity setup/teardown */ void setUp(void) { /* Silence error diagnostics during failure-path tests to keep output clean. */ force_silent = true; diagnose_surprises = false; verbosity = V_off; root_dev_ino = NULL; recurse = false; dereference = -1; /* default semantics */ umask_value = 0; /* don't mask off requested bits in mode_adjust */ /* Compile a default change: add user execute bit. */ change = mode_compile("u+x", 0); TEST_ASSERT_NOT_NULL_MESSAGE(change, "mode_compile returned NULL"); /* Ensure predictable file creation permissions in tests. */ saved_process_umask = umask(0); } void tearDown(void) { /* Restore the process umask. */ umask(saved_process_umask); } /* Compute FTS flags for use with xfts_open */ static int test_fts_flags(void) { return FTS_CWDFD | FTS_PHYSICAL | FTS_NOCHDIR; } /* Test 1: Regular file should be processed successfully and return true. */ void test_process_files_regular_file_success(void) { char *td = make_temp_dir(); char *file = create_file_with_mode(td, "file.txt", 0600); char *files[] = { file, NULL }; int flags = test_fts_flags(); bool ok = process_files(files, flags); TEST_ASSERT_TRUE_MESSAGE(ok, "process_files should succeed for a regular file"); /* Verify that the file now has user execute bit set (from change "u+x"). */ struct stat st; TEST_ASSERT_EQUAL_INT_MESSAGE(0, stat(file, &st), "stat failed on created file"); TEST_ASSERT_TRUE_MESSAGE((st.st_mode & S_IXUSR) != 0, "u+x not applied to regular file"); /* Cleanup */ unlink(file); rmdir(td); free(file); free(td); } /* Test 2: Nonexistent path should cause process_files to return false. */ void test_process_files_nonexistent_returns_false(void) { char *td = make_temp_dir(); char *missing = join_path(td, "no_such_file_or_dir"); char *files[] = { missing, NULL }; int flags = test_fts_flags(); bool ok = process_files(files, flags); TEST_ASSERT_FALSE_MESSAGE(ok, "process_files should fail for a nonexistent path"); /* Cleanup */ free(missing); rmdir(td); free(td); } /* Test 3: Mixed inputs (one missing, one valid) should return false overall. */ void test_process_files_mixed_inputs_accumulate_failure(void) { char *td = make_temp_dir(); char *missing = join_path(td, "nope"); char *file = create_file_with_mode(td, "ok.txt", 0600); char *files[] = { missing, file, NULL }; int flags = test_fts_flags(); bool ok = process_files(files, flags); TEST_ASSERT_FALSE_MESSAGE(ok, "process_files should return false if any input fails"); /* Cleanup */ unlink(file); free(file); free(missing); rmdir(td); free(td); } /* Test 4: Recursive directory traversal should change modes in nested entries and return true. */ void test_process_files_recursive_directory_changes_nested(void) { /* Use a change that visibly affects both directories and files. */ change = mode_compile("o+x", 0); TEST_ASSERT_NOT_NULL_MESSAGE(change, "mode_compile returned NULL for o+x"); recurse = true; char *td = make_temp_dir(); char *subdir = create_dir_with_mode(td, "d", 0700); /* o+x is initially off */ char *nested = create_file_with_mode(subdir, "f.txt", 0600); /* o+x off */ char *files[] = { subdir, NULL }; int flags = test_fts_flags(); bool ok = process_files(files, flags); TEST_ASSERT_TRUE_MESSAGE(ok, "process_files should succeed with recursion"); /* Verify directory has o+x set */ struct stat st; TEST_ASSERT_EQUAL_INT_MESSAGE(0, stat(subdir, &st), "stat failed on directory"); TEST_ASSERT_TRUE_MESSAGE((st.st_mode & S_IXOTH) != 0, "o+x not applied to directory"); /* Verify nested file has o+x set */ TEST_ASSERT_EQUAL_INT_MESSAGE(0, stat(nested, &st), "stat failed on nested file"); TEST_ASSERT_TRUE_MESSAGE((st.st_mode & S_IXOTH) != 0, "o+x not applied to nested file"); /* Cleanup */ unlink(nested); rmdir(subdir); rmdir(td); free(nested); free(subdir); free(td); } /* Optional: Test symlink handling on top-level with default dereference (-1) to ensure success. This also exercises symlink path where fchmodat may act on referent for top-level entries. */ void test_process_files_symlink_top_level_success(void) { char *td = make_temp_dir(); char *target = create_file_with_mode(td, "t.txt", 0600); /* Link points to target in the same directory (relative path). */ char *linkpath = create_symlink(td, "l", "t.txt"); char *files[] = { linkpath, NULL }; int flags = test_fts_flags(); bool ok = process_files(files, flags); TEST_ASSERT_TRUE_MESSAGE(ok, "process_files should succeed for a top-level symlink"); /* Verify referent got user execute bit set (from default 'u+x'). */ struct stat st; TEST_ASSERT_EQUAL_INT_MESSAGE(0, stat(target, &st), "stat failed for symlink target"); TEST_ASSERT_TRUE_MESSAGE((st.st_mode & S_IXUSR) != 0, "u+x not applied to symlink target"); /* Cleanup */ unlink(linkpath); unlink(target); rmdir(td); free(linkpath); free(target); free(td); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_process_files_regular_file_success); RUN_TEST(test_process_files_nonexistent_returns_false); RUN_TEST(test_process_files_mixed_inputs_accumulate_failure); RUN_TEST(test_process_files_recursive_directory_changes_nested); RUN_TEST(test_process_files_symlink_top_level_success); return UNITY_END(); }