Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wait/waitpid implementation #94

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/interface/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
9 changes: 9 additions & 0 deletions src/safeposix/cage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ pub use super::syscalls::sys_constants::*;

pub use crate::interface::CAGE_TABLE;

// Used to implement waitpid
#[derive(Debug, Clone, Copy)]
qianxichen233 marked this conversation as resolved.
Show resolved Hide resolved
pub struct Zombie {
pub cageid: u64,
pub exit_code: i32
}

#[derive(Debug)]
pub struct Cage {
pub cageid: u64,
Expand All @@ -34,6 +41,8 @@ pub struct Cage {
pub pendingsigset: interface::RustHashMap<u64, interface::RustAtomicU64>,
pub main_threadid: interface::RustAtomicU64,
pub interval_timer: interface::IntervalTimer,
pub zombies: interface::RustLock<Vec<Zombie>>,
pub child_num: interface::RustAtomicU64
}

impl Cage {
Expand Down
22 changes: 22 additions & 0 deletions src/safeposix/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
135 changes: 135 additions & 0 deletions src/safeposix/syscalls/sys_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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

Expand All @@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not some we implemented in lind-nacl either but worth adding a BUG tag here since we dont handle the situation where a parent has exited already. I'm not sure if we want to implement zombies being reclaimed by another process.

// 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
Expand All @@ -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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with what Justin said. Might be good to add a comment to explain high-level implementation logic here

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<Zombie> = 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
}
Expand Down
49 changes: 49 additions & 0 deletions src/tests/sys_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Loading