diff --git a/src/libos/src/process/do_wait4.rs b/src/libos/src/process/do_wait4.rs index c33f1736..7443c643 100644 --- a/src/libos/src/process/do_wait4.rs +++ b/src/libos/src/process/do_wait4.rs @@ -3,7 +3,7 @@ use super::wait::Waiter; use super::{table, ProcessRef, ProcessStatus}; use crate::prelude::*; -pub fn do_wait4(child_filter: &ProcessFilter) -> Result<(pid_t, i32)> { +pub fn do_wait4(child_filter: &ProcessFilter, options: WaitOptions) -> Result<(pid_t, i32)> { // Lock the process early to ensure that we do not miss any changes in // children processes let thread = current!(); @@ -36,6 +36,18 @@ pub fn do_wait4(child_filter: &ProcessFilter) -> Result<(pid_t, i32)> { return Ok((zombie_pid, exit_status)); } + // TODO: Support these options + if !options.supported() { + warn!("Unsupported options contained. wait options: {:?}", options); + } + + // If the WNOHANG bit is set in OPTIONS, and that child + // is not already dead, return (pid_t) 0. If successful, + // return PID and store the dead child's status in STAT_LOC. + if options.contains(WaitOptions::WNOHANG) { + return Ok((0, 0)); + } + let mut waiter = Waiter::new(child_filter); process_inner .waiting_children_mut() @@ -63,3 +75,26 @@ fn free_zombie_child(mut parent_inner: SgxMutexGuard, zombie_pid: let zombie_inner = zombie.inner(); zombie_inner.term_status().unwrap().as_u32() as i32 } + +// Based on waitflags.h +// WNOWAIT is not listed here which can only be used in "waitid" syscall. +bitflags! { + pub struct WaitOptions: u32 { + const WNOHANG = 0x1; + //Note: Below flags are not supported yet + const WSTOPPED = 0x2; // Same as WUNTRACED + const WEXITED = 0x4; + const WCONTINUED = 0x8; + } +} + +impl WaitOptions { + fn supported(&self) -> bool { + let unsupported_flags = WaitOptions::all() - WaitOptions::WNOHANG; + !self.intersects(unsupported_flags) + } +} + +// Based on waitstatus.h +const WAIT_STATUS_STOPPED: i32 = 0x7f; +const WAIT_STATUS_CONTINUED: i32 = 0xffff; diff --git a/src/libos/src/process/syscalls.rs b/src/libos/src/process/syscalls.rs index 12bb85c9..7142f092 100644 --- a/src/libos/src/process/syscalls.rs +++ b/src/libos/src/process/syscalls.rs @@ -2,6 +2,7 @@ use super::do_arch_prctl::ArchPrctlCode; use super::do_clone::CloneFlags; use super::do_futex::{FutexFlags, FutexOp, FutexTimeout}; use super::do_spawn::FileAction; +use super::do_wait4::WaitOptions; use super::prctl::PrctlCmd; use super::process::ProcessFilter; use crate::prelude::*; @@ -333,7 +334,7 @@ pub fn do_exit_group(status: i32) -> Result { Ok(0) } -pub fn do_wait4(pid: i32, exit_status_ptr: *mut i32) -> Result { +pub fn do_wait4(pid: i32, exit_status_ptr: *mut i32, options: u32) -> Result { if !exit_status_ptr.is_null() { check_mut_ptr(exit_status_ptr)?; } @@ -348,8 +349,11 @@ pub fn do_wait4(pid: i32, exit_status_ptr: *mut i32) -> Result { pid if pid > 0 => ProcessFilter::WithPid(pid as pid_t), _ => unreachable!(), }; + + let wait_options = + WaitOptions::from_bits(options).ok_or_else(|| errno!(EINVAL, "options not recognized"))?; let mut exit_status = 0; - match super::do_wait4::do_wait4(&child_process_filter) { + match super::do_wait4::do_wait4(&child_process_filter, wait_options) { Ok((pid, exit_status)) => { if !exit_status_ptr.is_null() { unsafe { diff --git a/src/libos/src/syscall/mod.rs b/src/libos/src/syscall/mod.rs index 745e51d2..0dcb8708 100644 --- a/src/libos/src/syscall/mod.rs +++ b/src/libos/src/syscall/mod.rs @@ -145,7 +145,7 @@ macro_rules! process_syscall_table_with_callback { (Vfork = 58) => handle_unsupported(), (Execve = 59) => handle_unsupported(), (Exit = 60) => do_exit(exit_status: i32), - (Wait4 = 61) => do_wait4(pid: i32, _exit_status: *mut i32), + (Wait4 = 61) => do_wait4(pid: i32, _exit_status: *mut i32, options: u32), (Kill = 62) => do_kill(pid: i32, sig: c_int), (Uname = 63) => do_uname(name: *mut utsname_t), (Semget = 64) => handle_unsupported(), diff --git a/test/Makefile b/test/Makefile index d0574f9c..04c0ed6d 100644 --- a/test/Makefile +++ b/test/Makefile @@ -18,7 +18,7 @@ TEST_DEPS := client data_sink TESTS ?= env empty hello_world malloc mmap file fs_perms getpid spawn sched pipe time \ truncate readdir mkdir open stat link symlink chmod chown tls pthread uname 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 + ioctl fcntl eventfd emulate_syscall access signal sysinfo prctl rename procfs wait # Benchmarks: need to be compiled and run by bench-% target BENCHES := spawn_and_exit_latency pipe_throughput unix_socket_throughput diff --git a/test/wait/Makefile b/test/wait/Makefile new file mode 100644 index 00000000..2f51ceda --- /dev/null +++ b/test/wait/Makefile @@ -0,0 +1,5 @@ +include ../test_common.mk + +EXTRA_C_FLAGS := -Wno-unused-function +EXTRA_LINK_FLAGS := +BIN_ARGS := diff --git a/test/wait/main.c b/test/wait/main.c new file mode 100644 index 00000000..7435ff79 --- /dev/null +++ b/test/wait/main.c @@ -0,0 +1,103 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include "test.h" + +static int test_wait_no_children() { + int status = 0; + int ret = wait(&status); + if (ret != -1 || errno != ECHILD) { + THROW_ERROR("wait no children error"); + } + return 0; +} + +static int test_wait_nohang() { + int status = 0; + int ret = waitpid(-1, &status, WNOHANG); + if (ret != -1 || errno != ECHILD) { + THROW_ERROR("wait no children with NOHANG error"); + } + + int child_pid = 0; + // /bin/sleep lasts more than 1 sec + if (posix_spawn(&child_pid, "/bin/sleep", NULL, NULL, NULL, NULL) < 0) { + THROW_ERROR("posix_spawn child error"); + } + + ret = waitpid(child_pid, &status, WNOHANG); + if (ret != 0) { + THROW_ERROR("wait child with NOHANG error"); + } + + sleep(2); + // The child process should exit + ret = waitpid(child_pid, &status, WNOHANG); + if (ret != child_pid) { + THROW_ERROR("wait child with NOHANG error"); + } + return 0; +} + +// NOTE: WUNTRACED is same as WSTOPPED +// TODO: Support WUNTRACED and WCONTINUED and enable this test case +static int test_wait_untraced_and_continued() { + int status = 0; + int ret = waitpid(-1, &status, WNOHANG); + if (ret != -1 || errno != ECHILD) { + THROW_ERROR("wait no children with NOHANG error"); + } + + int child_pid = 0; + if (posix_spawn(&child_pid, "/bin/sleep", NULL, NULL, NULL, NULL) < 0) { + THROW_ERROR("posix_spawn child error"); + } + + ret = waitpid(child_pid, &status, WNOHANG); + if (ret != 0) { + THROW_ERROR("wait child with NOHANG error"); + } + + kill(child_pid, SIGSTOP); + // WUNTRACED will get child_pid status + ret = waitpid(child_pid, &status, WUNTRACED); + printf("ret = %d, status = %d\n", ret, status); + if (ret != child_pid || !WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP ) { + THROW_ERROR("wait child status error"); + } + + // Let child get back to running by sending SIGCONT + kill(child_pid, SIGCONT); + ret = waitpid(child_pid, &status, WCONTINUED); + printf("ret = %d, status = %d\n", ret, status); + if (ret != child_pid || !WIFCONTINUED(status)) { + THROW_ERROR("wait child status error"); + } + + sleep(2); + // The child process should exit + ret = waitpid(child_pid, &status, WNOHANG | WUNTRACED); + printf("ret = %d, status = %d\n", ret, status); + if (ret != child_pid || !WIFEXITED(status) ) { + THROW_ERROR("wait child with NOHANG error"); + } + return 0; +} + +// ============================================================================ +// Test suite main +// ============================================================================ + +static test_case_t test_cases[] = { + TEST_CASE(test_wait_no_children), + TEST_CASE(test_wait_nohang), + // TODO: Enable this test case + // TEST_CASE(test_wait_untraced_and_continued), +}; + +int main(int argc, const char *argv[]) { + return test_suite_run(test_cases, ARRAY_SIZE(test_cases)); +}