diff options
-rw-r--r-- | CHANGELOG.md | 5 | ||||
-rw-r--r-- | src/sys/ptrace.rs | 98 | ||||
-rw-r--r-- | src/sys/select.rs | 225 | ||||
-rw-r--r-- | test/sys/mod.rs | 1 | ||||
-rw-r--r-- | test/sys/test_ptrace.rs | 49 | ||||
-rw-r--r-- | test/sys/test_select.rs | 59 | ||||
-rw-r--r-- | test/sys/test_wait.rs | 11 |
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))); } |