summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md5
-rw-r--r--src/sys/ptrace.rs98
-rw-r--r--src/sys/select.rs225
-rw-r--r--test/sys/mod.rs1
-rw-r--r--test/sys/test_ptrace.rs49
-rw-r--r--test/sys/test_select.rs59
-rw-r--r--test/sys/test_wait.rs11
7 files changed, 357 insertions, 91 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 838dcc33..88c266a5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
### Added
+- Added specialized wrappers: `sys::ptrace::{traceme, syscall, cont, attach}`. Using the matching routines
+ with `sys::ptrace::ptrace` is now deprecated.
- Added `nix::poll` module for all platforms
([#672](https://github.com/nix-rust/nix/pull/672))
- Added `nix::ppoll` function for FreeBSD and DragonFly
@@ -17,10 +19,13 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Changed
- Renamed existing `ptrace` wrappers to encourage namespacing ([#692](https://github.com/nix-rust/nix/pull/692))
+- Marked `sys::ptrace::ptrace` as `unsafe`.
- Changed function signature of `socket()` and `socketpair()`. The `protocol` argument
has changed type from `c_int` to `SockProtocol`.
It accepts a `None` value for default protocol that was specified with zero using `c_int`.
([#647](https://github.com/nix-rust/nix/pull/647))
+- Made `select` easier to use, adding the ability to automatically calculate the `nfds` parameter using the new
+ `FdSet::highest` ([#701](https://github.com/nix-rust/nix/pull/701))
- Exposed `unistd::setresuid` and `unistd::setresgid` on FreeBSD and OpenBSD
([#721](https://github.com/nix-rust/nix/pull/721))
- Refactored the `statvfs` module removing extraneous API functions and the
diff --git a/src/sys/ptrace.rs b/src/sys/ptrace.rs
index 877bfcb0..5f0391d4 100644
--- a/src/sys/ptrace.rs
+++ b/src/sys/ptrace.rs
@@ -1,12 +1,22 @@
+//! For detailed description of the ptrace requests, consult `man ptrace`.
+
use std::{mem, ptr};
use {Errno, Error, Result};
-use libc::{c_void, c_long, siginfo_t};
+use libc::{self, c_void, c_long, siginfo_t};
use ::unistd::Pid;
+use sys::signal::Signal;
pub mod ptrace {
use libc::c_int;
- pub type PtraceRequest = c_int;
+ cfg_if! {
+ if #[cfg(any(all(target_os = "linux", arch = "s390x"),
+ all(target_os = "linux", target_env = "gnu")))] {
+ pub type PtraceRequest = ::libc::c_uint;
+ } else {
+ pub type PtraceRequest = c_int;
+ }
+ }
pub const PTRACE_TRACEME: PtraceRequest = 0;
pub const PTRACE_PEEKTEXT: PtraceRequest = 1;
@@ -60,17 +70,13 @@ pub mod ptrace {
pub const PTRACE_O_TRACESECCOMP: PtraceOptions = (1 << PTRACE_EVENT_SECCOMP);
}
-mod ffi {
- use libc::{pid_t, c_int, c_long, c_void};
-
- extern {
- pub fn ptrace(request: c_int, pid: pid_t, addr: * const c_void, data: * const c_void) -> c_long;
- }
-}
-
/// Performs a ptrace request. If the request in question is provided by a specialised function
/// this function will return an unsupported operation error.
-pub fn ptrace(request: ptrace::PtraceRequest, pid: Pid, addr: *mut c_void, data: *mut c_void) -> Result<c_long> {
+#[deprecated(
+ since="0.10.0",
+ note="usages of `ptrace()` should be replaced with the specialized helper functions instead"
+)]
+pub unsafe fn ptrace(request: ptrace::PtraceRequest, pid: Pid, addr: *mut c_void, data: *mut c_void) -> Result<c_long> {
use self::ptrace::*;
match request {
@@ -83,7 +89,7 @@ pub fn ptrace(request: ptrace::PtraceRequest, pid: Pid, addr: *mut c_void, data:
fn ptrace_peek(request: ptrace::PtraceRequest, pid: Pid, addr: *mut c_void, data: *mut c_void) -> Result<c_long> {
let ret = unsafe {
Errno::clear();
- ffi::ptrace(request, pid.into(), addr, data)
+ libc::ptrace(request, libc::pid_t::from(pid), addr, data)
};
match Errno::result(ret) {
Ok(..) | Err(Error::Sys(Errno::UnknownErrno)) => Ok(ret),
@@ -98,13 +104,13 @@ fn ptrace_peek(request: ptrace::PtraceRequest, pid: Pid, addr: *mut c_void, data
fn ptrace_get_data<T>(request: ptrace::PtraceRequest, pid: Pid) -> Result<T> {
// Creates an uninitialized pointer to store result in
let data: T = unsafe { mem::uninitialized() };
- let res = unsafe { ffi::ptrace(request, pid.into(), ptr::null_mut(), &data as *const _ as *const c_void) };
+ let res = unsafe { libc::ptrace(request, libc::pid_t::from(pid), ptr::null_mut::<T>(), &data as *const _ as *const c_void) };
Errno::result(res)?;
Ok(data)
}
-fn ptrace_other(request: ptrace::PtraceRequest, pid: Pid, addr: *mut c_void, data: *mut c_void) -> Result<c_long> {
- Errno::result(unsafe { ffi::ptrace(request, pid.into(), addr, data) }).map(|_| 0)
+unsafe fn ptrace_other(request: ptrace::PtraceRequest, pid: Pid, addr: *mut c_void, data: *mut c_void) -> Result<c_long> {
+ Errno::result(libc::ptrace(request, libc::pid_t::from(pid), addr, data)).map(|_| 0)
}
/// Set options, as with `ptrace(PTRACE_SETOPTIONS,...)`.
@@ -112,7 +118,7 @@ pub fn setoptions(pid: Pid, options: ptrace::PtraceOptions) -> Result<()> {
use self::ptrace::*;
use std::ptr;
- let res = unsafe { ffi::ptrace(PTRACE_SETOPTIONS, pid.into(), ptr::null_mut(), options as *mut c_void) };
+ let res = unsafe { libc::ptrace(PTRACE_SETOPTIONS, libc::pid_t::from(pid), ptr::null_mut::<libc::c_void>(), options as *mut c_void) };
Errno::result(res).map(|_| ())
}
@@ -133,10 +139,68 @@ pub fn setsiginfo(pid: Pid, sig: &siginfo_t) -> Result<()> {
use self::ptrace::*;
let ret = unsafe{
Errno::clear();
- ffi::ptrace(PTRACE_SETSIGINFO, pid.into(), ptr::null_mut(), sig as *const _ as *const c_void)
+ libc::ptrace(PTRACE_SETSIGINFO, libc::pid_t::from(pid), ptr::null_mut::<libc::c_void>(), sig as *const _ as *const c_void)
};
match Errno::result(ret) {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
}
+
+/// Sets the process as traceable, as with `ptrace(PTRACE_TRACEME, ...)`
+///
+/// Indicates that this process is to be traced by its parent.
+/// This is the only ptrace request to be issued by the tracee.
+pub fn traceme() -> Result<()> {
+ unsafe {
+ ptrace_other(
+ ptrace::PTRACE_TRACEME,
+ Pid::from_raw(0),
+ ptr::null_mut(),
+ ptr::null_mut(),
+ ).map(|_| ()) // ignore the useless return value
+ }
+}
+
+/// Ask for next syscall, as with `ptrace(PTRACE_SYSCALL, ...)`
+///
+/// Arranges for the tracee to be stopped at the next entry to or exit from a system call.
+pub fn syscall(pid: Pid) -> Result<()> {
+ unsafe {
+ ptrace_other(
+ ptrace::PTRACE_SYSCALL,
+ pid,
+ ptr::null_mut(),
+ ptr::null_mut(),
+ ).map(|_| ()) // ignore the useless return value
+ }
+}
+
+/// Attach to a running process, as with `ptrace(PTRACE_ATTACH, ...)`
+///
+/// Attaches to the process specified in pid, making it a tracee of the calling process.
+pub fn attach(pid: Pid) -> Result<()> {
+ unsafe {
+ ptrace_other(
+ ptrace::PTRACE_ATTACH,
+ pid,
+ ptr::null_mut(),
+ ptr::null_mut(),
+ ).map(|_| ()) // ignore the useless return value
+ }
+}
+
+/// Restart the stopped tracee process, as with `ptrace(PTRACE_CONT, ...)`
+///
+/// Continues the execution of the process with PID `pid`, optionally
+/// delivering a signal specified by `sig`.
+pub fn cont<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
+ let data = match sig.into() {
+ Some(s) => s as i32 as *mut c_void,
+ None => ptr::null_mut(),
+ };
+ unsafe {
+ ptrace_other(ptrace::PTRACE_CONT, pid, ptr::null_mut(), data).map(|_| ()) // ignore the useless return value
+ }
+}
+
diff --git a/src/sys/select.rs b/src/sys/select.rs
index eae191b6..82b2aad3 100644
--- a/src/sys/select.rs
+++ b/src/sys/select.rs
@@ -53,6 +53,42 @@ impl FdSet {
*bits = 0
}
}
+
+ /// Finds the highest file descriptor in the set.
+ ///
+ /// Returns `None` if the set is empty.
+ ///
+ /// This can be used to calculate the `nfds` parameter of the [`select`] function.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # extern crate nix;
+ /// # use nix::sys::select::FdSet;
+ /// # fn main() {
+ /// let mut set = FdSet::new();
+ /// set.insert(4);
+ /// set.insert(9);
+ /// assert_eq!(set.highest(), Some(9));
+ /// # }
+ /// ```
+ ///
+ /// [`select`]: fn.select.html
+ pub fn highest(&self) -> Option<RawFd> {
+ for (i, &block) in self.bits.iter().enumerate().rev() {
+ if block != 0 {
+ // Highest bit is located at `BITS - 1 - n.leading_zeros()`. Examples:
+ // 0b00000001
+ // 7 leading zeros, result should be 0 (bit at index 0 is 1)
+ // 0b001xxxxx
+ // 2 leading zeros, result should be 5 (bit at index 5 is 1) - x may be 0 or 1
+
+ return Some((i * BITS + BITS - 1 - block.leading_zeros() as usize) as RawFd);
+ }
+ }
+
+ None
+ }
}
mod ffi {
@@ -68,11 +104,52 @@ mod ffi {
}
}
-pub fn select(nfds: c_int,
- readfds: Option<&mut FdSet>,
- writefds: Option<&mut FdSet>,
- errorfds: Option<&mut FdSet>,
- timeout: Option<&mut TimeVal>) -> Result<c_int> {
+/// Monitors file descriptors for readiness (see [select(2)]).
+///
+/// Returns the total number of ready file descriptors in all sets. The sets are changed so that all
+/// file descriptors that are ready for the given operation are set.
+///
+/// When this function returns, `timeout` has an implementation-defined value.
+///
+/// # Parameters
+///
+/// * `nfds`: The highest file descriptor set in any of the passed `FdSet`s, plus 1. If `None`, this
+/// is calculated automatically by calling [`FdSet::highest`] on all descriptor sets and adding 1
+/// to the maximum of that.
+/// * `readfds`: File descriptors to check for being ready to read.
+/// * `writefds`: File descriptors to check for being ready to write.
+/// * `errorfds`: File descriptors to check for pending error conditions.
+/// * `timeout`: Maximum time to wait for descriptors to become ready (`None` to block
+/// indefinitely).
+///
+/// [select(2)]: http://man7.org/linux/man-pages/man2/select.2.html
+/// [`FdSet::highest`]: struct.FdSet.html#method.highest
+pub fn select<'a, N, R, W, E, T>(nfds: N,
+ readfds: R,
+ writefds: W,
+ errorfds: E,
+ timeout: T) -> Result<c_int>
+where
+ N: Into<Option<c_int>>,
+ R: Into<Option<&'a mut FdSet>>,
+ W: Into<Option<&'a mut FdSet>>,
+ E: Into<Option<&'a mut FdSet>>,
+ T: Into<Option<&'a mut TimeVal>>,
+{
+ let readfds = readfds.into();
+ let writefds = writefds.into();
+ let errorfds = errorfds.into();
+ let timeout = timeout.into();
+
+ let nfds = nfds.into().unwrap_or_else(|| {
+ readfds.iter()
+ .chain(writefds.iter())
+ .chain(errorfds.iter())
+ .map(|set| set.highest().unwrap_or(-1))
+ .max()
+ .unwrap_or(-1) + 1
+ });
+
let readfds = readfds.map(|set| set as *mut FdSet).unwrap_or(null_mut());
let writefds = writefds.map(|set| set as *mut FdSet).unwrap_or(null_mut());
let errorfds = errorfds.map(|set| set as *mut FdSet).unwrap_or(null_mut());
@@ -85,3 +162,141 @@ pub fn select(nfds: c_int,
Errno::result(res)
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use sys::time::{TimeVal, TimeValLike};
+ use unistd::{write, pipe};
+
+ #[test]
+ fn fdset_insert() {
+ let mut fd_set = FdSet::new();
+
+ for i in 0..FD_SETSIZE {
+ assert!(!fd_set.contains(i));
+ }
+
+ fd_set.insert(7);
+
+ assert!(fd_set.contains(7));
+ }
+
+ #[test]
+ fn fdset_remove() {
+ let mut fd_set = FdSet::new();
+
+ for i in 0..FD_SETSIZE {
+ assert!(!fd_set.contains(i));
+ }
+
+ fd_set.insert(7);
+ fd_set.remove(7);
+
+ for i in 0..FD_SETSIZE {
+ assert!(!fd_set.contains(i));
+ }
+ }
+
+ #[test]
+ fn fdset_clear() {
+ let mut fd_set = FdSet::new();
+ fd_set.insert(1);
+ fd_set.insert(FD_SETSIZE / 2);
+ fd_set.insert(FD_SETSIZE - 1);
+
+ fd_set.clear();
+
+ for i in 0..FD_SETSIZE {
+ assert!(!fd_set.contains(i));
+ }
+ }
+
+ #[test]
+ fn fdset_highest() {
+ let mut set = FdSet::new();
+ assert_eq!(set.highest(), None);
+ set.insert(0);
+ assert_eq!(set.highest(), Some(0));
+ set.insert(90);
+ assert_eq!(set.highest(), Some(90));
+ set.remove(0);
+ assert_eq!(set.highest(), Some(90));
+ set.remove(90);
+ assert_eq!(set.highest(), None);
+
+ set.insert(4);
+ set.insert(5);
+ set.insert(7);
+ assert_eq!(set.highest(), Some(7));
+ }
+
+ // powerpc-unknown-linux-gnu currently fails on the first `assert_eq` because
+ // `select()` returns a 0 instead of a 1. Since this test has only been run on
+ // qemu, it's unclear if this is a OS or qemu bug. Just disable it on that arch
+ // for now.
+ // FIXME: Fix tests for powerpc and mips
+ // FIXME: Add a link to an upstream qemu bug if there is one
+ #[test]
+ #[cfg_attr(any(target_arch = "powerpc", target_arch = "mips"), ignore)]
+ fn test_select() {
+ let (r1, w1) = pipe().unwrap();
+ write(w1, b"hi!").unwrap();
+ let (r2, _w2) = pipe().unwrap();
+
+ let mut fd_set = FdSet::new();
+ fd_set.insert(r1);
+ fd_set.insert(r2);
+
+ let mut timeout = TimeVal::seconds(10);
+ assert_eq!(1, select(None,
+ &mut fd_set,
+ None,
+ None,
+ &mut timeout).unwrap());
+ assert!(fd_set.contains(r1));
+ assert!(!fd_set.contains(r2));
+ }
+
+ #[test]
+ #[cfg_attr(any(target_arch = "powerpc", target_arch = "mips"), ignore)]
+ fn test_select_nfds() {
+ let (r1, w1) = pipe().unwrap();
+ write(w1, b"hi!").unwrap();
+ let (r2, _w2) = pipe().unwrap();
+
+ let mut fd_set = FdSet::new();
+ fd_set.insert(r1);
+ fd_set.insert(r2);
+
+ let mut timeout = TimeVal::seconds(10);
+ assert_eq!(1, select(Some(fd_set.highest().unwrap() + 1),
+ &mut fd_set,
+ None,
+ None,
+ &mut timeout).unwrap());
+ assert!(fd_set.contains(r1));
+ assert!(!fd_set.contains(r2));
+ }
+
+ #[test]
+ #[cfg_attr(any(target_arch = "powerpc", target_arch = "mips"), ignore)]
+ fn test_select_nfds2() {
+ let (r1, w1) = pipe().unwrap();
+ write(w1, b"hi!").unwrap();
+ let (r2, _w2) = pipe().unwrap();
+
+ let mut fd_set = FdSet::new();
+ fd_set.insert(r1);
+ fd_set.insert(r2);
+
+ let mut timeout = TimeVal::seconds(10);
+ assert_eq!(1, select(::std::cmp::max(r1, r2) + 1,
+ &mut fd_set,
+ None,
+ None,
+ &mut timeout).unwrap());
+ assert!(fd_set.contains(r1));
+ assert!(!fd_set.contains(r2));
+ }
+}
diff --git a/test/sys/mod.rs b/test/sys/mod.rs
index 2ecc36f8..3aab9fc5 100644
--- a/test/sys/mod.rs
+++ b/test/sys/mod.rs
@@ -9,7 +9,6 @@ mod test_sockopt;
mod test_termios;
mod test_ioctl;
mod test_wait;
-mod test_select;
mod test_uio;
#[cfg(target_os = "linux")]
diff --git a/test/sys/test_ptrace.rs b/test/sys/test_ptrace.rs
index 0614c13f..16b24110 100644
--- a/test/sys/test_ptrace.rs
+++ b/test/sys/test_ptrace.rs
@@ -3,14 +3,13 @@ use nix::errno::Errno;
use nix::unistd::getpid;
use nix::sys::ptrace;
-use std::{mem, ptr};
+use std::mem;
#[test]
fn test_ptrace() {
- use nix::sys::ptrace::ptrace::PTRACE_ATTACH;
// Just make sure ptrace can be called at all, for now.
// FIXME: qemu-user doesn't implement ptrace on all arches, so permit ENOSYS
- let err = ptrace::ptrace(PTRACE_ATTACH, getpid(), ptr::null_mut(), ptr::null_mut()).unwrap_err();
+ let err = ptrace::attach(getpid()).unwrap_err();
assert!(err == Error::Sys(Errno::EPERM) || err == Error::Sys(Errno::ENOSYS));
}
@@ -47,3 +46,47 @@ fn test_ptrace_setsiginfo() {
_ => (),
}
}
+
+
+#[test]
+fn test_ptrace_cont() {
+ use nix::sys::ptrace;
+ use nix::sys::signal::{raise, Signal};
+ use nix::sys::wait::{waitpid, WaitStatus};
+ use nix::unistd::fork;
+ use nix::unistd::ForkResult::*;
+
+ // FIXME: qemu-user doesn't implement ptrace on all architectures
+ // and retunrs ENOSYS in this case.
+ // We (ab)use this behavior to detect the affected platforms
+ // and skip the test then.
+ // On valid platforms the ptrace call should return Errno::EPERM, this
+ // is already tested by `test_ptrace`.
+ let err = ptrace::attach(getpid()).unwrap_err();
+ if err == Error::Sys(Errno::ENOSYS) {
+ return;
+ }
+
+ match fork() {
+ Ok(Child) => {
+ ptrace::traceme().unwrap();
+ // As recommended by ptrace(2), raise SIGTRAP to pause the child
+ // until the parent is ready to continue
+ loop {
+ raise(Signal::SIGTRAP).unwrap();
+ }
+
+ },
+ Ok(Parent { child }) => {
+ assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTRAP)));
+ ptrace::cont(child, None).unwrap();
+ assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTRAP)));
+ ptrace::cont(child, Signal::SIGKILL).unwrap();
+ match waitpid(child, None) {
+ Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _)) if pid == child => {}
+ _ => panic!("The process should have been killed"),
+ }
+ },
+ Err(_) => panic!("Error: Fork Failed")
+ }
+}
diff --git a/test/sys/test_select.rs b/test/sys/test_select.rs
deleted file mode 100644
index d50c7d74..00000000
--- a/test/sys/test_select.rs
+++ /dev/null
@@ -1,59 +0,0 @@
-use nix::sys::select::{FdSet, FD_SETSIZE, select};
-use nix::sys::time::{TimeVal, TimeValLike};
-use nix::unistd::{write, pipe};
-
-#[test]
-fn test_fdset() {
- let mut fd_set = FdSet::new();
-
- for i in 0..FD_SETSIZE {
- assert!(!fd_set.contains(i));
- }
-
- fd_set.insert(7);
-
- assert!(fd_set.contains(7));
-
- fd_set.remove(7);
-
- for i in 0..FD_SETSIZE {
- assert!(!fd_set.contains(i));
- }
-
- fd_set.insert(1);
- fd_set.insert(FD_SETSIZE / 2);
- fd_set.insert(FD_SETSIZE - 1);
-
- fd_set.clear();
-
- for i in 0..FD_SETSIZE {
- assert!(!fd_set.contains(i));
- }
-}
-
-// powerpc-unknown-linux-gnu currently fails on the first `assert_eq` because
-// `select()` returns a 0 instead of a 1. Since this test has only been run on
-// qemu, it's unclear if this is a OS or qemu bug. Just disable it on that arch
-// for now.
-// FIXME: Fix tests for powerpc and mips
-// FIXME: Add a link to an upstream qemu bug if there is one
-#[test]
-#[cfg_attr(any(target_arch = "powerpc", target_arch = "mips"), ignore)]
-fn test_select() {
- let (r1, w1) = pipe().unwrap();
- write(w1, b"hi!").unwrap();
- let (r2, _w2) = pipe().unwrap();
-
- let mut fd_set = FdSet::new();
- fd_set.insert(r1);
- fd_set.insert(r2);
-
- let mut timeout = TimeVal::seconds(10);
- assert_eq!(1, select(r2 + 1,
- Some(&mut fd_set),
- None,
- None,
- Some(&mut timeout)).unwrap());
- assert!(fd_set.contains(r1));
- assert!(!fd_set.contains(r2));
-}
diff --git a/test/sys/test_wait.rs b/test/sys/test_wait.rs
index 620a4e33..0193e262 100644
--- a/test/sys/test_wait.rs
+++ b/test/sys/test_wait.rs
@@ -60,14 +60,13 @@ mod ptrace {
use nix::sys::wait::*;
use nix::unistd::*;
use nix::unistd::ForkResult::*;
- use std::ptr;
use libc::_exit;
fn ptrace_child() -> ! {
- ptrace::ptrace(PTRACE_TRACEME, Pid::from_raw(0), ptr::null_mut(), ptr::null_mut()).unwrap();
+ ptrace::traceme().unwrap();
// As recommended by ptrace(2), raise SIGTRAP to pause the child
// until the parent is ready to continue
- let _ = raise(SIGTRAP);
+ raise(SIGTRAP).unwrap();
unsafe { _exit(0) }
}
@@ -78,13 +77,13 @@ mod ptrace {
assert!(ptrace::setoptions(child, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXIT).is_ok());
// First, stop on the next system call, which will be exit()
- assert!(ptrace::ptrace(PTRACE_SYSCALL, child, ptr::null_mut(), ptr::null_mut()).is_ok());
+ assert!(ptrace::syscall(child).is_ok());
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
// Then get the ptrace event for the process exiting
- assert!(ptrace::ptrace(PTRACE_CONT, child, ptr::null_mut(), ptr::null_mut()).is_ok());
+ assert!(ptrace::cont(child, None).is_ok());
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceEvent(child, SIGTRAP, PTRACE_EVENT_EXIT)));
// Finally get the normal wait() result, now that the process has exited
- assert!(ptrace::ptrace(PTRACE_CONT, child, ptr::null_mut(), ptr::null_mut()).is_ok());
+ assert!(ptrace::cont(child, None).is_ok());
assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 0)));
}