Add vfork support

This commit is contained in:
Hui, Chunyang 2021-07-06 03:51:06 +00:00 committed by Zongmin.Gu
parent 88f04c8df9
commit 99688183f0
13 changed files with 376 additions and 33 deletions

@ -3,14 +3,16 @@ use std::path::Path;
use super::do_exit::exit_old_process_for_execve; use super::do_exit::exit_old_process_for_execve;
use super::do_spawn::new_process_for_exec; 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::process::ProcessFilter;
use super::term_status::TermStatus; use super::term_status::TermStatus;
use super::wait::Waiter; use super::wait::Waiter;
use super::{do_exit, do_exit_group}; use super::{do_exit, do_exit_group};
use super::{table, ProcessRef, ProcessStatus}; use super::{table, ProcessRef, ProcessStatus};
use super::{task, ThreadRef}; use super::{task, ThreadId, ThreadRef};
use crate::interrupt::broadcast_interrupts; use crate::interrupt::broadcast_interrupts;
use crate::prelude::*; use crate::prelude::*;
use crate::syscall::CpuContext;
// FIXME: `occlum exec` command will return early if the application calls execve successfully. // 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. // 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], argv: &[CString],
envp: &[CString], envp: &[CString],
current_ref: &ThreadRef, current_ref: &ThreadRef,
context: *mut CpuContext,
) -> Result<isize> { ) -> Result<isize> {
trace!( trace!(
"exec current process pid = {:?}", "exec current process pid = {:?}",
current_ref.process().pid() current_ref.process().pid()
); );
// Construct new process structure but with same parent, pid, tid let (is_vforked, tid, parent_process) =
let current = current!(); if let Some((tid, parent_process)) = check_vfork_for_exec(current_ref) {
let new_process_ref = super::do_spawn::new_process_for_exec(path, argv, envp, 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 { if let Ok(new_process_ref) = new_process_ref {
let new_main_thread = new_process_ref let new_main_thread = new_process_ref
.main_thread() .main_thread()
.expect("the main thread is just created; it must exist"); .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 // Force exit all child threads of current process
let term_status = TermStatus::Exited(0 as u8); 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). // Don't hesitate. Interrupt all threads right now (except the calling thread).
broadcast_interrupts(); broadcast_interrupts();
// Wait for all threads (except calling thread) to exit // 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 current thread and let new process to adopt current's child process
exit_old_process_for_execve(term_status, new_process_ref.clone()); 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 // 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 super::do_futex::{self, FutexTimeout};
use crate::time::{timespec_t, ClockID}; use crate::time::{timespec_t, ClockID};
use core::time::Duration; 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)), timespec_t::from(Duration::from_millis(50)),
); );
// Use calling process's pointer as futex value // Use calling process's pointer as futex value
let futex_val = Arc::as_ptr(&current_ref.process()) as *const i32; let futex_val = Arc::as_ptr(current_ref.process()) as *const i32;
loop { loop {
let thread_num = current_ref.process().threads().len(); let thread_num = current_ref.process().threads().len();
if thread_num == 1 { 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. // Blocking wait here. When a thread exit, it will notify us.
unsafe { unsafe {
do_futex::futex_wait( do_futex::futex_wait(
Arc::as_ptr(&current_ref.process()) as *const i32, Arc::as_ptr(current_ref.process()) as *const i32,
*futex_val, *futex_val,
&Some(timeout), &Some(timeout),
) )

@ -2,16 +2,24 @@ use crate::signal::constants::*;
use std::intrinsics::atomic_store; use std::intrinsics::atomic_store;
use super::do_futex::futex_wake; 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::pgrp::clean_pgrp_when_exit;
use super::process::{Process, ProcessFilter}; use super::process::{Process, ProcessFilter};
use super::{table, ProcessRef, TermStatus, ThreadRef, ThreadStatus}; use super::{table, ProcessRef, TermStatus, ThreadRef, ThreadStatus};
use crate::prelude::*; use crate::prelude::*;
use crate::signal::{KernelSignal, SigNum}; use crate::signal::{KernelSignal, SigNum};
use crate::syscall::CpuContext;
pub fn do_exit_group(status: i32) { pub fn do_exit_group(status: i32, curr_user_ctxt: &mut CpuContext) -> Result<isize> {
let term_status = TermStatus::Exited(status as u8); if is_vforked_child_process() {
current!().process().force_exit(term_status); let current = current!();
exit_thread(term_status); return vfork_return_to_parent(curr_user_ctxt as *mut _, &current);
} 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) { pub fn do_exit(status: i32) {

@ -118,6 +118,7 @@ fn new_process(
host_stdio_fds, host_stdio_fds,
current_ref, current_ref,
None, None,
None,
)?; )?;
table::add_process(new_process_ref.clone()); table::add_process(new_process_ref.clone());
table::add_thread(new_process_ref.main_thread().unwrap()); table::add_thread(new_process_ref.main_thread().unwrap());
@ -131,6 +132,8 @@ pub fn new_process_for_exec(
argv: &[CString], argv: &[CString],
envp: &[CString], envp: &[CString],
current_ref: &ThreadRef, current_ref: &ThreadRef,
reuse_tid: Option<ThreadId>,
parent_process: Option<ProcessRef>,
) -> Result<ProcessRef> { ) -> Result<ProcessRef> {
let tid = ThreadId { let tid = ThreadId {
tid: current_ref.process().pid() as u32, tid: current_ref.process().pid() as u32,
@ -143,7 +146,8 @@ pub fn new_process_for_exec(
None, None,
None, None,
current_ref, current_ref,
Some(tid), reuse_tid,
parent_process,
)?; )?;
Ok(new_process_ref) Ok(new_process_ref)
@ -158,6 +162,7 @@ fn new_process_common(
host_stdio_fds: Option<&HostStdioFds>, host_stdio_fds: Option<&HostStdioFds>,
current_ref: &ThreadRef, current_ref: &ThreadRef,
reuse_tid: Option<ThreadId>, reuse_tid: Option<ThreadId>,
parent_process: Option<ProcessRef>,
) -> Result<ProcessRef> { ) -> Result<ProcessRef> {
let mut argv = argv.clone().to_vec(); let mut argv = argv.clone().to_vec();
let (is_script, elf_inode, mut elf_buf, elf_header) = 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::<Vec<&str>>()[0]; let elf_name = elf_path.rsplit('/').collect::<Vec<&str>>()[0];
let thread_name = ThreadName::new(elf_name); let thread_name = ThreadName::new(elf_name);
let mut parent;
let mut process_builder = ProcessBuilder::new(); let mut process_builder = ProcessBuilder::new();
if reuse_tid.is_some() {
process_builder = process_builder.tid(reuse_tid.unwrap()); // Use specified tid if any
parent = current!().process().parent(); if let Some(reuse_tid) = reuse_tid {
} else { process_builder = process_builder.tid(reuse_tid);
parent = process_ref;
} }
// Use specified parent process if any
let parent = if let Some(parent) = parent_process {
parent
} else {
process_ref
};
let new_process = process_builder let new_process = process_builder
.vm(vm_ref) .vm(vm_ref)
.exec_path(&elf_path) .exec_path(&elf_path)

@ -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<HashMap<pid_t, FileTable>> = 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<Option<(pid_t, CpuContext)>> = Default::default();
}
pub fn do_vfork(mut context: *mut CpuContext) -> Result<isize> {
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<isize> {
return restore_parent_process(context, current_ref);
}
fn restore_parent_process(mut context: *mut CpuContext, current_ref: &ThreadRef) -> Result<isize> {
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(&current_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<ProcessRef>)> {
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
}
}

@ -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_futex::{futex_wait, futex_wake};
pub use self::do_robust_list::RobustListHead; pub use self::do_robust_list::RobustListHead;
pub use self::do_spawn::do_spawn_without_exec; 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::do_wait4::idle_reap_zombie_children;
pub use self::process::{Process, ProcessFilter, ProcessStatus, IDLE}; pub use self::process::{Process, ProcessFilter, ProcessStatus, IDLE};
pub use self::spawn_attribute::posix_spawnattr_t; pub use self::spawn_attribute::posix_spawnattr_t;
@ -43,6 +44,7 @@ mod do_getpid;
mod do_robust_list; mod do_robust_list;
mod do_set_tid_address; mod do_set_tid_address;
mod do_spawn; mod do_spawn;
mod do_vfork;
mod do_wait4; mod do_wait4;
mod pgrp; mod pgrp;
mod prctl; mod prctl;

@ -10,6 +10,7 @@ use super::prctl::PrctlCmd;
use super::process::ProcessFilter; use super::process::ProcessFilter;
use super::spawn_attribute::{clone_spawn_atrributes_safely, posix_spawnattr_t, SpawnAttr}; use super::spawn_attribute::{clone_spawn_atrributes_safely, posix_spawnattr_t, SpawnAttr};
use crate::prelude::*; use crate::prelude::*;
use crate::syscall::CpuContext;
use crate::time::{timespec_t, ClockID}; use crate::time::{timespec_t, ClockID};
use crate::util::mem_util::from_user::*; use crate::util::mem_util::from_user::*;
use std::ptr::NonNull; use std::ptr::NonNull;
@ -341,10 +342,10 @@ pub fn do_exit(status: i32) -> Result<isize> {
Ok(0) Ok(0)
} }
pub fn do_exit_group(status: i32) -> Result<isize> { pub fn do_exit_group(status: i32, user_context: *mut CpuContext) -> Result<isize> {
debug!("exit_group: {}", status); debug!("exit_group: {}", status);
super::do_exit::do_exit_group(status); let user_context = unsafe { &mut *user_context };
Ok(0) return super::do_exit::do_exit_group(status, user_context);
} }
pub fn do_wait4(pid: i32, exit_status_ptr: *mut i32, options: u32) -> Result<isize> { pub fn do_wait4(pid: i32, exit_status_ptr: *mut i32, options: u32) -> Result<isize> {
@ -469,7 +470,12 @@ pub fn do_getgroups(size: isize, buf_ptr: *mut u32) -> Result<isize> {
} }
} }
pub fn do_execve(path: *const i8, argv: *const *const i8, envp: *const *const i8) -> Result<isize> { pub fn do_execve(
path: *const i8,
argv: *const *const i8,
envp: *const *const i8,
context: *mut CpuContext,
) -> Result<isize> {
let path = clone_cstring_safely(path)?.to_string_lossy().into_owned(); let path = clone_cstring_safely(path)?.to_string_lossy().into_owned();
let argv = clone_cstrings_safely(argv)?; let argv = clone_cstrings_safely(argv)?;
let envp = clone_cstrings_safely(envp)?; 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 path, argv, envp
); );
do_exec(&path, &argv, &envp, &current) do_exec(&path, &argv, &envp, &current, context)
} }
pub fn do_set_robust_list(list_head_ptr: *mut RobustListHead, len: usize) -> Result<isize> { pub fn do_set_robust_list(list_head_ptr: *mut RobustListHead, len: usize) -> Result<isize> {

@ -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_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_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_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, RobustListHead, SpawnFileActions, ThreadStatus,
}; };
use crate::sched::{do_getcpu, do_sched_getaffinity, do_sched_setaffinity, do_sched_yield}; 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), (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), (Clone = 56) => do_clone(flags: u32, stack_addr: usize, ptid: *mut pid_t, ctid: *mut pid_t, new_tls: usize),
(Fork = 57) => handle_unsupported(), (Fork = 57) => handle_unsupported(),
(Vfork = 58) => handle_unsupported(), (Vfork = 58) => do_vfork(context: *mut CpuContext),
(Execve = 59) => do_execve(path: *const i8, argv: *const *const i8, envp: *const *const i8), (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), (Exit = 60) => do_exit(exit_status: i32),
(Wait4 = 61) => do_wait4(pid: i32, _exit_status: *mut i32, options: u32), (Wait4 = 61) => do_wait4(pid: i32, _exit_status: *mut i32, options: u32),
(Kill = 62) => do_kill(pid: i32, sig: c_int), (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), (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), (ClockGetres = 229) => do_clock_getres(clockid: clockid_t, res_u: *mut timespec_t),
(ClockNanosleep = 230) => handle_unsupported(), (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), (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), (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), (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 // need to modify it
if syscall_num == SyscallNum::RtSigreturn { if syscall_num == SyscallNum::RtSigreturn {
syscall.args[0] = user_context as *mut _ as isize; 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 { } else if syscall_num == SyscallNum::HandleException {
// syscall.args[0] == info // syscall.args[0] == info
// syscall.args[1] == fpregs // syscall.args[1] == fpregs

@ -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 \ 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 \ 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 \ 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 # Benchmarks: need to be compiled and run by bench-% target
BENCHES := spawn_and_exit_latency pipe_throughput unix_socket_throughput BENCHES := spawn_and_exit_latency pipe_throughput unix_socket_throughput

@ -190,6 +190,39 @@ int test_execve_child_thread() {
return -1; 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 // Test suite
// ============================================================================ // ============================================================================
@ -205,6 +238,8 @@ int start_test(const char *test_name) {
return test_ioctl_fioclex(); return test_ioctl_fioclex();
} else if (strcmp(test_name, "execve_thread") == 0) { } else if (strcmp(test_name, "execve_thread") == 0) {
return test_execve_child_thread(); return test_execve_child_thread();
} else if (strcmp(test_name, "vfork") == 0) {
return test_vfork_child();
} else { } else {
fprintf(stderr, "[child] test case not found\n"); fprintf(stderr, "[child] test case not found\n");
return -1; return -1;
@ -213,7 +248,8 @@ int start_test(const char *test_name) {
void print_usage() { void print_usage() {
fprintf(stderr, "Usage:\n naughty_child [-t testcase1] [-t testcase2] ...\n\n"); fprintf(stderr, "Usage:\n naughty_child [-t testcase1] [-t testcase2] ...\n\n");
fprintf(stderr, " Now support testcase: <sigmask, sigdef, fioclex, execve_thread>\n"); fprintf(stderr,
" Now support testcase: <sigmask, sigdef, fioclex, execve_thread, vfork>\n");
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {

@ -1,5 +1,5 @@
include ../test_common.mk include ../test_common.mk
EXTRA_C_FLAGS := EXTRA_C_FLAGS := -g
EXTRA_LINK_FLAGS := EXTRA_LINK_FLAGS :=
BIN_ARGS := BIN_ARGS :=

5
test/vfork/Makefile Normal file

@ -0,0 +1,5 @@
include ../test_common.mk
EXTRA_C_FLAGS := -g
EXTRA_LINK_FLAGS :=
BIN_ARGS :=

100
test/vfork/main.c Normal file

@ -0,0 +1,100 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.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);
}
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));
}