diff --git a/CHANGELOG.md b/CHANGELOG.md index 350fc117c2..16343a3177 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). and nix::Error::UnsupportedOperation}` ([#614](https://github.com/nix-rust/nix/pull/614)) - Added `cfmakeraw`, `cfsetspeed`, and `tcgetsid`. ([#527](https://github.com/nix-rust/nix/pull/527)) +- Added `nix::sys::uio::{process_vm_readv, process_vm_writev}` on Linux + ([#568](https://github.com/nix-rust/nix/pull/568)) ### Changed - Changed `ioctl!(write ...)` to take argument by value instead as pointer. diff --git a/src/sys/uio.rs b/src/sys/uio.rs index edca8cb4ff..a137c6f016 100644 --- a/src/sys/uio.rs +++ b/src/sys/uio.rs @@ -2,6 +2,7 @@ #![allow(improper_ctypes)] use {Errno, Result}; +use unistd::Pid; use libc::{self, c_int, c_void, size_t, off_t}; use std::marker::PhantomData; use std::os::unix::io::RawFd; @@ -56,6 +57,84 @@ pub fn pread(fd: RawFd, buf: &mut [u8], offset: off_t) -> Result{ Errno::result(res).map(|r| r as usize) } +/// A slice of memory in a remote process, starting at address `base` +/// and consisting of `len` bytes. +/// +/// This is the same underlying C structure as [`IoVec`](struct.IoVec.html), +/// except that it refers to memory in some other process, and is +/// therefore not represented in Rust by an actual slice as IoVec is. It +/// is used with [`process_vm_readv`](fn.process_vm_readv.html) +/// and [`process_vm_writev`](fn.process_vm_writev.html). +#[repr(C)] +pub struct RemoteIoVec { + /// The starting address of this slice (`iov_base`). + pub base: usize, + /// The number of bytes in this slice (`iov_len`). + pub len: usize, +} + +/// Write data directly to another process's virtual memory +/// (see [`process_vm_writev`(2)]). +/// +/// `local_iov` is a list of [`IoVec`]s containing the data to be written, +/// and `remote_iov` is a list of [`RemoteIoVec`]s identifying where the +/// data should be written in the target process. On success, returns the +/// number of bytes written, which will always be a whole +/// number of remote_iov chunks. +/// +/// This requires the same permissions as debugging the process using +/// [ptrace]: you must either be a privileged process (with +/// `CAP_SYS_PTRACE`), or you must be running as the same user as the +/// target process and the OS must have unprivileged debugging enabled. +/// +/// This function is only available on Linux. +/// +/// [`process_vm_writev`(2)]: http://man7.org/linux/man-pages/man2/process_vm_writev.2.html +/// [ptrace]: ../ptrace/index.html +/// [`IoVec`]: struct.IoVec.html +/// [`RemoteIoVec`]: struct.RemoteIoVec.html +#[cfg(target_os = "linux")] +pub fn process_vm_writev(pid: Pid, local_iov: &[IoVec<&[u8]>], remote_iov: &[RemoteIoVec]) -> Result { + let res = unsafe { + libc::process_vm_writev(pid.into(), + local_iov.as_ptr() as *const libc::iovec, local_iov.len() as libc::c_ulong, + remote_iov.as_ptr() as *const libc::iovec, remote_iov.len() as libc::c_ulong, 0) + }; + + Errno::result(res).map(|r| r as usize) +} + +/// Read data directly from another process's virtual memory +/// (see [`process_vm_readv`(2)]). +/// +/// `local_iov` is a list of [`IoVec`]s containing the buffer to copy +/// data into, and `remote_iov` is a list of [`RemoteIoVec`]s identifying +/// where the source data is in the target process. On success, +/// returns the number of bytes written, which will always be a whole +/// number of remote_iov chunks. +/// +/// This requires the same permissions as debugging the process using +/// [ptrace]: you must either be a privileged process (with +/// `CAP_SYS_PTRACE`), or you must be running as the same user as the +/// target process and the OS must have unprivileged debugging enabled. +/// +/// This function is only available on Linux. +/// +/// [`process_vm_readv`(2)]: http://man7.org/linux/man-pages/man2/process_vm_readv.2.html +/// [ptrace]: ../ptrace/index.html +/// [`IoVec`]: struct.IoVec.html +/// [`RemoteIoVec`]: struct.RemoteIoVec.html +#[cfg(any(target_os = "linux"))] +pub fn process_vm_readv(pid: Pid, local_iov: &[IoVec<&mut [u8]>], remote_iov: &[RemoteIoVec]) -> Result { + let res = unsafe { + libc::process_vm_readv(pid.into(), + local_iov.as_ptr() as *const libc::iovec, local_iov.len() as libc::c_ulong, + remote_iov.as_ptr() as *const libc::iovec, remote_iov.len() as libc::c_ulong, 0) + }; + + Errno::result(res).map(|r| r as usize) +} + #[repr(C)] pub struct IoVec(libc::iovec, PhantomData); diff --git a/test/sys/test_uio.rs b/test/sys/test_uio.rs index d805b3b399..636c8bfb28 100644 --- a/test/sys/test_uio.rs +++ b/test/sys/test_uio.rs @@ -190,3 +190,51 @@ fn test_preadv() { let all = buffers.concat(); assert_eq!(all, expected); } + +#[test] +#[cfg(target_os = "linux")] +// FIXME: qemu-user doesn't implement process_vm_readv/writev on most arches +#[cfg_attr(not(any(target_arch = "x86", target_arch = "x86_64")), ignore)] +fn test_process_vm_readv() { + use nix::unistd::ForkResult::*; + use nix::sys::signal::*; + use nix::sys::wait::*; + use std::str; + + #[allow(unused_variables)] + let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test"); + + let (r, w) = pipe().unwrap(); + match fork() { + Ok(Parent { child }) => { + close(w).unwrap(); + let mut msg = vec![0u8; 32]; + let bytes_read = read(r, &mut msg).unwrap(); + msg.truncate(bytes_read); + close(r).unwrap(); + + let ptr: usize = str::from_utf8(&msg).unwrap().parse().unwrap(); + let remote_iov = RemoteIoVec { base: ptr, len: 4 }; + let mut buf = vec![0u8; 4]; + + let ret = process_vm_readv(child, + &[IoVec::from_mut_slice(&mut buf)], + &[remote_iov]); + + kill(child, SIGTERM).unwrap(); + waitpid(child, None).unwrap(); + + assert_eq!(Ok(4), ret); + assert_eq!(&buf, b"test"); + }, + Ok(Child) => { + close(r).unwrap(); + let s = String::from("test"); + let msg = format!("{}", s.as_bytes().as_ptr() as usize); + write(w, msg.as_bytes()).unwrap(); + close(w).unwrap(); + pause().unwrap(); + }, + Err(_) => panic!("fork failed") + } +}