Skip to content

Commit 8667f86

Browse files
mbedtls_test_fork_run_child: Use temporary file instead of pipe
Use a temporary file instead of a pipe to communicate the test result and the output from the child to the parent. This makes the data flow and the control flow easier because the code can seek back and forth, whereas a pipe required doing things in order. In particular, there are now fewer special cases to manage, and the parent can read the data in the most convenient order. Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
1 parent 389a258 commit 8667f86

1 file changed

Lines changed: 99 additions & 104 deletions

File tree

tests/src/fork_helpers.c

Lines changed: 99 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include <test/fork_helpers.h>
1818

19+
#include <errno.h>
1920
#include <stdlib.h>
2021
#include <stdio.h>
2122
#include <string.h>
@@ -25,20 +26,20 @@
2526
/** Child exit code for mbedtls_test_fork_run_child().
2627
*/
2728
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.
3032
*
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).
3936
*/
4037
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(). */
4243
CHILD_EXIT_CODE_REPORTING_FAILED = 122,
4344
} child_exit_code_t;
4445

@@ -67,45 +68,40 @@ static int probably_running_under_valgrind(void)
6768
__attribute__((__noreturn__))
6869
#endif
6970
static void run_child(
70-
int write_fd,
71+
FILE *file,
7172
mbedtls_test_fork_child_callback_t *child_callback,
7273
void *param,
7374
unsigned char *buf, size_t size)
7475
{
7576
/* If something goes wrong while trying to report what happened
7677
* in the child, exit with a nonzero status. */
7778
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");
8179
size_t length = 0;
8280

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-
8981
child_callback(param, buf, size, &length);
90-
TEST_LE_U(length, size);
9182

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. */
9395
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) {
97101
goto write_done;
98102
}
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;
109105
}
110106
if (fflush(file) != 0) {
111107
goto write_done;
@@ -155,90 +151,89 @@ int mbedtls_test_fork_run_child(
155151
{
156152
*child_output_length = 0;
157153

158-
int ret = -1;
159154
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;
165156

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 */
166191
pid = fork();
167192
TEST_ASSERT_ERRNO(pid != -1);
168193

169194
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,
173196
child_output, child_output_size);
174197
/* Unreachable */
175198
}
176199
/* Beyond this point, we're in the parent (original) process. */
177200

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 */
224202
int wstatus;
225203
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;
236215
}
237216

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+
238231
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;
242237
}
243238

244239
#endif /* MBEDTLS_PLATFORM_IS_UNIXLIKE */

0 commit comments

Comments
 (0)