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_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(¤t_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(¤t_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 _, ¤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) { | 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) | ||||||
|  | |||||||
							
								
								
									
										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_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, ¤t) |     do_exec(&path, &argv, &envp, ¤t, 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
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										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