| #include "../../unity/unity.h" |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <signal.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
|
|
| |
| |
| |
|
|
| 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); |
| } |
| |
| 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(tmpl); |
| if (dup2(tfd, STDOUT_FILENO) < 0) { |
| close(tfd); |
| return -1; |
| } |
| *tmpfd_out = tfd; |
| return 0; |
| } |
|
|
| void setUp(void) { |
| |
| } |
|
|
| void tearDown(void) { |
| |
| } |
|
|
| |
| |
| 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; |
|
|
| |
| cleanup(); |
|
|
| int in_closed = fd_is_closed(STDIN_FILENO); |
| int out_closed = fd_is_closed(STDOUT_FILENO); |
|
|
| |
| TEST_ASSERT_EQUAL_INT(0, restore_stdio(saved_in, saved_out)); |
| saved_in = saved_out = -1; |
|
|
| |
| 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"); |
|
|
| |
| TEST_ASSERT_TRUE_MESSAGE((conversions_mask & (C_FSYNC | C_FDATASYNC)) == (C_FSYNC | C_FDATASYNC), |
| "synchronize_output ran despite interrupt_signal; bits were cleared"); |
|
|
| |
| conversions_mask = old_mask; |
| interrupt_signal = old_intr; |
| } |
|
|
| |
| |
| |
| 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; |
| interrupt_signal = 0; |
|
|
| cleanup(); |
|
|
| int in_closed = fd_is_closed(STDIN_FILENO); |
| int out_closed = fd_is_closed(STDOUT_FILENO); |
|
|
| |
| TEST_ASSERT_EQUAL_INT(0, restore_stdio(saved_in, saved_out)); |
| saved_in = saved_out = -1; |
|
|
| |
| 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"); |
|
|
| |
| TEST_ASSERT_EQUAL_INT_MESSAGE(0, conversions_mask & (C_FSYNC | C_FDATASYNC), |
| "synchronize_output did not clear sync bits"); |
|
|
| |
| conversions_mask = old_mask; |
| interrupt_signal = old_intr; |
| } |
|
|
| |
| |
| 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) { |
| |
| int fds[2]; |
| if (pipe(fds) != 0) _exit(100); |
| |
| if (dup2(fds[1], STDOUT_FILENO) < 0) _exit(101); |
| close(fds[0]); |
| close(fds[1]); |
|
|
| conversions_mask = C_FSYNC; |
| interrupt_signal = 0; |
|
|
| |
| cleanup(); |
|
|
| |
| _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(); |
| } |