| use libc::_exit; |
| use nix::errno::Errno; |
| use nix::sys::signal::*; |
| use nix::sys::wait::*; |
| use nix::unistd::ForkResult::*; |
| use nix::unistd::*; |
| |
| #[test] |
| #[cfg(not(any(target_os = "redox", target_os = "haiku")))] |
| fn test_wait_signal() { |
| let _m = crate::FORK_MTX.lock(); |
| |
| // Safe: The child only calls `pause` and/or `_exit`, which are async-signal-safe. |
| match unsafe { fork() }.expect("Error: Fork Failed") { |
| Child => { |
| pause(); |
| unsafe { _exit(123) } |
| } |
| Parent { child } => { |
| kill(child, Some(SIGKILL)).expect("Error: Kill Failed"); |
| assert_eq!( |
| waitpid(child, None), |
| Ok(WaitStatus::Signaled(child, SIGKILL, false)) |
| ); |
| } |
| } |
| } |
| |
| #[test] |
| #[cfg(any( |
| target_os = "android", |
| target_os = "freebsd", |
| //target_os = "haiku", |
| all(target_os = "linux", not(target_env = "uclibc")), |
| ))] |
| #[cfg(not(any(target_arch = "mips", target_arch = "mips64")))] |
| fn test_waitid_signal() { |
| let _m = crate::FORK_MTX.lock(); |
| |
| // Safe: The child only calls `pause` and/or `_exit`, which are async-signal-safe. |
| match unsafe { fork() }.expect("Error: Fork Failed") { |
| Child => { |
| pause(); |
| unsafe { _exit(123) } |
| } |
| Parent { child } => { |
| kill(child, Some(SIGKILL)).expect("Error: Kill Failed"); |
| assert_eq!( |
| waitid(Id::Pid(child), WaitPidFlag::WEXITED), |
| Ok(WaitStatus::Signaled(child, SIGKILL, false)), |
| ); |
| } |
| } |
| } |
| |
| #[test] |
| fn test_wait_exit() { |
| let _m = crate::FORK_MTX.lock(); |
| |
| // Safe: Child only calls `_exit`, which is async-signal-safe. |
| match unsafe { fork() }.expect("Error: Fork Failed") { |
| Child => unsafe { |
| _exit(12); |
| }, |
| Parent { child } => { |
| assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 12))); |
| } |
| } |
| } |
| |
| #[cfg(not(target_os = "haiku"))] |
| #[test] |
| #[cfg(any( |
| target_os = "android", |
| target_os = "freebsd", |
| target_os = "haiku", |
| all(target_os = "linux", not(target_env = "uclibc")), |
| ))] |
| #[cfg(not(any(target_arch = "mips", target_arch = "mips64")))] |
| fn test_waitid_exit() { |
| let _m = crate::FORK_MTX.lock(); |
| |
| // Safe: Child only calls `_exit`, which is async-signal-safe. |
| match unsafe { fork() }.expect("Error: Fork Failed") { |
| Child => unsafe { |
| _exit(12); |
| }, |
| Parent { child } => { |
| assert_eq!( |
| waitid(Id::Pid(child), WaitPidFlag::WEXITED), |
| Ok(WaitStatus::Exited(child, 12)), |
| ); |
| } |
| } |
| } |
| |
| #[test] |
| fn test_waitstatus_from_raw() { |
| let pid = Pid::from_raw(1); |
| assert_eq!( |
| WaitStatus::from_raw(pid, 0x0002), |
| Ok(WaitStatus::Signaled(pid, Signal::SIGINT, false)) |
| ); |
| assert_eq!( |
| WaitStatus::from_raw(pid, 0x0200), |
| Ok(WaitStatus::Exited(pid, 2)) |
| ); |
| assert_eq!(WaitStatus::from_raw(pid, 0x7f7f), Err(Errno::EINVAL)); |
| } |
| |
| #[test] |
| fn test_waitstatus_pid() { |
| let _m = crate::FORK_MTX.lock(); |
| |
| match unsafe { fork() }.unwrap() { |
| Child => unsafe { _exit(0) }, |
| Parent { child } => { |
| let status = waitpid(child, None).unwrap(); |
| assert_eq!(status.pid(), Some(child)); |
| } |
| } |
| } |
| |
| #[test] |
| #[cfg(any( |
| target_os = "android", |
| target_os = "freebsd", |
| target_os = "haiku", |
| all(target_os = "linux", not(target_env = "uclibc")), |
| ))] |
| fn test_waitid_pid() { |
| let _m = crate::FORK_MTX.lock(); |
| |
| match unsafe { fork() }.unwrap() { |
| Child => unsafe { _exit(0) }, |
| Parent { child } => { |
| let status = waitid(Id::Pid(child), WaitPidFlag::WEXITED).unwrap(); |
| assert_eq!(status.pid(), Some(child)); |
| } |
| } |
| } |
| |
| #[cfg(any(target_os = "linux", target_os = "android"))] |
| // FIXME: qemu-user doesn't implement ptrace on most arches |
| #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] |
| mod ptrace { |
| use crate::*; |
| use libc::_exit; |
| use nix::sys::ptrace::{self, Event, Options}; |
| use nix::sys::signal::*; |
| use nix::sys::wait::*; |
| use nix::unistd::ForkResult::*; |
| use nix::unistd::*; |
| |
| fn ptrace_child() -> ! { |
| ptrace::traceme().unwrap(); |
| // As recommended by ptrace(2), raise SIGTRAP to pause the child |
| // until the parent is ready to continue |
| raise(SIGTRAP).unwrap(); |
| unsafe { _exit(0) } |
| } |
| |
| fn ptrace_wait_parent(child: Pid) { |
| // Wait for the raised SIGTRAP |
| assert_eq!( |
| waitpid(child, None), |
| Ok(WaitStatus::Stopped(child, SIGTRAP)) |
| ); |
| // We want to test a syscall stop and a PTRACE_EVENT stop |
| ptrace::setoptions( |
| child, |
| Options::PTRACE_O_TRACESYSGOOD | Options::PTRACE_O_TRACEEXIT, |
| ) |
| .expect("setoptions failed"); |
| |
| // First, stop on the next system call, which will be exit() |
| ptrace::syscall(child, None).expect("syscall failed"); |
| assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child))); |
| // Then get the ptrace event for the process exiting |
| ptrace::cont(child, None).expect("cont failed"); |
| assert_eq!( |
| waitpid(child, None), |
| Ok(WaitStatus::PtraceEvent( |
| child, |
| SIGTRAP, |
| Event::PTRACE_EVENT_EXIT as i32 |
| )) |
| ); |
| // Finally get the normal wait() result, now that the process has exited |
| ptrace::cont(child, None).expect("cont failed"); |
| assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 0))); |
| } |
| |
| #[cfg(not(target_env = "uclibc"))] |
| fn ptrace_waitid_parent(child: Pid) { |
| // Wait for the raised SIGTRAP |
| // |
| // Unlike waitpid(), waitid() can distinguish trap events from regular |
| // stop events, so unlike ptrace_wait_parent(), we get a PtraceEvent here |
| assert_eq!( |
| waitid(Id::Pid(child), WaitPidFlag::WEXITED), |
| Ok(WaitStatus::PtraceEvent(child, SIGTRAP, 0)), |
| ); |
| // We want to test a syscall stop and a PTRACE_EVENT stop |
| ptrace::setoptions( |
| child, |
| Options::PTRACE_O_TRACESYSGOOD | Options::PTRACE_O_TRACEEXIT, |
| ) |
| .expect("setopts failed"); |
| |
| // First, stop on the next system call, which will be exit() |
| ptrace::syscall(child, None).expect("syscall failed"); |
| assert_eq!( |
| waitid(Id::Pid(child), WaitPidFlag::WEXITED), |
| Ok(WaitStatus::PtraceSyscall(child)), |
| ); |
| // Then get the ptrace event for the process exiting |
| ptrace::cont(child, None).expect("cont failed"); |
| assert_eq!( |
| waitid(Id::Pid(child), WaitPidFlag::WEXITED), |
| Ok(WaitStatus::PtraceEvent( |
| child, |
| SIGTRAP, |
| Event::PTRACE_EVENT_EXIT as i32 |
| )), |
| ); |
| // Finally get the normal wait() result, now that the process has exited |
| ptrace::cont(child, None).expect("cont failed"); |
| assert_eq!( |
| waitid(Id::Pid(child), WaitPidFlag::WEXITED), |
| Ok(WaitStatus::Exited(child, 0)), |
| ); |
| } |
| |
| #[test] |
| fn test_wait_ptrace() { |
| require_capability!("test_wait_ptrace", CAP_SYS_PTRACE); |
| let _m = crate::FORK_MTX.lock(); |
| |
| match unsafe { fork() }.expect("Error: Fork Failed") { |
| Child => ptrace_child(), |
| Parent { child } => ptrace_wait_parent(child), |
| } |
| } |
| |
| #[test] |
| #[cfg(not(target_env = "uclibc"))] |
| fn test_waitid_ptrace() { |
| require_capability!("test_waitid_ptrace", CAP_SYS_PTRACE); |
| let _m = crate::FORK_MTX.lock(); |
| |
| match unsafe { fork() }.expect("Error: Fork Failed") { |
| Child => ptrace_child(), |
| Parent { child } => ptrace_waitid_parent(child), |
| } |
| } |
| } |