#include "../../unity/unity.h" #include #include #include #include #include #include #include #include #include #include #include /* The test file is included into dd.c, so we can directly access static globals/functions like cleanup, conversions_mask, interrupt_signal, and constants C_FSYNC/C_FDATASYNC. */ static int fd_is_closed(int fd) { errno = 0; int flags = fcntl(fd, F_GETFL); return (flags == -1 && errno == EBADF); } static int save_stdio(int *saved_in, int *saved_out) { *saved_in = dup(STDIN_FILENO); if (*saved_in < 0) return -1; *saved_out = dup(STDOUT_FILENO); if (*saved_out < 0) { close(*saved_in); return -1; } return 0; } static int restore_stdio(int saved_in, int saved_out) { int ok = 0; if (saved_in >= 0) { if (dup2(saved_in, STDIN_FILENO) < 0) ok = -1; close(saved_in); } if (saved_out >= 0) { if (dup2(saved_out, STDOUT_FILENO) < 0) ok = -1; close(saved_out); } /* stdout FILE* still refers to fd=1; ensure stream is usable */ clearerr(stdout); return ok; } static int redirect_stdout_to_temp(int *tmpfd_out) { char tmpl[] = "/tmp/dd_cleanup_test_XXXXXX"; int tfd = mkstemp(tmpl); if (tfd < 0) return -1; /* Unlink so the file is removed when all descriptors close */ unlink(tmpl); if (dup2(tfd, STDOUT_FILENO) < 0) { close(tfd); return -1; } *tmpfd_out = tfd; /* Keep tfd open separately */ return 0; } void setUp(void) { /* No global setup */ } void tearDown(void) { /* No global teardown */ } /* Test: When interrupted, cleanup should skip synchronize_output (i.e., not clear C_FSYNC/C_FDATASYNC), and must close stdin/stdout. */ void test_cleanup_interrupted_skips_sync_and_closes(void) { int saved_in = -1, saved_out = -1; TEST_ASSERT_EQUAL_INT(0, save_stdio(&saved_in, &saved_out)); int old_mask = conversions_mask; sig_atomic_t old_intr = interrupt_signal; conversions_mask = C_FSYNC | C_FDATASYNC; interrupt_signal = SIGINT; /* Nonzero => skip synchronize_output */ /* Call cleanup. Do not use Unity asserts until stdout is restored. */ cleanup(); int in_closed = fd_is_closed(STDIN_FILENO); int out_closed = fd_is_closed(STDOUT_FILENO); /* Restore stdio before asserting */ TEST_ASSERT_EQUAL_INT(0, restore_stdio(saved_in, saved_out)); saved_in = saved_out = -1; /* Assert that both stdio descriptors were closed */ TEST_ASSERT_TRUE_MESSAGE(in_closed, "STDIN was not closed by cleanup when interrupted"); TEST_ASSERT_TRUE_MESSAGE(out_closed, "STDOUT was not closed by cleanup when interrupted"); /* Assert that sync bits remain set (synchronize_output not called) */ TEST_ASSERT_TRUE_MESSAGE((conversions_mask & (C_FSYNC | C_FDATASYNC)) == (C_FSYNC | C_FDATASYNC), "synchronize_output ran despite interrupt_signal; bits were cleared"); /* Restore globals */ conversions_mask = old_mask; interrupt_signal = old_intr; } /* Test: When not interrupted and stdout is a regular file, cleanup should call synchronize_output (which succeeds) and clear C_FSYNC/C_FDATASYNC, and close stdin/stdout. */ void test_cleanup_calls_sync_clears_bits_and_closes(void) { int saved_in = -1, saved_out = -1; TEST_ASSERT_EQUAL_INT(0, save_stdio(&saved_in, &saved_out)); int tmpfd = -1; TEST_ASSERT_EQUAL_INT_MESSAGE(0, redirect_stdout_to_temp(&tmpfd), "Failed to redirect stdout to temp file"); int old_mask = conversions_mask; sig_atomic_t old_intr = interrupt_signal; conversions_mask = C_FDATASYNC; /* Will call fdatasync and possibly fsync */ interrupt_signal = 0; /* Not interrupted => synchronize_output runs */ cleanup(); int in_closed = fd_is_closed(STDIN_FILENO); int out_closed = fd_is_closed(STDOUT_FILENO); /* Restore stdio before asserting */ TEST_ASSERT_EQUAL_INT(0, restore_stdio(saved_in, saved_out)); saved_in = saved_out = -1; /* Close temp backing fd (cleanup closed fd 1; tfd still open) */ if (tmpfd >= 0) close(tmpfd); TEST_ASSERT_TRUE_MESSAGE(in_closed, "STDIN was not closed by cleanup"); TEST_ASSERT_TRUE_MESSAGE(out_closed, "STDOUT was not closed by cleanup"); /* synchronize_output clears these bits even if it had to fall back to fsync */ TEST_ASSERT_EQUAL_INT_MESSAGE(0, conversions_mask & (C_FSYNC | C_FDATASYNC), "synchronize_output did not clear sync bits"); /* Restore globals */ conversions_mask = old_mask; interrupt_signal = old_intr; } /* Test: When not interrupted and stdout is a pipe, synchronize_output fails (fsync on a pipe -> EINVAL), so cleanup must exit with failure. */ void test_cleanup_exit_on_sync_failure(void) { fflush(stdout); fflush(stderr); pid_t pid = fork(); TEST_ASSERT_MESSAGE(pid >= 0, "fork failed"); if (pid == 0) { /* Child: set up stdout as a pipe's write end */ int fds[2]; if (pipe(fds) != 0) _exit(100); /* Redirect stdout to pipe write end */ if (dup2(fds[1], STDOUT_FILENO) < 0) _exit(101); close(fds[0]); close(fds[1]); conversions_mask = C_FSYNC; /* Force fsync */ interrupt_signal = 0; /* Not interrupted */ /* This should call synchronize_output, which fails, and then exit(EXIT_FAILURE). */ cleanup(); /* Should not reach here */ _exit(77); } else { int status = 0; waitpid(pid, &status, 0); TEST_ASSERT_TRUE_MESSAGE(WIFEXITED(status), "Child did not exit normally"); TEST_ASSERT_EQUAL_INT_MESSAGE(EXIT_FAILURE, WEXITSTATUS(status), "cleanup did not exit with failure on sync failure"); } } int main(void) { UNITY_BEGIN(); RUN_TEST(test_cleanup_interrupted_skips_sync_and_closes); RUN_TEST(test_cleanup_calls_sync_clears_bits_and_closes); RUN_TEST(test_cleanup_exit_on_sync_failure); return UNITY_END(); }