diff --git a/src/interface/types.rs b/src/interface/types.rs index 40efd4d..e5b545f 100644 --- a/src/interface/types.rs +++ b/src/interface/types.rs @@ -379,6 +379,10 @@ pub fn get_ioctlptrunion<'a>(generic_argument: u64) -> Result<&'a mut u8, i32> { )); } +pub fn get_i32_ref<'a>(generic_argument: u64) -> Result<&'a mut i32, i32> { + unsafe { Ok(&mut *((generic_argument) as *mut i32)) } +} + pub fn get_pipearray<'a>(generic_argument: u64) -> Result<&'a mut PipeArray, i32> { let pointer = generic_argument as *mut PipeArray; if !pointer.is_null() { diff --git a/src/safeposix/cage.rs b/src/safeposix/cage.rs index 2243a6a..ea49a29 100644 --- a/src/safeposix/cage.rs +++ b/src/safeposix/cage.rs @@ -14,6 +14,13 @@ pub use super::syscalls::sys_constants::*; pub use crate::interface::CAGE_TABLE; +// Used to implement waitpid +#[derive(Debug, Clone, Copy)] +pub struct Zombie { + pub cageid: u64, + pub exit_code: i32 +} + #[derive(Debug)] pub struct Cage { pub cageid: u64, @@ -34,6 +41,8 @@ pub struct Cage { pub pendingsigset: interface::RustHashMap, pub main_threadid: interface::RustAtomicU64, pub interval_timer: interface::IntervalTimer, + pub zombies: interface::RustLock>, + pub child_num: interface::RustAtomicU64 } impl Cage { diff --git a/src/safeposix/dispatcher.rs b/src/safeposix/dispatcher.rs index 814a240..2f41f44 100644 --- a/src/safeposix/dispatcher.rs +++ b/src/safeposix/dispatcher.rs @@ -114,6 +114,8 @@ const SYNC_FILE_RANGE: i32 = 164; const WRITEV_SYSCALL: i32 = 170; const CLONE_SYSCALL: i32 = 171; +const WAIT_SYSCALL: i32 = 172; +const WAITPID_SYSCALL: i32 = 173; const NANOSLEEP_TIME64_SYSCALL : i32 = 181; @@ -1005,6 +1007,22 @@ pub fn lind_syscall_api( .nanosleep_time64_syscall(clockid, flags, req, rem) } + WAIT_SYSCALL => { + let mut status = interface::get_i32_ref(start_address + arg1).unwrap(); + + interface::cagetable_getref(cageid) + .wait_syscall(&mut status) + } + + WAITPID_SYSCALL => { + let pid = arg1 as i32; + let mut status = interface::get_i32_ref(start_address + arg2).unwrap(); + let options = arg3 as i32; + + interface::cagetable_getref(cageid) + .waitpid_syscall(pid, &mut status, options) + } + _ => -1, // Return -1 for unknown syscalls }; ret @@ -1097,6 +1115,8 @@ pub fn lindrustinit(verbosity: isize) { pendingsigset: interface::RustHashMap::new(), main_threadid: interface::RustAtomicU64::new(0), interval_timer: interface::IntervalTimer::new(0), + zombies: interface::RustLock::new(vec![]), + child_num: interface::RustAtomicU64::new(0), }; interface::cagetable_insert(0, utilcage); @@ -1136,6 +1156,8 @@ pub fn lindrustinit(verbosity: isize) { pendingsigset: interface::RustHashMap::new(), main_threadid: interface::RustAtomicU64::new(0), interval_timer: interface::IntervalTimer::new(1), + zombies: interface::RustLock::new(vec![]), + child_num: interface::RustAtomicU64::new(0), }; interface::cagetable_insert(1, initcage); fdtables::init_empty_cage(1); diff --git a/src/safeposix/syscalls/sys_calls.rs b/src/safeposix/syscalls/sys_calls.rs index 85a7b15..f265992 100644 --- a/src/safeposix/syscalls/sys_calls.rs +++ b/src/safeposix/syscalls/sys_calls.rs @@ -169,8 +169,13 @@ impl Cage { pendingsigset: interface::RustHashMap::new(), main_threadid: interface::RustAtomicU64::new(0), interval_timer: interface::IntervalTimer::new(child_cageid), + zombies: interface::RustLock::new(vec![]), + child_num: interface::RustAtomicU64::new(0), }; + // increment child counter for parent + self.child_num.fetch_add(1, interface::RustAtomicOrdering::SeqCst); + let shmtable = &SHM_METADATA.shmtable; //update fields for shared mappings in cage for rev_mapping in cageobj.rev_shm.lock().iter() { @@ -202,6 +207,11 @@ impl Cage { self.unmap_shm_mappings(); + let zombies = self.zombies.read(); + let cloned_zombies = zombies.clone(); + let child_num = self.child_num.load(interface::RustAtomicOrdering::Relaxed); + drop(zombies); + // we grab the parent cages main threads sigset and store it at 0 // this way the child can initialize the sigset properly when it establishes its own mainthreadid let newsigset = interface::RustHashMap::new(); @@ -241,6 +251,9 @@ impl Cage { pendingsigset: interface::RustHashMap::new(), main_threadid: interface::RustAtomicU64::new(0), interval_timer: self.interval_timer.clone_with_new_cageid(child_cageid), + // when a process exec-ed, its child relationship should be perserved + zombies: interface::RustLock::new(cloned_zombies), + child_num: interface::RustAtomicU64::new(child_num), }; //wasteful clone of fdtable, but mutability constraints exist @@ -258,6 +271,23 @@ impl Cage { //may not be removable in case of lindrustfinalize, we don't unwrap the remove result interface::cagetable_remove(self.cageid); + // if the cage has parent + if self.parent != self.cageid { + let parent_cage = interface::cagetable_getref_opt(self.parent); + // if parent hasn't exited yet + if let Some(parent) = parent_cage { + // decrement parent's child counter + parent.child_num.fetch_sub(1, interface::RustAtomicOrdering::SeqCst); + + // push the exit status to parent's zombie list + let mut zombie_vec = parent.zombies.write(); + zombie_vec.push(Zombie { cageid: self.cageid, exit_code: status }); + } else { + // if parent already exited + // BUG: we currently do not handle the situation where a parent has exited already + } + } + // Trigger SIGCHLD if !interface::RUSTPOSIX_TESTSUITE.load(interface::RustAtomicOrdering::Relaxed) { // dont trigger SIGCHLD for test suite @@ -271,6 +301,111 @@ impl Cage { status } + //------------------------------------WAITPID SYSCALL------------------------------------ + /* + * waitpid() will return the cageid of waited cage, or 0 when WNOHANG is set and there is no cage already exited + * waitpid_syscall utilizes the zombie list stored in cage struct. When a cage exited, a zombie entry will be inserted + * into the end of its parent's zombie list. Then when parent wants to wait for any of child, it could just check its + * zombie list and retrieve the first entry from it (first in, first out). + */ + pub fn waitpid_syscall(&self, cageid: i32, status: &mut i32, options: i32) -> i32 { + let mut zombies = self.zombies.write(); + let child_num = self.child_num.load(interface::RustAtomicOrdering::Relaxed); + + // if there is no pending zombies to wait, and there is no active child, return ECHILD + if zombies.len() == 0 && child_num == 0 { + return syscall_error(Errno::ECHILD, "waitpid", "no existing unwaited-for child processes"); + } + + let mut zombie_opt: Option = None; + + // cageid <= 0 means wait for ANY child + // cageid < 0 actually refers to wait for any child process whose process group ID equals -pid + // but we do not have the concept of process group in lind, so let's just treat it as cageid == 0 + if cageid <= 0 { + loop { + if zombies.len() == 0 && (options & libc::WNOHANG > 0) { + // if there is no pending zombies and WNOHANG is set + // return immediately + return 0; + } else if zombies.len() == 0 { + // if there is no pending zombies and WNOHANG is not set + // then we need to wait for children to exit + // drop the zombies list before sleep to avoid deadlock + drop(zombies); + // TODO: replace busy waiting with more efficient mechanism + interface::lind_yield(); + // after sleep, get the write access of zombies list back + zombies = self.zombies.write(); + continue; + } else { + // there are zombies avaliable + // let's retrieve the first zombie + zombie_opt = Some(zombies.remove(0)); + break; + } + } + } + // if cageid is specified, then we need to look up the zombie list for the id + else { + // first let's check if the cageid is in the zombie list + if let Some(index) = zombies.iter().position(|zombie| zombie.cageid == cageid as u64) { + // find the cage in zombie list, remove it from the list and break + zombie_opt = Some(zombies.remove(index)); + } else { + // if the cageid is not in the zombie list, then we know either + // 1. the child is still running, or + // 2. the cage has exited, but it is not the child of this cage, or + // 3. the cage does not exist + // we need to make sure the child is still running, and it is the child of this cage + let child = interface::cagetable_getref_opt(cageid as u64); + if let Some(child_cage) = child { + // make sure the child's parent is correct + if child_cage.parent != self.cageid { + return syscall_error(Errno::ECHILD, "waitpid", "waited cage is not the child of the cage"); + } + } else { + // cage does not exist + return syscall_error(Errno::ECHILD, "waitpid", "cage does not exist"); + } + + // now we have verified that the cage exists and is the child of the cage + loop { + // the cage is not in the zombie list + // we need to wait for the cage to actually exit + + // drop the zombies list before sleep to avoid deadlock + drop(zombies); + // TODO: replace busy waiting with more efficient mechanism + interface::lind_yield(); + // after sleep, get the write access of zombies list back + zombies = self.zombies.write(); + + // let's check if the zombie list contains the cage + if let Some(index) = zombies.iter().position(|zombie| zombie.cageid == cageid as u64) { + // find the cage in zombie list, remove it from the list and break + zombie_opt = Some(zombies.remove(index)); + break; + } + + continue; + } + } + } + + // reach here means we already found the desired exited child + let zombie = zombie_opt.unwrap(); + // update the status + *status = zombie.exit_code; + + // return child's cageid + zombie.cageid as i32 + } + + pub fn wait_syscall(&self, status: &mut i32) -> i32 { + self.waitpid_syscall(0, status, 0) + } + pub fn getpid_syscall(&self) -> i32 { self.cageid as i32 //not sure if this is quite what we want but it's easy enough to change later } diff --git a/src/tests/sys_tests.rs b/src/tests/sys_tests.rs index f72b4f1..5d3bbb1 100644 --- a/src/tests/sys_tests.rs +++ b/src/tests/sys_tests.rs @@ -135,4 +135,53 @@ pub mod sys_tests { lindrustfinalize(); } + + #[test] + pub fn ut_lind_waitpid() { + // acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + let cage = interface::cagetable_getref(1); + + // first let's fork some children + cage.fork_syscall(2); + cage.fork_syscall(3); + cage.fork_syscall(4); + cage.fork_syscall(5); + + let child_cage2 = interface::cagetable_getref(2); + let child_cage3 = interface::cagetable_getref(3); + let child_cage4 = interface::cagetable_getref(4); + + // cage2 exit before parent wait + child_cage2.exit_syscall(123); + + let mut status = 0; + let pid = cage.waitpid_syscall(2, &mut status, 0); + assert_eq!(pid, 2); + assert_eq!(status, 123); + + // test for WNOHANG option + let pid = cage.waitpid_syscall(0, &mut status, libc::WNOHANG); + assert_eq!(pid, 0); + + // test for waitpid when the cage exits in the middle of waiting + let thread1 = interface::helper_thread(move || { + interface::sleep(interface::RustDuration::from_millis(100)); + child_cage4.exit_syscall(4); + child_cage3.exit_syscall(3); + }); + + let pid = cage.waitpid_syscall(0, &mut status, 0); + assert_eq!(pid, 4); + assert_eq!(status, 4); + + let pid = cage.waitpid_syscall(0, &mut status, 0); + assert_eq!(pid, 3); + assert_eq!(status, 3); + + let _ = thread1.join().unwrap(); + + lindrustfinalize(); + } }