|
16 | 16 |
|
17 | 17 | #include <test/fork_helpers.h> |
18 | 18 |
|
| 19 | +#include <errno.h> |
19 | 20 | #include <stdlib.h> |
20 | 21 | #include <stdio.h> |
21 | 22 | #include <string.h> |
|
25 | 26 | /** Child exit code for mbedtls_test_fork_run_child(). |
26 | 27 | */ |
27 | 28 | typedef enum { |
28 | | - /** Reporting of the child output or the child test result through |
29 | | - * the pipe succeeded. |
| 29 | + /** The child successfully reported its test result in the temporary |
| 30 | + * file, and its output if the test passed. Alternatively, the |
| 31 | + * child died before writing anything to the temporary file. |
30 | 32 | * |
31 | | - * The content sent on the pipe has the following format: |
32 | | - * - [1 byte] #mbedtls_test_result_t \c test_result |
33 | | - * - Case \c test_result: |
34 | | - * - If \c test_result == #MBEDTLS_TEST_RESULT_SUCCESS: |
35 | | - * the output from the child body function. |
36 | | - * - Otherwise: |
37 | | - * the child failure (or skip) information, a direct write of |
38 | | - * a #mbedtls_test_result_t structure. |
| 33 | + * The content of the file has the following format: |
| 34 | + * - mbedtls_test_info_t structure; |
| 35 | + * - on pass, the ouptut (up to the end of the file). |
39 | 36 | */ |
40 | 37 | CHILD_EXIT_CODE_OK = 0, |
41 | | - /** Something went wrong, e.g. a write error on the pipe. */ |
| 38 | + /** Something went wrong in a way that could not be recorded |
| 39 | + * in the temporary file. Pick a value that's nonzero (not a success), |
| 40 | + * less than 128 (conventionally the upper bound for exit statuses), |
| 41 | + * and unlikely to be the exit code of some other program if the |
| 42 | + * child calls execve(). */ |
42 | 43 | CHILD_EXIT_CODE_REPORTING_FAILED = 122, |
43 | 44 | } child_exit_code_t; |
44 | 45 |
|
@@ -67,45 +68,40 @@ static int probably_running_under_valgrind(void) |
67 | 68 | __attribute__((__noreturn__)) |
68 | 69 | #endif |
69 | 70 | static void run_child( |
70 | | - int write_fd, |
| 71 | + FILE *file, |
71 | 72 | mbedtls_test_fork_child_callback_t *child_callback, |
72 | 73 | void *param, |
73 | 74 | unsigned char *buf, size_t size) |
74 | 75 | { |
75 | 76 | /* If something goes wrong while trying to report what happened |
76 | 77 | * in the child, exit with a nonzero status. */ |
77 | 78 | int child_exit_code = CHILD_EXIT_CODE_REPORTING_FAILED; |
78 | | - /* We'll use stdio to write to the pipe, so we don't have to |
79 | | - * manage EINTR and such. */ |
80 | | - FILE *file = fdopen(write_fd, "a"); |
81 | 79 | size_t length = 0; |
82 | 80 |
|
83 | | - if (file == NULL) { |
84 | | - /* There's no way we can report anything other than the exit code. |
85 | | - * So we might as well quit without even running the child callback. */ |
86 | | - goto write_done; |
87 | | - } |
88 | | - |
89 | 81 | child_callback(param, buf, size, &length); |
90 | | - TEST_LE_U(length, size); |
91 | 82 |
|
92 | | - /* Label called `exit`: this is where TEST_ASSERT() and friends jump to. */ |
| 83 | + if (mbedtls_test_get_result() == MBEDTLS_TEST_RESULT_SUCCESS && length != 0) { |
| 84 | + /* Write the output. This could fail on a full disk. Remember to |
| 85 | + * flush (otherwise the output would likely be truncated). */ |
| 86 | + errno = 0; |
| 87 | + size_t n = fwrite(buf, 1, length, file); |
| 88 | + TEST_ASSERT_ERRNO(n == length); |
| 89 | + TEST_ASSERT_ERRNO(fflush(file) == 0); |
| 90 | + } |
| 91 | + |
| 92 | + /* Report our final status (covering the child callback and the output |
| 93 | + * reporting). The file is already large enough, so a failure at this |
| 94 | + * stage is very unlikely. */ |
93 | 95 | exit: |
94 | | - ; // label followed by a declaration is not portable C |
95 | | - char result_char = mbedtls_test_get_result(); |
96 | | - if (fputc(result_char, file) == EOF) { |
| 96 | + /* can't put a label on a declaration */; |
| 97 | + mbedtls_test_info_t test_info; |
| 98 | + mbedtls_test_info_save(&test_info); |
| 99 | + |
| 100 | + if (fseek(file, 0, SEEK_SET) != 0) { |
97 | 101 | goto write_done; |
98 | 102 | } |
99 | | - if (result_char == MBEDTLS_TEST_RESULT_SUCCESS) { |
100 | | - if (length != 0 && fwrite(buf, length, 1, file) != 1) { |
101 | | - goto write_done; |
102 | | - } |
103 | | - } else { |
104 | | - mbedtls_test_info_t test_info; |
105 | | - mbedtls_test_info_save(&test_info); |
106 | | - if (fwrite(&test_info, sizeof(test_info), 1, file) != 1) { |
107 | | - goto write_done; |
108 | | - } |
| 103 | + if (fwrite(&test_info, sizeof(test_info), 1, file) != 1) { |
| 104 | + goto write_done; |
109 | 105 | } |
110 | 106 | if (fflush(file) != 0) { |
111 | 107 | goto write_done; |
@@ -155,90 +151,89 @@ int mbedtls_test_fork_run_child( |
155 | 151 | { |
156 | 152 | *child_output_length = 0; |
157 | 153 |
|
158 | | - int ret = -1; |
159 | 154 | pid_t pid = -1; |
160 | | - int pipe_fd[2] = { -1, -1 }; |
161 | | - |
162 | | - /* Set up a pipe. The child will write to pipe_fd[1], and the |
163 | | - * parent will read from pipe_fd[0]. */ |
164 | | - TEST_ASSERT_ERRNO(pipe(pipe_fd) != -1); |
| 155 | + FILE *file = NULL; |
165 | 156 |
|
| 157 | + /* Create a temporary file where the child can write the test result |
| 158 | + * and its output. We unlink the file as soon as we've created it, |
| 159 | + * but we keep it open. This way, both the parent and the child process |
| 160 | + * can keep using the file until they close it, but the file will |
| 161 | + * be removed when both processes have closed it. |
| 162 | + */ |
| 163 | + /* Make up a unique name using the current (parent) process ID and |
| 164 | + * a stack address inside that process. */ |
| 165 | + char filename[sizeof("mbedtls_test_fork_run_child-%ld-%p.tmp") + |
| 166 | + 3 * sizeof(long) + 2 * sizeof(void *)]; |
| 167 | + mbedtls_snprintf(filename, sizeof(filename), |
| 168 | + "mbedtls_test_fork_run_child-%ld-%p.tmp", |
| 169 | + (long) getpid(), (void *) filename); |
| 170 | + file = fopen(filename, "w+"); |
| 171 | + TEST_ASSERT_ERRNO(file != NULL); |
| 172 | + unlink(filename); |
| 173 | + |
| 174 | + /* The temporary file will contain the test result from the child, |
| 175 | + * followed by the output from the child callback. |
| 176 | + * |
| 177 | + * Pre-fill a failure result. |
| 178 | + * The file position is at the beginning of the expected output. |
| 179 | + */ |
| 180 | + mbedtls_test_info_t child_test_info; |
| 181 | + memset(&child_test_info, 0, sizeof(child_test_info)); |
| 182 | + child_test_info.result = MBEDTLS_TEST_RESULT_FAILED; |
| 183 | + child_test_info.test = "Child died without reporting status"; |
| 184 | + child_test_info.step = -1; |
| 185 | + child_test_info.filename = __FILE__; |
| 186 | + child_test_info.line_no = __LINE__; |
| 187 | + TEST_ASSERT_ERRNO(fwrite(&child_test_info, sizeof(child_test_info), 1, file) > 0); |
| 188 | + TEST_ASSERT_ERRNO(fflush(file) == 0); |
| 189 | + |
| 190 | + /* Fork the child */ |
166 | 191 | pid = fork(); |
167 | 192 | TEST_ASSERT_ERRNO(pid != -1); |
168 | 193 |
|
169 | 194 | if (pid == 0) { |
170 | | - /* The child code */ |
171 | | - close(pipe_fd[0]); |
172 | | - run_child(pipe_fd[1], child_callback, param, |
| 195 | + run_child(file, child_callback, param, |
173 | 196 | child_output, child_output_size); |
174 | 197 | /* Unreachable */ |
175 | 198 | } |
176 | 199 | /* Beyond this point, we're in the parent (original) process. */ |
177 | 200 |
|
178 | | - close(pipe_fd[1]); |
179 | | - pipe_fd[1] = -1; |
180 | | - |
181 | | - unsigned char result_char; |
182 | | - struct { |
183 | | - mbedtls_test_info_t child_test_info; |
184 | | - unsigned char excess; |
185 | | - } reading_on_failure; |
186 | | - /* Normally, the child should give us a 1-byte result, then either |
187 | | - * the child body's output or a test info. */ |
188 | | - ssize_t n = read(pipe_fd[0], &result_char, 1); |
189 | | - TEST_EQUAL(n, 1); |
190 | | - |
191 | | - /* Tentatively read what we were promised. Don't commit to anything |
192 | | - * until we have the child's exit status. */ |
193 | | - size_t bytes_read = 0; |
194 | | - if (result_char == MBEDTLS_TEST_RESULT_SUCCESS) { |
195 | | - do { |
196 | | - n = read(pipe_fd[0], |
197 | | - child_output + bytes_read, |
198 | | - child_output_size - bytes_read); |
199 | | - if (n > 0) { |
200 | | - bytes_read += n; |
201 | | - } |
202 | | - } while (n > 0 && bytes_read < child_output_size); |
203 | | - TEST_ASSERT_ERRNO(n != -1); |
204 | | - } else { |
205 | | - do { |
206 | | - n = read(pipe_fd[0], |
207 | | - (unsigned char *) &reading_on_failure + bytes_read, |
208 | | - sizeof(reading_on_failure) - bytes_read); |
209 | | - if (n > 0) { |
210 | | - bytes_read += n; |
211 | | - } |
212 | | - } while (n > 0 && bytes_read < sizeof(reading_on_failure)); |
213 | | - TEST_ASSERT_ERRNO(n != -1); |
214 | | - /* Check that the child wrote the amount of data that what we expect. */ |
215 | | - TEST_EQUAL(bytes_read, sizeof(reading_on_failure.child_test_info)); |
216 | | - } |
217 | | - |
218 | | - /* Close the pipe. If we left it open, there could be a deadlock if the |
219 | | - * child tried to write more than it should, while the parent is just |
220 | | - * waiting for the child to exit. */ |
221 | | - close(pipe_fd[0]); |
222 | | - pipe_fd[0] = -1; |
223 | | - |
| 201 | + /* Reap the child */ |
224 | 202 | int wstatus; |
225 | 203 | TEST_ASSERT_ERRNO(waitpid(pid, &wstatus, 0) == pid); |
226 | | - if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == CHILD_EXIT_CODE_OK) { |
227 | | - if (result_char == MBEDTLS_TEST_RESULT_SUCCESS) { |
228 | | - *child_output_length = bytes_read; |
229 | | - ret = 0; |
230 | | - } else { |
231 | | - mbedtls_test_info_overwrite(&reading_on_failure.child_test_info); |
232 | | - } |
233 | | - } else { |
234 | | - /* Weird status, just report it. */ |
235 | | - TEST_EQUAL(wstatus, 0); |
| 204 | + TEST_EQUAL(wstatus, CHILD_EXIT_CODE_OK); |
| 205 | + |
| 206 | + /* The child exited normally. Obtain the test result from the child. */ |
| 207 | + TEST_ASSERT_ERRNO(fseek(file, 0, SEEK_SET) == 0); |
| 208 | + TEST_ASSERT_ERRNO(fread(&child_test_info, 1, sizeof(child_test_info), file) > 0); |
| 209 | + |
| 210 | + if (child_test_info.result != MBEDTLS_TEST_RESULT_SUCCESS) { |
| 211 | + /* Skip or failure in the child. Transfer the child's test |
| 212 | + * result into the parent. */ |
| 213 | + mbedtls_test_info_overwrite(&child_test_info); |
| 214 | + goto exit; |
236 | 215 | } |
237 | 216 |
|
| 217 | + /* The child succeeded. Read the output. */ |
| 218 | + TEST_ASSERT_ERRNO(fseek(file, 0, SEEK_END) == 0); |
| 219 | + long pos = ftell(file); |
| 220 | + TEST_ASSERT_ERRNO(pos >= 0); |
| 221 | + TEST_LE_U(sizeof(child_test_info), pos); |
| 222 | + size_t len = pos - sizeof(child_test_info); |
| 223 | + TEST_LE_U(len, child_output_size); |
| 224 | + TEST_ASSERT_ERRNO(fseek(file, sizeof(child_test_info), SEEK_SET) == 0); |
| 225 | + TEST_EQUAL(fread(child_output, 1, len, file), len); |
| 226 | + *child_output_length = len; |
| 227 | + |
| 228 | + fclose(file); |
| 229 | + return 0; |
| 230 | + |
238 | 231 | exit: |
239 | | - close(pipe_fd[0]); |
240 | | - close(pipe_fd[1]); |
241 | | - return ret; |
| 232 | + if (file != NULL) { |
| 233 | + fclose(file); |
| 234 | + } |
| 235 | + mbedtls_test_fork_child_fd = -1; |
| 236 | + return -1; |
242 | 237 | } |
243 | 238 |
|
244 | 239 | #endif /* MBEDTLS_PLATFORM_IS_UNIXLIKE */ |
0 commit comments