Add vfork support
This commit is contained in:
parent
88f04c8df9
commit
99688183f0
@ -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<isize> {
|
||||
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),
|
||||
)
|
||||
|
@ -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<isize> {
|
||||
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) {
|
||||
|
@ -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<ThreadId>,
|
||||
parent_process: Option<ProcessRef>,
|
||||
) -> Result<ProcessRef> {
|
||||
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<ThreadId>,
|
||||
parent_process: Option<ProcessRef>,
|
||||
) -> Result<ProcessRef> {
|
||||
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::<Vec<&str>>()[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)
|
||||
|
133
src/libos/src/process/do_vfork.rs
Normal file
133
src/libos/src/process/do_vfork.rs
Normal file
@ -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(¤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<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_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;
|
||||
|
@ -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<isize> {
|
||||
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);
|
||||
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<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 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<isize> {
|
||||
|
@ -68,4 +68,4 @@ void do_exit_task(void) {
|
||||
struct Task *task = __get_current_task();
|
||||
jmp_buf *jb = task->saved_state;
|
||||
longjmp(*jb, 1);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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: <sigmask, sigdef, fioclex, execve_thread>\n");
|
||||
fprintf(stderr,
|
||||
" Now support testcase: <sigmask, sigdef, fioclex, execve_thread, vfork>\n");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
include ../test_common.mk
|
||||
|
||||
EXTRA_C_FLAGS :=
|
||||
EXTRA_C_FLAGS := -g
|
||||
EXTRA_LINK_FLAGS :=
|
||||
BIN_ARGS :=
|
||||
|
5
test/vfork/Makefile
Normal file
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
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));
|
||||
}
|
Loading…
Reference in New Issue
Block a user