occlum/test/vfork/main.c
Hui, Chunyang 4c3ca79134 Make vfork stop parent child threads
When vfork is called and the current process has other running child threads,
for Linux, the other threads remain running. For Occlum, this behavior is
different. All the other threads will be frozen until the vfork returns
or execve is called in the child process.

The reason is that since Occlum doesn't support fork, many applications will
use vfork to replace fork. For multi-threaded applications, if vfork doesn't
stop other child threads, the application will be more likely to fail because
the child process directly uses the VM and the file table of the parent process.
2022-10-18 21:57:57 +08:00

179 lines
5.2 KiB
C

#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <pthread.h>
#include "test.h"
// Note: This test intends to test the case that child process directly calls _exit()
// after vfork. "exit", "_exit" and returning from main function are different.
// And here the exit function must be "_exit" to prevent undefined bevaviour.
int test_vfork_exit() {
pid_t child_pid = vfork();
if (child_pid == 0) {
_exit(0);
} else {
printf ("Comming back to parent process from child with pid = %d\n", child_pid);
}
return 0;
}
int test_multiple_vfork_execve() {
char **child_argv = calloc(1, sizeof(char *) * 2); // "hello_world", NULL
child_argv[0] = strdup("naughty_child");
for (int i = 0; i < 3; i++ ) {
pid_t child_pid = vfork();
if (child_pid == 0) {
int ret = execve("/bin/naughty_child", child_argv, NULL);
if (ret != 0) {
printf("child process execve error");
}
_exit(1);
} else {
printf ("Comming back to parent process from child with pid = %d\n", child_pid);
int ret = waitpid(child_pid, 0, 0);
if (ret != child_pid) {
THROW_ERROR("wait child error, child pid = %d\n", child_pid);
}
}
}
return 0;
}
// Create a pipe between parent and child and check file status.
int test_vfork_isolate_file_table() {
int pipe_fds[2];
if (pipe(pipe_fds) < 0) {
THROW_ERROR("failed to create a pipe");
}
pid_t child_pid = vfork();
if (child_pid == 0) {
close(pipe_fds[1]); // close write end
char **child_argv = calloc(1,
sizeof(char *) * (5 + 1)); // naughty_child -t vfork reader_fd writer_fd
child_argv[0] = "naughty_child";
child_argv[1] = "-t";
child_argv[2] = "vfork";
if (asprintf(&child_argv[3], "%d", pipe_fds[0]) < 0 ||
asprintf(&child_argv[4], "%d", pipe_fds[1]) < 0) {
THROW_ERROR("failed to asprintf");
}
int ret = execve("/bin/naughty_child", child_argv, NULL);
if (ret != 0) {
printf("child process execve error\n");
}
_exit(1);
} else {
printf ("Comming back to parent process from child with pid = %d\n", child_pid);
if (close(pipe_fds[0]) < 0) { // close read end
printf("close pipe reader error\n");
goto parent_exit;
}
char *greetings = "Hello from parent\n";
if (write(pipe_fds[1], greetings, strlen(greetings) + 1) < 0) {
printf("parent write pipe error\n");
goto parent_exit;
}
int ret = waitpid(child_pid, 0, 0);
if (ret != child_pid) {
THROW_ERROR("wait child error, child pid = %d\n", child_pid);
}
}
return 0;
parent_exit:
kill(child_pid, SIGKILL);
exit(1);
}
volatile static int test_stop_child_flag = 0;
static void *child_thread_routine(void *_arg) {
printf("Child thread starts\n");
test_stop_child_flag = 1;
struct timespec t1, t2;
if (clock_gettime(CLOCK_REALTIME, &t1)) {
return (void *) -1;
}
sleep(1);
if (clock_gettime(CLOCK_REALTIME, &t2)) {
return (void *) -1;
}
// Parent thread vfork and will stop this thread for several seconds
if (t2.tv_sec - t1.tv_sec <= 1) {
printf("the thread is not stopped");
exit(-1);
}
printf("child thread exits\n");
return NULL;
}
// Test the behavior that when vfork is called, the parent process' other child threads are forced to stopped.
//
// This test case has different behaviors for Linux and Occlum
// This limitation is recorded in src/libos/src/process/do_vfork.rs
int test_vfork_stop_child_thread() {
pthread_t child_thread;
pid_t child_pid;
struct timespec ts;
ts.tv_sec = 3;
ts.tv_nsec = 0;
if (pthread_create(&child_thread, NULL, child_thread_routine, NULL) < 0) {
THROW_ERROR("pthread_create failed\n");
}
// Wait for child thread to start
while (test_stop_child_flag == 0);
child_pid = vfork();
if (child_pid == 0) {
printf("child process created\n");
char **child_argv = calloc(1, sizeof(char *) * 2);
child_argv[0] = "getpid";
// Wait for a few seconds
while (1) {
int ret = nanosleep(&ts, &ts);
if (ret == 0) {
break;
}
if (ret < 0 && errno != EINTR) {
THROW_ERROR("nanosleep failed");
}
}
printf("child process exec\n");
int ret = execve("/bin/getpid", child_argv, NULL);
if (ret != 0) {
printf("child process execve error\n");
}
_exit(1);
} else {
printf("return to parent\n");
pthread_join(child_thread, NULL);
}
return 0;
}
static test_case_t test_cases[] = {
TEST_CASE(test_vfork_exit),
TEST_CASE(test_multiple_vfork_execve),
TEST_CASE(test_vfork_isolate_file_table),
TEST_CASE(test_vfork_stop_child_thread),
};
int main() {
return test_suite_run(test_cases, ARRAY_SIZE(test_cases));
}