From 7cf0f1a6c82ba5143fd383b351aba54e2c10182c Mon Sep 17 00:00:00 2001 From: qianxichen233 Date: Sat, 16 Nov 2024 04:57:18 +0000 Subject: [PATCH 1/5] implement waitpid and wait syscall into rawposix --- src/safeposix/cage.rs | 8 ++ src/safeposix/dispatcher.rs | 4 + src/safeposix/syscalls/sys_calls.rs | 125 ++++++++++++++++++++++++++++ src/tests/sys_tests.rs | 49 +++++++++++ 4 files changed, 186 insertions(+) diff --git a/src/safeposix/cage.rs b/src/safeposix/cage.rs index 2243a6ac..6f7a3eac 100644 --- a/src/safeposix/cage.rs +++ b/src/safeposix/cage.rs @@ -14,6 +14,12 @@ pub use super::syscalls::sys_constants::*; pub use crate::interface::CAGE_TABLE; +#[derive(Debug, Clone, Copy)] +pub struct Zombie { + pub cageid: u64, + pub exit_code: i32 +} + #[derive(Debug)] pub struct Cage { pub cageid: u64, @@ -34,6 +40,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 814a2409..268def33 100644 --- a/src/safeposix/dispatcher.rs +++ b/src/safeposix/dispatcher.rs @@ -1097,6 +1097,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 +1138,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 85a7b151..eb5a80eb 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,20 @@ 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 }); + } + } + // Trigger SIGCHLD if !interface::RUSTPOSIX_TESTSUITE.load(interface::RustAtomicOrdering::Relaxed) { // dont trigger SIGCHLD for test suite @@ -271,6 +298,104 @@ impl Cage { status } + pub fn waitpid_syscall(&self, cageid: u64, 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 the 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) { + // 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); + 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) { + // 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 f72b4f1c..5d3bbb1d 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(); + } } From 075d9add274ed8ce20fcbcb10d6f23ba17ea8ecf Mon Sep 17 00:00:00 2001 From: qianxichen233 Date: Sat, 16 Nov 2024 05:39:43 +0000 Subject: [PATCH 2/5] add interface for wait and waitpid --- src/safeposix/dispatcher.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/safeposix/dispatcher.rs b/src/safeposix/dispatcher.rs index 268def33..eec402ec 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 = unsafe { &mut *((start_address + arg1) as *mut i32) }; + + interface::cagetable_getref(cageid) + .wait_syscall(&mut status) + } + + WAITPID_SYSCALL => { + let pid = arg1 as u64; + let mut status = unsafe { &mut *((start_address + arg2) as *mut i32) }; + let options = arg3 as i32; + + interface::cagetable_getref(cageid) + .waitpid_syscall(pid, &mut status, options) + } + _ => -1, // Return -1 for unknown syscalls }; ret From c89e5935a9a603fccbb7b3f493e93d02555ba8ac Mon Sep 17 00:00:00 2001 From: qianxichen233 Date: Sat, 16 Nov 2024 05:52:12 +0000 Subject: [PATCH 3/5] wait: fix negative pid --- src/safeposix/dispatcher.rs | 2 +- src/safeposix/syscalls/sys_calls.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/safeposix/dispatcher.rs b/src/safeposix/dispatcher.rs index eec402ec..6667a7ac 100644 --- a/src/safeposix/dispatcher.rs +++ b/src/safeposix/dispatcher.rs @@ -1015,7 +1015,7 @@ pub fn lind_syscall_api( } WAITPID_SYSCALL => { - let pid = arg1 as u64; + let pid = arg1 as i32; let mut status = unsafe { &mut *((start_address + arg2) as *mut i32) }; let options = arg3 as i32; diff --git a/src/safeposix/syscalls/sys_calls.rs b/src/safeposix/syscalls/sys_calls.rs index eb5a80eb..ac5c8092 100644 --- a/src/safeposix/syscalls/sys_calls.rs +++ b/src/safeposix/syscalls/sys_calls.rs @@ -298,7 +298,7 @@ impl Cage { status } - pub fn waitpid_syscall(&self, cageid: u64, status: &mut i32, options: i32) -> i32 { + 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); @@ -339,7 +339,7 @@ impl Cage { // 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) { + 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 { @@ -348,7 +348,7 @@ impl Cage { // 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); + 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 { @@ -372,7 +372,7 @@ impl Cage { 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) { + 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; From 88fd636210c3b2d7051890f7740b0d9d94c9ce09 Mon Sep 17 00:00:00 2001 From: qianxichen233 Date: Sat, 16 Nov 2024 23:23:12 +0000 Subject: [PATCH 4/5] resolved some comments --- src/interface/types.rs | 4 ++++ src/safeposix/dispatcher.rs | 4 ++-- src/safeposix/syscalls/sys_calls.rs | 12 +++++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/interface/types.rs b/src/interface/types.rs index 40efd4d9..e5b545f2 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/dispatcher.rs b/src/safeposix/dispatcher.rs index 6667a7ac..2f41f445 100644 --- a/src/safeposix/dispatcher.rs +++ b/src/safeposix/dispatcher.rs @@ -1008,7 +1008,7 @@ pub fn lind_syscall_api( } WAIT_SYSCALL => { - let mut status = unsafe { &mut *((start_address + arg1) as *mut i32) }; + let mut status = interface::get_i32_ref(start_address + arg1).unwrap(); interface::cagetable_getref(cageid) .wait_syscall(&mut status) @@ -1016,7 +1016,7 @@ pub fn lind_syscall_api( WAITPID_SYSCALL => { let pid = arg1 as i32; - let mut status = unsafe { &mut *((start_address + arg2) as *mut i32) }; + let mut status = interface::get_i32_ref(start_address + arg2).unwrap(); let options = arg3 as i32; interface::cagetable_getref(cageid) diff --git a/src/safeposix/syscalls/sys_calls.rs b/src/safeposix/syscalls/sys_calls.rs index ac5c8092..f2659924 100644 --- a/src/safeposix/syscalls/sys_calls.rs +++ b/src/safeposix/syscalls/sys_calls.rs @@ -282,6 +282,9 @@ impl Cage { // 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 } } @@ -298,6 +301,13 @@ 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); @@ -311,7 +321,7 @@ impl Cage { // 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 the cageid == 0 + // 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) { From 39d9387f49c773c2a968f515ec484bcb6144bee3 Mon Sep 17 00:00:00 2001 From: Qianxi Chen <53324229+qianxichen233@users.noreply.github.com> Date: Sun, 17 Nov 2024 07:26:37 +0800 Subject: [PATCH 5/5] Update src/safeposix/cage.rs Co-authored-by: Justin Cappos --- src/safeposix/cage.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/safeposix/cage.rs b/src/safeposix/cage.rs index 6f7a3eac..ea49a295 100644 --- a/src/safeposix/cage.rs +++ b/src/safeposix/cage.rs @@ -14,6 +14,7 @@ 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,