#include "../../unity/unity.h" #include #include #include #include #include #include #include #include #include "quote.h" /* Unity fixtures */ void setUp(void) { /* Ensure deterministic messages and quoting */ setlocale(LC_ALL, "C"); set_quoting_style(NULL, literal_quoting_style); } void tearDown(void) { /* no-op */ } /* Helper: capture stdout produced by describe_change. On success, returns malloc'd NUL-terminated string (caller frees). On failure, returns NULL (no Unity assertions while stdout is redirected). */ static char* capture_describe_change_output(const char *file, const struct change_status *ch) { int pipefd[2]; if (pipe(pipefd) == -1) { return NULL; } int saved_stdout = dup(STDOUT_FILENO); if (saved_stdout == -1) { close(pipefd[0]); close(pipefd[1]); return NULL; } fflush(stdout); if (dup2(pipefd[1], STDOUT_FILENO) == -1) { close(saved_stdout); close(pipefd[0]); close(pipefd[1]); return NULL; } /* We can close the original write end now; stdout holds a dup of it. */ close(pipefd[1]); /* Call the target function; avoid Unity asserts during redirection */ describe_change(file, ch); /* Flush and restore stdout */ fflush(stdout); if (dup2(saved_stdout, STDOUT_FILENO) == -1) { /* Even if restore fails, attempt to continue to read what we can */ } close(saved_stdout); /* Read all data from the pipe */ char *buf = NULL; size_t cap = 0; size_t len = 0; char tmp[4096]; ssize_t n; while ((n = read(pipefd[0], tmp, sizeof tmp)) > 0) { if (len + (size_t)n + 1 > cap) { size_t new_cap = (cap == 0 ? 4096 : cap * 2); while (new_cap < len + (size_t)n + 1) new_cap *= 2; char *new_buf = (char*)realloc(buf, new_cap); if (!new_buf) { free(buf); close(pipefd[0]); return NULL; } buf = new_buf; cap = new_cap; } memcpy(buf + len, tmp, (size_t)n); len += (size_t)n; } close(pipefd[0]); if (!buf) { buf = (char*)malloc(1); if (!buf) return NULL; len = 0; } buf[len] = '\0'; return buf; } /* Test: CH_NOT_APPLIED message */ void test_describe_change_not_applied(void) { struct change_status ch; memset(&ch, 0, sizeof ch); ch.status = CH_NOT_APPLIED; char *out = capture_describe_change_output("file", &ch); TEST_ASSERT_NOT_NULL_MESSAGE(out, "Failed to capture output"); TEST_ASSERT_EQUAL_STRING("neither symbolic link file nor referent has been changed\n", out); free(out); } /* Test: CH_NO_STAT message */ void test_describe_change_no_stat(void) { struct change_status ch; memset(&ch, 0, sizeof ch); ch.status = CH_NO_STAT; char *out = capture_describe_change_output("missing", &ch); TEST_ASSERT_NOT_NULL_MESSAGE(out, "Failed to capture output"); TEST_ASSERT_EQUAL_STRING("missing could not be accessed\n", out); free(out); } /* Test: CH_SUCCEEDED message with regular file modes */ void test_describe_change_succeeded_basic(void) { struct change_status ch; memset(&ch, 0, sizeof ch); ch.status = CH_SUCCEEDED; ch.old_mode = S_IFREG | 0600; ch.new_mode = S_IFREG | 0640; char *out = capture_describe_change_output("file", &ch); TEST_ASSERT_NOT_NULL_MESSAGE(out, "Failed to capture output"); TEST_ASSERT_EQUAL_STRING( "mode of file changed from 0600 (rw-------) to 0640 (rw-r-----)\n", out); free(out); } /* Test: CH_FAILED message with regular file modes */ void test_describe_change_failed_basic(void) { struct change_status ch; memset(&ch, 0, sizeof ch); ch.status = CH_FAILED; ch.old_mode = S_IFREG | 0755; ch.new_mode = S_IFREG | 0700; char *out = capture_describe_change_output("exec", &ch); TEST_ASSERT_NOT_NULL_MESSAGE(out, "Failed to capture output"); TEST_ASSERT_EQUAL_STRING( "failed to change mode of exec from 0755 (rwxr-xr-x) to 0700 (rwx------)\n", out); free(out); } /* Test: CH_NO_CHANGE_REQUESTED prints only new mode and perms */ void test_describe_change_no_change_requested_with_setuid(void) { struct change_status ch; memset(&ch, 0, sizeof ch); ch.status = CH_NO_CHANGE_REQUESTED; ch.old_mode = S_IFREG | 0600; /* old_mode is ignored in this branch */ ch.new_mode = S_IFREG | S_ISUID | 0755; /* 04755 -> rwsr-xr-x */ char *out = capture_describe_change_output("suidbin", &ch); TEST_ASSERT_NOT_NULL_MESSAGE(out, "Failed to capture output"); TEST_ASSERT_EQUAL_STRING( "mode of suidbin retained as 4755 (rwsr-xr-x)\n", out); free(out); } /* Test: CH_SUCCEEDED with directory mode; ensure leading file-type char removed */ void test_describe_change_succeeded_directory(void) { struct change_status ch; memset(&ch, 0, sizeof ch); ch.status = CH_SUCCEEDED; ch.old_mode = S_IFDIR | 0700; ch.new_mode = S_IFDIR | 0777; char *out = capture_describe_change_output("dir", &ch); TEST_ASSERT_NOT_NULL_MESSAGE(out, "Failed to capture output"); TEST_ASSERT_EQUAL_STRING( "mode of dir changed from 0700 (rwx------) to 0777 (rwxrwxrwx)\n", out); free(out); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_describe_change_not_applied); RUN_TEST(test_describe_change_no_stat); RUN_TEST(test_describe_change_succeeded_basic); RUN_TEST(test_describe_change_failed_basic); RUN_TEST(test_describe_change_no_change_requested_with_setuid); RUN_TEST(test_describe_change_succeeded_directory); return UNITY_END(); }