File size: 6,210 Bytes
78d2150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#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();
}