diff --git a/src/libos/src/process/do_exec.rs b/src/libos/src/process/do_exec.rs index e76ffa21..74f08b2e 100644 --- a/src/libos/src/process/do_exec.rs +++ b/src/libos/src/process/do_exec.rs @@ -3,14 +3,16 @@ use std::path::Path; use super::do_exit::exit_old_process_for_execve; use super::do_spawn::new_process_for_exec; +use super::do_vfork::{check_vfork_for_exec, vfork_return_to_parent}; use super::process::ProcessFilter; use super::term_status::TermStatus; use super::wait::Waiter; use super::{do_exit, do_exit_group}; use super::{table, ProcessRef, ProcessStatus}; -use super::{task, ThreadRef}; +use super::{task, ThreadId, ThreadRef}; use crate::interrupt::broadcast_interrupts; use crate::prelude::*; +use crate::syscall::CpuContext; // FIXME: `occlum exec` command will return early if the application calls execve successfully. // Because the "execved"-ed application will run on a new thread and the current thread will exit. @@ -21,30 +23,61 @@ pub fn do_exec( argv: &[CString], envp: &[CString], current_ref: &ThreadRef, + context: *mut CpuContext, ) -> Result { trace!( "exec current process pid = {:?}", current_ref.process().pid() ); - // Construct new process structure but with same parent, pid, tid - let current = current!(); - let new_process_ref = super::do_spawn::new_process_for_exec(path, argv, envp, current_ref); + let (is_vforked, tid, parent_process) = + if let Some((tid, parent_process)) = check_vfork_for_exec(current_ref) { + (true, tid, parent_process) + } else { + // Without vfork, current process directly calls execve. + // Construct new process structure but with same parent, pid, tid + ( + false, + // Reuse self tid + ThreadId { + tid: current_ref.process().pid() as u32, + }, + // Reuse parent process as parent + Some(current_ref.process().parent().clone()), + ) + }; + + let new_process_ref = super::do_spawn::new_process_for_exec( + path, + argv, + envp, + current_ref, + Some(tid), + parent_process, + ); if let Ok(new_process_ref) = new_process_ref { let new_main_thread = new_process_ref .main_thread() .expect("the main thread is just created; it must exist"); + if is_vforked { + // Don't exit current process if this is a vforked child process. + table::add_thread(new_process_ref.main_thread().unwrap()); + table::add_process(new_process_ref); + task::enqueue_and_exec(new_main_thread); + return vfork_return_to_parent(context, current_ref); + } + // Force exit all child threads of current process let term_status = TermStatus::Exited(0 as u8); - current.process().force_exit(term_status); + current_ref.process().force_exit(term_status); // Don't hesitate. Interrupt all threads right now (except the calling thread). broadcast_interrupts(); // Wait for all threads (except calling thread) to exit - wait_for_other_threads_to_exit(current); + wait_for_other_threads_to_exit(current_ref); // Exit current thread and let new process to adopt current's child process exit_old_process_for_execve(term_status, new_process_ref.clone()); @@ -67,7 +100,7 @@ pub fn do_exec( } // Blocking wait until there is only one thread in the calling process -fn wait_for_other_threads_to_exit(current_ref: ThreadRef) { +fn wait_for_other_threads_to_exit(current_ref: &ThreadRef) { use super::do_futex::{self, FutexTimeout}; use crate::time::{timespec_t, ClockID}; use core::time::Duration; @@ -78,7 +111,7 @@ fn wait_for_other_threads_to_exit(current_ref: ThreadRef) { timespec_t::from(Duration::from_millis(50)), ); // Use calling process's pointer as futex value - let futex_val = Arc::as_ptr(¤t_ref.process()) as *const i32; + let futex_val = Arc::as_ptr(current_ref.process()) as *const i32; loop { let thread_num = current_ref.process().threads().len(); if thread_num == 1 { @@ -87,7 +120,7 @@ fn wait_for_other_threads_to_exit(current_ref: ThreadRef) { // Blocking wait here. When a thread exit, it will notify us. unsafe { do_futex::futex_wait( - Arc::as_ptr(¤t_ref.process()) as *const i32, + Arc::as_ptr(current_ref.process()) as *const i32, *futex_val, &Some(timeout), ) diff --git a/src/libos/src/process/do_exit.rs b/src/libos/src/process/do_exit.rs index 2cfeb7f3..d0b68ea0 100644 --- a/src/libos/src/process/do_exit.rs +++ b/src/libos/src/process/do_exit.rs @@ -2,16 +2,24 @@ use crate::signal::constants::*; use std::intrinsics::atomic_store; use super::do_futex::futex_wake; +use super::do_vfork::{is_vforked_child_process, vfork_return_to_parent}; use super::pgrp::clean_pgrp_when_exit; use super::process::{Process, ProcessFilter}; use super::{table, ProcessRef, TermStatus, ThreadRef, ThreadStatus}; use crate::prelude::*; use crate::signal::{KernelSignal, SigNum}; +use crate::syscall::CpuContext; -pub fn do_exit_group(status: i32) { - let term_status = TermStatus::Exited(status as u8); - current!().process().force_exit(term_status); - exit_thread(term_status); +pub fn do_exit_group(status: i32, curr_user_ctxt: &mut CpuContext) -> Result { + if is_vforked_child_process() { + let current = current!(); + return vfork_return_to_parent(curr_user_ctxt as *mut _, ¤t); + } else { + let term_status = TermStatus::Exited(status as u8); + current!().process().force_exit(term_status); + exit_thread(term_status); + Ok(0) + } } pub fn do_exit(status: i32) { diff --git a/src/libos/src/process/do_spawn/mod.rs b/src/libos/src/process/do_spawn/mod.rs index 77da1272..13889d58 100644 --- a/src/libos/src/process/do_spawn/mod.rs +++ b/src/libos/src/process/do_spawn/mod.rs @@ -118,6 +118,7 @@ fn new_process( host_stdio_fds, current_ref, None, + None, )?; table::add_process(new_process_ref.clone()); table::add_thread(new_process_ref.main_thread().unwrap()); @@ -131,6 +132,8 @@ pub fn new_process_for_exec( argv: &[CString], envp: &[CString], current_ref: &ThreadRef, + reuse_tid: Option, + parent_process: Option, ) -> Result { let tid = ThreadId { tid: current_ref.process().pid() as u32, @@ -143,7 +146,8 @@ pub fn new_process_for_exec( None, None, current_ref, - Some(tid), + reuse_tid, + parent_process, )?; Ok(new_process_ref) @@ -158,6 +162,7 @@ fn new_process_common( host_stdio_fds: Option<&HostStdioFds>, current_ref: &ThreadRef, reuse_tid: Option, + parent_process: Option, ) -> Result { let mut argv = argv.clone().to_vec(); let (is_script, elf_inode, mut elf_buf, elf_header) = @@ -279,15 +284,20 @@ fn new_process_common( let elf_name = elf_path.rsplit('/').collect::>()[0]; let thread_name = ThreadName::new(elf_name); - let mut parent; let mut process_builder = ProcessBuilder::new(); - if reuse_tid.is_some() { - process_builder = process_builder.tid(reuse_tid.unwrap()); - parent = current!().process().parent(); - } else { - parent = process_ref; + + // Use specified tid if any + if let Some(reuse_tid) = reuse_tid { + process_builder = process_builder.tid(reuse_tid); } + // Use specified parent process if any + let parent = if let Some(parent) = parent_process { + parent + } else { + process_ref + }; + let new_process = process_builder .vm(vm_ref) .exec_path(&elf_path) diff --git a/src/libos/src/process/do_vfork.rs b/src/libos/src/process/do_vfork.rs new file mode 100644 index 00000000..df1a64d8 --- /dev/null +++ b/src/libos/src/process/do_vfork.rs @@ -0,0 +1,133 @@ +use super::{ProcessRef, ThreadId, ThreadRef}; +use crate::fs::FileTable; +use crate::prelude::*; +use crate::syscall::CpuContext; +use std::collections::HashMap; +use std::mem; + +// From Man page: The calling thread is suspended until the child terminates (either normally, by calling +// _exit(2), or abnormally, after delivery of a fatal signal), or it makes a call to execve(2). +// Until that point, the child shares all memory with its parent, including the stack. +// +// Thus in this implementation, the main idea is to let child use parent's task until exit or execve. +// +// Limitation: +// The child process will not have a complete process structure before execve. Thus during the time from vfork +// to new child process execve or exit, the child process just reuse the parent process's everything, including +// task, pid and etc. And also the log of child process will not start from the point that vfork returns but the +// point that execve returns. + +lazy_static! { + // Store all the parents's file tables who call vfork. It will be recovered when the child exits or has its own task. + // K: parent pid, V: parent file table + static ref VFORK_PARENT_FILE_TABLES: SgxMutex> = SgxMutex::new(HashMap::new()); +} + +thread_local! { + // Store the current process' vforked child and current thread's cpu context. A parent only has one vforked child at a time. + static VFORK_CONTEXT: RefCell> = Default::default(); +} + +pub fn do_vfork(mut context: *mut CpuContext) -> Result { + let current = current!(); + trace!("vfork parent process pid = {:?}", current.process().pid()); + + // Generate a new pid for child process + let child_pid = { + let new_tid = ThreadId::new(); + new_tid.as_u32() as pid_t + }; + + // Save parent's context in TLS + VFORK_CONTEXT.with(|cell| { + let mut ctx = cell.borrow_mut(); + let new_context = (child_pid, unsafe { (*context).clone() }); + *ctx = Some(new_context); + }); + + // Save parent's file table + let parent_pid = current.process().pid(); + let mut vfork_file_tables = VFORK_PARENT_FILE_TABLES.lock().unwrap(); + let parent_file_table = { + let mut current_file_table = current.files().lock().unwrap(); + let new_file_table = current_file_table.clone(); + // FileTable contains non-cloned struct, so here we do a memory replacement to use new + // file table in child and store the original file table in TLS. + mem::replace(&mut *current_file_table, new_file_table) + }; + if let Some(_) = vfork_file_tables.insert(parent_pid, parent_file_table) { + return_errno!(EINVAL, "current process's vfork has not returned yet"); + } + + // This is the first time return and will return as child. + // The second time return will return as parent in vfork_return_to_parent. + info!("vfork child pid = {:?}", child_pid); + return Ok(0 as isize); +} + +// Check if the calling process is a vforked child process that reuse parent's task and pid. +pub fn is_vforked_child_process() -> bool { + VFORK_CONTEXT.with(|cell| { + let ctx = cell.borrow(); + return ctx.is_some(); + }) +} + +// Return to parent process to continue executing +pub fn vfork_return_to_parent( + mut context: *mut CpuContext, + current_ref: &ThreadRef, +) -> Result { + return restore_parent_process(context, current_ref); +} + +fn restore_parent_process(mut context: *mut CpuContext, current_ref: &ThreadRef) -> Result { + let current_thread = current!(); + let current_pid = current_ref.process().pid(); + + // Restore parent file table + let parent_file_table = { + let mut parent_file_tables = VFORK_PARENT_FILE_TABLES.lock().unwrap(); + if let Some(table) = parent_file_tables.remove(¤t_pid) { + table + } else { + return_errno!(EFAULT, "couldn't restore parent file table"); + } + }; + let mut current_file_table = current_ref.files().lock().unwrap(); + *current_file_table = parent_file_table; + + // Get child pid and restore CpuContext + let mut child_pid = 0; + VFORK_CONTEXT.with(|cell| { + let mut ctx = cell.borrow_mut(); + child_pid = ctx.unwrap().0; + unsafe { *context = ctx.unwrap().1 }; + *ctx = None; + }); + + // Set return value to child_pid + // This will be the second time return + Ok(child_pid as isize) +} + +pub fn check_vfork_for_exec(current_ref: &ThreadRef) -> Option<(ThreadId, Option)> { + let current_pid = current_ref.process().pid(); + if is_vforked_child_process() { + let mut child_pid = 0; + VFORK_CONTEXT.with(|cell| { + let ctx = cell.borrow().unwrap(); + child_pid = ctx.0; + }); + return Some(( + // Reuse tid which was generated when do_vfork + ThreadId { + tid: child_pid as u32, + }, + // By default, use current process as parent + None, + )); + } else { + None + } +} diff --git a/src/libos/src/process/mod.rs b/src/libos/src/process/mod.rs index 3ecbb38c..03971582 100644 --- a/src/libos/src/process/mod.rs +++ b/src/libos/src/process/mod.rs @@ -25,6 +25,7 @@ pub use self::do_exit::handle_force_exit; pub use self::do_futex::{futex_wait, futex_wake}; pub use self::do_robust_list::RobustListHead; pub use self::do_spawn::do_spawn_without_exec; +pub use self::do_vfork::do_vfork; pub use self::do_wait4::idle_reap_zombie_children; pub use self::process::{Process, ProcessFilter, ProcessStatus, IDLE}; pub use self::spawn_attribute::posix_spawnattr_t; @@ -43,6 +44,7 @@ mod do_getpid; mod do_robust_list; mod do_set_tid_address; mod do_spawn; +mod do_vfork; mod do_wait4; mod pgrp; mod prctl; diff --git a/src/libos/src/process/syscalls.rs b/src/libos/src/process/syscalls.rs index ce3fc5df..c00d0994 100644 --- a/src/libos/src/process/syscalls.rs +++ b/src/libos/src/process/syscalls.rs @@ -10,6 +10,7 @@ use super::prctl::PrctlCmd; use super::process::ProcessFilter; use super::spawn_attribute::{clone_spawn_atrributes_safely, posix_spawnattr_t, SpawnAttr}; use crate::prelude::*; +use crate::syscall::CpuContext; use crate::time::{timespec_t, ClockID}; use crate::util::mem_util::from_user::*; use std::ptr::NonNull; @@ -341,10 +342,10 @@ pub fn do_exit(status: i32) -> Result { Ok(0) } -pub fn do_exit_group(status: i32) -> Result { +pub fn do_exit_group(status: i32, user_context: *mut CpuContext) -> Result { debug!("exit_group: {}", status); - super::do_exit::do_exit_group(status); - Ok(0) + let user_context = unsafe { &mut *user_context }; + return super::do_exit::do_exit_group(status, user_context); } pub fn do_wait4(pid: i32, exit_status_ptr: *mut i32, options: u32) -> Result { @@ -469,7 +470,12 @@ pub fn do_getgroups(size: isize, buf_ptr: *mut u32) -> Result { } } -pub fn do_execve(path: *const i8, argv: *const *const i8, envp: *const *const i8) -> Result { +pub fn do_execve( + path: *const i8, + argv: *const *const i8, + envp: *const *const i8, + context: *mut CpuContext, +) -> Result { let path = clone_cstring_safely(path)?.to_string_lossy().into_owned(); let argv = clone_cstrings_safely(argv)?; let envp = clone_cstrings_safely(envp)?; @@ -479,7 +485,7 @@ pub fn do_execve(path: *const i8, argv: *const *const i8, envp: *const *const i8 path, argv, envp ); - do_exec(&path, &argv, &envp, ¤t) + do_exec(&path, &argv, &envp, ¤t, context) } pub fn do_set_robust_list(list_head_ptr: *mut RobustListHead, len: usize) -> Result { diff --git a/src/libos/src/process/task/task.c b/src/libos/src/process/task/task.c index 89ac7097..668b3e27 100644 --- a/src/libos/src/process/task/task.c +++ b/src/libos/src/process/task/task.c @@ -68,4 +68,4 @@ void do_exit_task(void) { struct Task *task = __get_current_task(); jmp_buf *jb = task->saved_state; longjmp(*jb, 1); -} \ No newline at end of file +} diff --git a/src/libos/src/syscall/mod.rs b/src/libos/src/syscall/mod.rs index 28a9c7ee..6af77e2a 100644 --- a/src/libos/src/syscall/mod.rs +++ b/src/libos/src/syscall/mod.rs @@ -45,7 +45,7 @@ use crate::process::{ do_arch_prctl, do_clone, do_execve, do_exit, do_exit_group, do_futex, do_get_robust_list, do_getegid, do_geteuid, do_getgid, do_getgroups, do_getpgid, do_getpgrp, do_getpid, do_getppid, do_gettid, do_getuid, do_prctl, do_set_robust_list, do_set_tid_address, do_setpgid, - do_spawn_for_glibc, do_spawn_for_musl, do_wait4, pid_t, posix_spawnattr_t, FdOp, + do_spawn_for_glibc, do_spawn_for_musl, do_vfork, do_wait4, pid_t, posix_spawnattr_t, FdOp, RobustListHead, SpawnFileActions, ThreadStatus, }; use crate::sched::{do_getcpu, do_sched_getaffinity, do_sched_setaffinity, do_sched_yield}; @@ -146,8 +146,8 @@ macro_rules! process_syscall_table_with_callback { (Getsockopt = 55) => do_getsockopt(fd: c_int, level: c_int, optname: c_int, optval: *mut c_void, optlen: *mut libc::socklen_t), (Clone = 56) => do_clone(flags: u32, stack_addr: usize, ptid: *mut pid_t, ctid: *mut pid_t, new_tls: usize), (Fork = 57) => handle_unsupported(), - (Vfork = 58) => handle_unsupported(), - (Execve = 59) => do_execve(path: *const i8, argv: *const *const i8, envp: *const *const i8), + (Vfork = 58) => do_vfork(context: *mut CpuContext), + (Execve = 59) => do_execve(path: *const i8, argv: *const *const i8, envp: *const *const i8, context: *mut CpuContext), (Exit = 60) => do_exit(exit_status: i32), (Wait4 = 61) => do_wait4(pid: i32, _exit_status: *mut i32, options: u32), (Kill = 62) => do_kill(pid: i32, sig: c_int), @@ -319,7 +319,7 @@ macro_rules! process_syscall_table_with_callback { (ClockGettime = 228) => do_clock_gettime(clockid: clockid_t, ts_u: *mut timespec_t), (ClockGetres = 229) => do_clock_getres(clockid: clockid_t, res_u: *mut timespec_t), (ClockNanosleep = 230) => handle_unsupported(), - (ExitGroup = 231) => do_exit_group(exit_status: i32), + (ExitGroup = 231) => do_exit_group(exit_status: i32, user_context: *mut CpuContext), (EpollWait = 232) => do_epoll_wait(epfd: c_int, events: *mut libc::epoll_event, maxevents: c_int, timeout: c_int), (EpollCtl = 233) => do_epoll_ctl(epfd: c_int, op: c_int, fd: c_int, event: *const libc::epoll_event), (Tgkill = 234) => do_tgkill(pid: i32, tid: pid_t, sig: c_int), @@ -631,6 +631,16 @@ fn do_syscall(user_context: &mut CpuContext) { // need to modify it if syscall_num == SyscallNum::RtSigreturn { syscall.args[0] = user_context as *mut _ as isize; + } else if syscall_num == SyscallNum::Vfork { + syscall.args[0] = user_context as *mut _ as isize; + } else if syscall_num == SyscallNum::Execve { + // syscall.args[0] == path + // syscall.args[1] == argv + // syscall.args[2] == envp + syscall.args[3] = user_context as *mut _ as isize; + } else if syscall_num == SyscallNum::ExitGroup { + // syscall.args[0] == status + syscall.args[1] = user_context as *mut _ as isize; } else if syscall_num == SyscallNum::HandleException { // syscall.args[0] == info // syscall.args[1] == fpregs diff --git a/test/Makefile b/test/Makefile index 6e0429ff..89f10d91 100644 --- a/test/Makefile +++ b/test/Makefile @@ -19,7 +19,7 @@ TESTS ?= env empty hello_world malloc mmap file fs_perms getpid spawn sched pipe truncate readdir mkdir open stat link symlink chmod chown tls pthread system_info resolv_conf rlimit \ server server_epoll unix_socket cout hostfs cpuid rdtsc device sleep exit_group \ ioctl fcntl eventfd emulate_syscall access signal sysinfo prctl rename procfs wait \ - spawn_attribute exec statfs umask pgrp + spawn_attribute exec statfs umask pgrp vfork # Benchmarks: need to be compiled and run by bench-% target BENCHES := spawn_and_exit_latency pipe_throughput unix_socket_throughput diff --git a/test/naughty_child/main.c b/test/naughty_child/main.c index 760d2d37..ddbab170 100644 --- a/test/naughty_child/main.c +++ b/test/naughty_child/main.c @@ -190,6 +190,39 @@ int test_execve_child_thread() { return -1; } +// /bin/naughty_child -t vfork reader_fd writer_fd +// pipe_reader should remain open becuase it is inherited. +// pipe_writer should be closed already before execve naughty_child. +int test_vfork_child() { + int pipe_reader_fd = atoi(g_argv[3]); + int pipe_writer_fd = atoi(g_argv[4]); + char buf[30] = {0}; + struct stat stat_buf; + + int ret = read(pipe_reader_fd, buf, sizeof(buf)); + if (ret < 0) { + THROW_ERROR("[child] read from pipe error"); + } + + // Check pipe reader + if (fstat(pipe_reader_fd, &stat_buf) < 0 ) { + THROW_ERROR("[child] fstat pipe files error"); + } + + if (!S_ISFIFO(stat_buf.st_mode)) { + THROW_ERROR("failed to check the pipe reader st_mode"); + } + + // Check pipe writer which should be closed already + ret = fstat(pipe_writer_fd, &stat_buf); + if (ret >= 0 || errno != EBADF) { + THROW_ERROR("failed to check the pipe writer which should be closed"); + } + + printf("[child] received mesg: %s", buf); + return 0; +} + // ============================================================================ // Test suite // ============================================================================ @@ -205,6 +238,8 @@ int start_test(const char *test_name) { return test_ioctl_fioclex(); } else if (strcmp(test_name, "execve_thread") == 0) { return test_execve_child_thread(); + } else if (strcmp(test_name, "vfork") == 0) { + return test_vfork_child(); } else { fprintf(stderr, "[child] test case not found\n"); return -1; @@ -213,7 +248,8 @@ int start_test(const char *test_name) { void print_usage() { fprintf(stderr, "Usage:\n naughty_child [-t testcase1] [-t testcase2] ...\n\n"); - fprintf(stderr, " Now support testcase: \n"); + fprintf(stderr, + " Now support testcase: \n"); } int main(int argc, char *argv[]) { diff --git a/test/spawn/Makefile b/test/spawn/Makefile index 9e1b6dec..02032dff 100644 --- a/test/spawn/Makefile +++ b/test/spawn/Makefile @@ -1,5 +1,5 @@ include ../test_common.mk -EXTRA_C_FLAGS := +EXTRA_C_FLAGS := -g EXTRA_LINK_FLAGS := BIN_ARGS := diff --git a/test/vfork/Makefile b/test/vfork/Makefile new file mode 100644 index 00000000..02032dff --- /dev/null +++ b/test/vfork/Makefile @@ -0,0 +1,5 @@ +include ../test_common.mk + +EXTRA_C_FLAGS := -g +EXTRA_LINK_FLAGS := +BIN_ARGS := diff --git a/test/vfork/main.c b/test/vfork/main.c new file mode 100644 index 00000000..ad645e67 --- /dev/null +++ b/test/vfork/main.c @@ -0,0 +1,100 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#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); +} + +static test_case_t test_cases[] = { + TEST_CASE(test_vfork_exit), + TEST_CASE(test_multiple_vfork_execve), + TEST_CASE(test_vfork_isolate_file_table), +}; + +int main() { + return test_suite_run(test_cases, ARRAY_SIZE(test_cases)); +}