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