coreutils / tests /dd /tests_for_cleanup.c
AryaWu's picture
Upload folder using huggingface_hub
78d2150 verified
#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>
/* 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();
}