From 93b85d8b70b14c49a983a6ab94cec6154bb209ef Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Sat, 4 Nov 2017 11:59:39 -0600 Subject: [PATCH 1/4] Add AioCb::from_ptr and AioCb::from_mut_ptr --- CHANGELOG.md | 2 ++ src/sys/aio.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++ test/sys/test_aio.rs | 65 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 133 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 624f0c7fa7..573e167c73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +- Added `AioCb::from_ptr` and `AioCb::from_mut_ptr` + ([#820](https://github.com/nix-rust/nix/pull/820)) - 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 diff --git a/src/sys/aio.rs b/src/sys/aio.rs index d85ecfa11f..94837a1285 100644 --- a/src/sys/aio.rs +++ b/src/sys/aio.rs @@ -178,6 +178,73 @@ impl<'a> AioCb<'a> { } } + /// Constructs a new `AioCb` from a mutable raw pointer + /// + /// * `fd` File descriptor. Required for all aio functions. + /// * `offs` File offset + /// * `buf` Pointer to the memory buffer + /// * `len` Length of the buffer pointed to by `buf` + /// * `prio` If POSIX Prioritized IO is supported, then the operation will + /// be prioritized at the process's priority level minus `prio` + /// * `sigev_notify` Determines how you will be notified of event + /// completion. + /// * `opcode` This field is only used for `lio_listio`. It determines + /// which operation to use for this individual aiocb + /// + /// # Safety + /// + /// Unsafe because using this `AioCb` will cause `libc::aio_read` or + /// `libc::aio_write` to dereference a raw pointer, without type, bounds, or + /// lifetime checking. + pub unsafe fn from_mut_ptr(fd: RawFd, offs: off_t, + buf: *mut c_void, len: usize, + prio: libc::c_int, sigev_notify: SigevNotify, + opcode: LioOpcode) -> AioCb<'a> { + let mut a = AioCb::common_init(fd, prio, sigev_notify); + a.aio_offset = offs; + a.aio_nbytes = len; + a.aio_buf = buf; + a.aio_lio_opcode = opcode as libc::c_int; + + let aiocb = AioCb { aiocb: a, mutable: true, in_progress: false, + keeper: Keeper::none}; + aiocb + } + + /// Constructs a new `AioCb` from a raw pointer + /// + /// * `fd` File descriptor. Required for all aio functions. + /// * `offs` File offset + /// * `buf` Pointer to the memory buffer + /// * `len` Length of the buffer pointed to by `buf` + /// * `prio` If POSIX Prioritized IO is supported, then the operation will + /// be prioritized at the process's priority level minus `prio` + /// * `sigev_notify` Determines how you will be notified of event + /// completion. + /// * `opcode` This field is only used for `lio_listio`. It determines + /// which operation to use for this individual aiocb + /// + /// # Safety + /// + /// Unsafe because using this `AioCb` will cause `libc::aio_write` to + /// dereference a raw pointer, without type, bounds, or lifetime checking. + pub unsafe fn from_ptr(fd: RawFd, offs: off_t, + buf: *const c_void, len: usize, + prio: libc::c_int, sigev_notify: SigevNotify, + opcode: LioOpcode) -> AioCb<'a> { + let mut a = AioCb::common_init(fd, prio, sigev_notify); + a.aio_offset = offs; + a.aio_nbytes = len; + // casting a const ptr to a mutable ptr here is ok, because we set the + // AioCb's mutable field to false + a.aio_buf = buf as *mut c_void; + a.aio_lio_opcode = opcode as libc::c_int; + + let aiocb = AioCb { aiocb: a, mutable: false, in_progress: false, + keeper: Keeper::none}; + aiocb + } + /// Like `from_mut_slice`, but works on constant slices rather than /// mutable slices. /// diff --git a/test/sys/test_aio.rs b/test/sys/test_aio.rs index 1f80758509..42e71fdb14 100644 --- a/test/sys/test_aio.rs +++ b/test/sys/test_aio.rs @@ -1,4 +1,4 @@ -use libc::c_int; +use libc::{c_int, c_void}; use nix::{Error, Result}; use nix::errno::*; use nix::sys::aio::*; @@ -244,6 +244,36 @@ fn test_read_into_mut_slice() { assert!(rbuf == EXPECT); } +// Tests from_ptr +#[test] +#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] +fn test_read_into_pointer() { + const INITIAL: &'static [u8] = b"abcdef123456"; + let mut rbuf = vec![0; 4]; + const EXPECT: &'static [u8] = b"cdef"; + let mut f = tempfile().unwrap(); + f.write(INITIAL).unwrap(); + { + // Safety: ok because rbuf lives until after poll_aio + let mut aiocb = unsafe { + AioCb::from_mut_ptr( f.as_raw_fd(), + 2, //offset + rbuf.as_mut_ptr() as *mut c_void, + rbuf.len(), + 0, //priority + SigevNotify::SigevNone, + LioOpcode::LIO_NOP) + }; + aiocb.read().unwrap(); + + let err = poll_aio(&mut aiocb); + assert!(err == Ok(())); + assert!(aiocb.aio_return().unwrap() as usize == EXPECT.len()); + } + + assert!(rbuf == EXPECT); +} + // Test reading into an immutable buffer. It should fail // FIXME: This test fails to panic on Linux/musl #[test] @@ -292,6 +322,39 @@ fn test_write() { assert!(rbuf == EXPECT); } +// Tests `AioCb::from_ptr` +#[test] +#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] +fn test_write_into_pointer() { + const INITIAL: &'static [u8] = b"abcdef123456"; + let wbuf = "CDEF".to_string().into_bytes(); + let mut rbuf = Vec::new(); + const EXPECT: &'static [u8] = b"abCDEF123456"; + + let mut f = tempfile().unwrap(); + f.write(INITIAL).unwrap(); + // Safety: ok because aiocb outlives poll_aio + let mut aiocb = unsafe { + AioCb::from_ptr( f.as_raw_fd(), + 2, //offset + wbuf.as_ptr() as *const c_void, + wbuf.len(), + 0, //priority + SigevNotify::SigevNone, + LioOpcode::LIO_NOP) + }; + aiocb.write().unwrap(); + + let err = poll_aio(&mut aiocb); + assert!(err == Ok(())); + assert!(aiocb.aio_return().unwrap() as usize == wbuf.len()); + + f.seek(SeekFrom::Start(0)).unwrap(); + let len = f.read_to_end(&mut rbuf).unwrap(); + assert!(len == EXPECT.len()); + assert!(rbuf == EXPECT); +} + /// `AioCb::write` should not modify the `AioCb` object if libc::aio_write returns /// an error // Skip on Linux, because Linux's AIO implementation can't detect errors From 631e3f30f6ed58596efec58a286bad75fee482de Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Sat, 4 Nov 2017 14:48:14 -0600 Subject: [PATCH 2/4] aio: use `Bytes` instead of `Rc<[u8]>` It's not actually safe to read into an `Rc<[u8]>`. It only worked because of a coincidental `unsafe` block. Replace that type with `BytesMut` from the bytes crate. For consistency's sake, use `Bytes` for writing too, and completely remove methods relating to `Rc<[u8]>`. Note that the `AioCb` will actually own the `BytesMut` object. The caller must call `into_buffer` to get it back once the I/O is complete. Fixes #788 --- CHANGELOG.md | 3 + Cargo.toml | 5 ++ src/lib.rs | 1 + src/sys/aio.rs | 168 ++++++++++++++++++++++++++++++++++++------- test/sys/test_aio.rs | 137 ++++++++++++++++++++++++++++------- test/test.rs | 1 + 6 files changed, 266 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 573e167c73..f2d9d71b17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). ([#648](https://github.com/nix-rust/nix/pull/648)) ### Removed +- `AioCb::from_boxed_slice` has been removed. It was never actually safe. Use + `from_bytes` or `from_bytes_mut` instead. + ([#820](https://github.com/nix-rust/nix/pull/820)) - The syscall module has been removed. This only exposed enough functionality for `memfd_create()` and `pivot_root()`, which are still exposed as separate functions. ([#747](https://github.com/nix-rust/nix/pull/747)) diff --git a/Cargo.toml b/Cargo.toml index 0f3f4b956b..5abd446385 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,11 @@ bitflags = "1.0" cfg-if = "0.1.0" void = "1.0.2" +[dependencies.bytes] +version = "0.4.5" +# Don't include the optional serde feature +default-features = false + [target.'cfg(target_os = "dragonfly")'.build-dependencies] gcc = "0.3" diff --git a/src/lib.rs b/src/lib.rs index 8a63c07217..5e37dec25c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ #![cfg_attr(test, deny(warnings))] #![recursion_limit = "500"] +extern crate bytes; #[macro_use] extern crate bitflags; diff --git a/src/sys/aio.rs b/src/sys/aio.rs index 94837a1285..11defd61df 100644 --- a/src/sys/aio.rs +++ b/src/sys/aio.rs @@ -1,4 +1,5 @@ use {Error, Result}; +use bytes::{Bytes, BytesMut}; use errno::Errno; use std::os::unix::io::RawFd; use libc::{c_void, off_t, size_t}; @@ -7,7 +8,7 @@ use std::fmt; use std::fmt::Debug; use std::marker::PhantomData; use std::mem; -use std::rc::Rc; +use std::ops::Deref; use std::ptr::{null, null_mut}; use sys::signal::*; use sys::time::TimeSpec; @@ -66,15 +67,50 @@ pub enum AioCancelStat { AioAllDone = libc::AIO_ALLDONE, } -/// Private type used by nix to keep buffers from Drop'ing while the kernel has -/// a pointer to them. +/// Owns (uniquely or shared) a memory buffer to keep it from `Drop`ing while +/// the kernel has a pointer to it. #[derive(Clone, Debug)] -enum Keeper<'a> { - none, - /// Keeps a reference to a Boxed slice - boxed(Rc>), +pub enum Buffer<'a> { + /// No buffer to own. + /// + /// Used for operations like `aio_fsync` that have no data, or for unsafe + /// operations that work with raw pointers. + None, + /// Immutable shared ownership `Bytes` object + // Must use out-of-line allocation so the address of the data will be + // stable. `Bytes` and `BytesMut` sometimes dynamically allocate a buffer, + // and sometimes inline the data within the struct itself. + Bytes(Bytes), + /// Mutable uniquely owned `BytesMut` object + BytesMut(BytesMut), /// Keeps a reference to a slice - phantom(PhantomData<&'a mut [u8]>) + Phantom(PhantomData<&'a mut [u8]>) +} + +impl<'a> Buffer<'a> { + /// Return the inner `Bytes`, if any + pub fn bytes(&self) -> Option<&Bytes> { + match self { + &Buffer::Bytes(ref x) => Some(x), + _ => None + } + } + + /// Return the inner `BytesMut`, if any + pub fn bytes_mut(&self) -> Option<&BytesMut> { + match self { + &Buffer::BytesMut(ref x) => Some(x), + _ => None + } + } + + /// Is this `Buffer` `None`? + pub fn is_none(&self) -> bool { + match self { + &Buffer::None => true, + _ => false, + } + } } /// The basic structure used by all aio functions. Each `aiocb` represents one @@ -86,10 +122,21 @@ pub struct AioCb<'a> { /// Could this `AioCb` potentially have any in-kernel state? in_progress: bool, /// Used to keep buffers from Drop'ing - keeper: Keeper<'a> + buffer: Buffer<'a> } impl<'a> AioCb<'a> { + /// Remove the inner `Buffer` and return it + /// + /// It is an error to call this method while the `AioCb` is still in + /// progress. + pub fn buffer(&mut self) -> Buffer<'a> { + assert!(!self.in_progress); + let mut x = Buffer::None; + mem::swap(&mut self.buffer, &mut x); + x + } + /// Returns the underlying file descriptor associated with the `AioCb` pub fn fd(&self) -> RawFd { self.aiocb.aio_fildes @@ -115,7 +162,7 @@ impl<'a> AioCb<'a> { aiocb: a, mutable: false, in_progress: false, - keeper: Keeper::none + buffer: Buffer::None } } @@ -143,38 +190,92 @@ impl<'a> AioCb<'a> { aiocb: a, mutable: true, in_progress: false, - keeper: Keeper::phantom(PhantomData) + buffer: Buffer::Phantom(PhantomData) } } /// Constructs a new `AioCb`. /// /// Unlike `from_mut_slice`, this method returns a structure suitable for - /// placement on the heap. + /// placement on the heap. It may be used for write operations, but not + /// read operations. /// /// * `fd` File descriptor. Required for all aio functions. /// * `offs` File offset - /// * `buf` A shared memory buffer on the heap + /// * `buf` A shared memory buffer /// * `prio` If POSIX Prioritized IO is supported, then the operation will /// be prioritized at the process's priority level minus `prio` /// * `sigev_notify` Determines how you will be notified of event /// completion. /// * `opcode` This field is only used for `lio_listio`. It determines /// which operation to use for this individual aiocb - pub fn from_boxed_slice(fd: RawFd, offs: off_t, buf: Rc>, + pub fn from_bytes(fd: RawFd, offs: off_t, buf: Bytes, + prio: libc::c_int, sigev_notify: SigevNotify, + opcode: LioOpcode) -> AioCb<'a> { + // Small BytesMuts are stored inline. Inline storage is a no-no, + // because we store a pointer to the buffer in the AioCb before + // returning the Buffer by move. If the buffer is too small, reallocate + // it to force out-of-line storage + // TODO: Add an is_inline() method to BytesMut, and a way to explicitly + // force out-of-line allocation. + let buf2 = if buf.len() < 64 { + // Reallocate to force out-of-line allocation + let mut ool = Bytes::with_capacity(64); + ool.extend_from_slice(buf.deref()); + ool + } else { + buf + }; + let mut a = AioCb::common_init(fd, prio, sigev_notify); + a.aio_offset = offs; + a.aio_nbytes = buf2.len() as size_t; + a.aio_buf = buf2.as_ptr() as *mut c_void; + a.aio_lio_opcode = opcode as libc::c_int; + + AioCb { + aiocb: a, + mutable: false, + in_progress: false, + buffer: Buffer::Bytes(buf2) + } + } + + /// Constructs a new `AioCb`. + /// + /// Unlike `from_mut_slice`, this method returns a structure suitable for + /// placement on the heap. It may be used for both reads and writes. + /// + /// * `fd` File descriptor. Required for all aio functions. + /// * `offs` File offset + /// * `buf` A shared memory buffer + /// * `prio` If POSIX Prioritized IO is supported, then the operation will + /// be prioritized at the process's priority level minus `prio` + /// * `sigev_notify` Determines how you will be notified of event + /// completion. + /// * `opcode` This field is only used for `lio_listio`. It determines + /// which operation to use for this individual aiocb + pub fn from_bytes_mut(fd: RawFd, offs: off_t, buf: BytesMut, prio: libc::c_int, sigev_notify: SigevNotify, opcode: LioOpcode) -> AioCb<'a> { + let mut buf2 = if buf.len() < 64 { + // Reallocate to force out-of-line allocation + let mut ool = BytesMut::with_capacity(64); + ool.extend_from_slice(buf.deref()); + ool + } else { + buf + }; let mut a = AioCb::common_init(fd, prio, sigev_notify); a.aio_offset = offs; - a.aio_nbytes = buf.len() as size_t; - a.aio_buf = buf.as_ptr() as *mut c_void; + a.aio_nbytes = buf2.len() as size_t; + a.aio_buf = buf2.as_mut_ptr() as *mut c_void; a.aio_lio_opcode = opcode as libc::c_int; AioCb { aiocb: a, mutable: true, in_progress: false, - keeper: Keeper::boxed(buf) + buffer: Buffer::BytesMut(buf2) } } @@ -206,9 +307,12 @@ impl<'a> AioCb<'a> { a.aio_buf = buf; a.aio_lio_opcode = opcode as libc::c_int; - let aiocb = AioCb { aiocb: a, mutable: true, in_progress: false, - keeper: Keeper::none}; - aiocb + AioCb { + aiocb: a, + mutable: true, + in_progress: false, + buffer: Buffer::None + } } /// Constructs a new `AioCb` from a raw pointer @@ -240,9 +344,12 @@ impl<'a> AioCb<'a> { a.aio_buf = buf as *mut c_void; a.aio_lio_opcode = opcode as libc::c_int; - let aiocb = AioCb { aiocb: a, mutable: false, in_progress: false, - keeper: Keeper::none}; - aiocb + AioCb { + aiocb: a, + mutable: false, + in_progress: false, + buffer: Buffer::None + } } /// Like `from_mut_slice`, but works on constant slices rather than @@ -275,7 +382,20 @@ impl<'a> AioCb<'a> { aiocb: a, mutable: false, in_progress: false, - keeper: Keeper::none + buffer: Buffer::None + } + } + + /// Consumes the `aiocb` and returns its inner `Buffer`, if any. + /// + /// This method is especially useful when reading into a `BytesMut`, because + /// that type does not support shared ownership. + pub fn into_buffer(mut self) -> Buffer<'static> { + let buf = self.buffer(); + match buf { + Buffer::BytesMut(x) => Buffer::BytesMut(x), + Buffer::Bytes(x) => Buffer::Bytes(x), + _ => Buffer::None } } diff --git a/test/sys/test_aio.rs b/test/sys/test_aio.rs index 42e71fdb14..de4a920f61 100644 --- a/test/sys/test_aio.rs +++ b/test/sys/test_aio.rs @@ -1,3 +1,4 @@ +use bytes::{Bytes, BytesMut}; use libc::{c_int, c_void}; use nix::{Error, Result}; use nix::errno::*; @@ -7,7 +8,6 @@ use nix::sys::time::{TimeSpec, TimeValLike}; use std::io::{Write, Read, Seek, SeekFrom}; use std::ops::Deref; use std::os::unix::io::AsRawFd; -use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; use std::{thread, time}; use tempfile::tempfile; @@ -135,9 +135,10 @@ fn test_fsync_error() { #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] fn test_aio_suspend() { const INITIAL: &[u8] = b"abcdef123456"; - const WBUF: &[u8] = b"CDEF"; + const WBUF: &[u8] = b"CDEFG"; let timeout = TimeSpec::seconds(10); - let rbuf = Rc::new(vec![0; 4].into_boxed_slice()); + let mut rbuf = vec![0; 4]; + let rlen = rbuf.len(); let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); @@ -148,9 +149,9 @@ fn test_aio_suspend() { SigevNotify::SigevNone, LioOpcode::LIO_WRITE); - let mut rcb = AioCb::from_boxed_slice( f.as_raw_fd(), + let mut rcb = AioCb::from_mut_slice( f.as_raw_fd(), 8, //offset - Rc::clone(&rbuf), + &mut rbuf, 0, //priority SigevNotify::SigevNone, LioOpcode::LIO_READ); @@ -168,7 +169,7 @@ fn test_aio_suspend() { } assert!(wcb.aio_return().unwrap() as usize == WBUF.len()); - assert!(rcb.aio_return().unwrap() as usize == WBUF.len()); + assert!(rcb.aio_return().unwrap() as usize == rlen); } // Test a simple aio operation with no completion notification. We must poll @@ -177,14 +178,14 @@ fn test_aio_suspend() { #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] fn test_read() { const INITIAL: &[u8] = b"abcdef123456"; - let rbuf = Rc::new(vec![0; 4].into_boxed_slice()); + let mut rbuf = vec![0; 4]; const EXPECT: &[u8] = b"cdef"; let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); { - let mut aiocb = AioCb::from_boxed_slice( f.as_raw_fd(), + let mut aiocb = AioCb::from_mut_slice( f.as_raw_fd(), 2, //offset - Rc::clone(&rbuf), + &mut rbuf, 0, //priority SigevNotify::SigevNone, LioOpcode::LIO_NOP); @@ -206,12 +207,12 @@ fn test_read() { #[cfg(any(target_os = "freebsd", target_os = "macos"))] fn test_read_error() { const INITIAL: &[u8] = b"abcdef123456"; - let rbuf = Rc::new(vec![0; 4].into_boxed_slice()); + let mut rbuf = vec![0; 4]; let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); - let mut aiocb = AioCb::from_boxed_slice( f.as_raw_fd(), + let mut aiocb = AioCb::from_mut_slice( f.as_raw_fd(), -1, //an invalid offset - Rc::clone(&rbuf), + &mut rbuf, 0, //priority SigevNotify::SigevNone, LioOpcode::LIO_NOP); @@ -322,10 +323,93 @@ fn test_write() { assert!(rbuf == EXPECT); } +// Tests `AioCb::from_bytes` +#[test] +#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] +fn test_write_bytes() { + const INITIAL: &[u8] = b"abcdef123456"; + let wbuf = Bytes::from(&b"CDEF"[..]); + let mut rbuf = Vec::new(); + const EXPECT: &[u8] = b"abCDEF123456"; + + let mut f = tempfile().unwrap(); + f.write(INITIAL).unwrap(); + let mut aiocb = AioCb::from_bytes( f.as_raw_fd(), + 2, //offset + wbuf.clone(), + 0, //priority + SigevNotify::SigevNone, + LioOpcode::LIO_NOP); + aiocb.write().unwrap(); + + let err = poll_aio(&mut aiocb); + assert!(err == Ok(())); + assert!(aiocb.aio_return().unwrap() as usize == wbuf.len()); + + f.seek(SeekFrom::Start(0)).unwrap(); + let len = f.read_to_end(&mut rbuf).unwrap(); + assert!(len == EXPECT.len()); + assert!(rbuf == EXPECT); +} + +// Tests `AioCb::from_bytes_mut` +#[test] +#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] +fn test_read_bytes_mut_big() { + const INITIAL: &'static [u8] = b"abcdefgh12345678abcdefgh12345678abcdefgh12345678abcdefgh12345678abcdefgh12345678"; + // rbuf needs to be larger than 32 bytes (64 on 32-bit systems) so + // BytesMut::clone is implemented by reference. + let rbuf = BytesMut::from(vec![0; 70]); + const EXPECT: &'static [u8] = b"cdefgh12345678abcdefgh12345678abcdefgh12345678abcdefgh12345678abcdefgh"; + let mut f = tempfile().unwrap(); + f.write(INITIAL).unwrap(); + + let mut aiocb = AioCb::from_bytes_mut( f.as_raw_fd(), + 2, //offset + rbuf, + 0, //priority + SigevNotify::SigevNone, + LioOpcode::LIO_NOP); + aiocb.read().unwrap(); + + let err = poll_aio(&mut aiocb); + assert_eq!(err, Ok(())); + assert_eq!(aiocb.aio_return().unwrap() as usize, EXPECT.len()); + let buffer = aiocb.into_buffer(); + assert_eq!(buffer.bytes_mut().unwrap(), EXPECT); +} + +// Tests reallocation in `AioCb::from_bytes_mut` +#[test] +#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] +fn test_read_bytes_mut_small() { + const INITIAL: &'static [u8] = b"abcdef"; + // rbuf needs to be no more than 32 bytes (64 on 32-bit systems) so + // BytesMut::clone is implemented inline. + let rbuf = BytesMut::from(vec![0; 4]); + const EXPECT: &'static [u8] = b"cdef"; + let mut f = tempfile().unwrap(); + f.write(INITIAL).unwrap(); + + let mut aiocb = AioCb::from_bytes_mut( f.as_raw_fd(), + 2, //offset + rbuf, + 0, //priority + SigevNotify::SigevNone, + LioOpcode::LIO_NOP); + aiocb.read().unwrap(); + + let err = poll_aio(&mut aiocb); + assert_eq!(err, Ok(())); + assert_eq!(aiocb.aio_return().unwrap() as usize, EXPECT.len()); + let buffer = aiocb.into_buffer(); + assert_eq!(buffer.bytes_mut().unwrap(), EXPECT); +} + // Tests `AioCb::from_ptr` #[test] #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] -fn test_write_into_pointer() { +fn test_write_from_pointer() { const INITIAL: &'static [u8] = b"abcdef123456"; let wbuf = "CDEF".to_string().into_bytes(); let mut rbuf = Vec::new(); @@ -429,7 +513,8 @@ fn test_write_sigev_signal() { fn test_lio_listio_wait() { const INITIAL: &[u8] = b"abcdef123456"; const WBUF: &[u8] = b"CDEF"; - let rbuf = Rc::new(vec![0; 4].into_boxed_slice()); + let mut rbuf = vec![0; 4]; + let rlen = rbuf.len(); let mut rbuf2 = Vec::new(); const EXPECT: &[u8] = b"abCDEF123456"; let mut f = tempfile().unwrap(); @@ -444,9 +529,9 @@ fn test_lio_listio_wait() { SigevNotify::SigevNone, LioOpcode::LIO_WRITE); - let mut rcb = AioCb::from_boxed_slice( f.as_raw_fd(), + let mut rcb = AioCb::from_mut_slice( f.as_raw_fd(), 8, //offset - Rc::clone(&rbuf), + &mut rbuf, 0, //priority SigevNotify::SigevNone, LioOpcode::LIO_READ); @@ -454,7 +539,7 @@ fn test_lio_listio_wait() { err.expect("lio_listio failed"); assert!(wcb.aio_return().unwrap() as usize == WBUF.len()); - assert!(rcb.aio_return().unwrap() as usize == WBUF.len()); + assert!(rcb.aio_return().unwrap() as usize == rlen); } assert!(rbuf.deref().deref() == b"3456"); @@ -472,7 +557,8 @@ fn test_lio_listio_wait() { fn test_lio_listio_nowait() { const INITIAL: &[u8] = b"abcdef123456"; const WBUF: &[u8] = b"CDEF"; - let rbuf = Rc::new(vec![0; 4].into_boxed_slice()); + let mut rbuf = vec![0; 4]; + let rlen = rbuf.len(); let mut rbuf2 = Vec::new(); const EXPECT: &[u8] = b"abCDEF123456"; let mut f = tempfile().unwrap(); @@ -487,9 +573,9 @@ fn test_lio_listio_nowait() { SigevNotify::SigevNone, LioOpcode::LIO_WRITE); - let mut rcb = AioCb::from_boxed_slice( f.as_raw_fd(), + let mut rcb = AioCb::from_mut_slice( f.as_raw_fd(), 8, //offset - Rc::clone(&rbuf), + &mut rbuf, 0, //priority SigevNotify::SigevNone, LioOpcode::LIO_READ); @@ -499,7 +585,7 @@ fn test_lio_listio_nowait() { poll_aio(&mut wcb).unwrap(); poll_aio(&mut rcb).unwrap(); assert!(wcb.aio_return().unwrap() as usize == WBUF.len()); - assert!(rcb.aio_return().unwrap() as usize == WBUF.len()); + assert!(rcb.aio_return().unwrap() as usize == rlen); } assert!(rbuf.deref().deref() == b"3456"); @@ -520,7 +606,8 @@ fn test_lio_listio_signal() { let m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test"); const INITIAL: &[u8] = b"abcdef123456"; const WBUF: &[u8] = b"CDEF"; - let rbuf = Rc::new(vec![0; 4].into_boxed_slice()); + let mut rbuf = vec![0; 4]; + let rlen = rbuf.len(); let mut rbuf2 = Vec::new(); const EXPECT: &[u8] = b"abCDEF123456"; let mut f = tempfile().unwrap(); @@ -540,9 +627,9 @@ fn test_lio_listio_signal() { SigevNotify::SigevNone, LioOpcode::LIO_WRITE); - let mut rcb = AioCb::from_boxed_slice( f.as_raw_fd(), + let mut rcb = AioCb::from_mut_slice( f.as_raw_fd(), 8, //offset - Rc::clone(&rbuf), + &mut rbuf, 0, //priority SigevNotify::SigevNone, LioOpcode::LIO_READ); @@ -555,7 +642,7 @@ fn test_lio_listio_signal() { } assert!(wcb.aio_return().unwrap() as usize == WBUF.len()); - assert!(rcb.aio_return().unwrap() as usize == WBUF.len()); + assert!(rcb.aio_return().unwrap() as usize == rlen); } assert!(rbuf.deref().deref() == b"3456"); diff --git a/test/test.rs b/test/test.rs index 14cbd7bcab..e449c13e9f 100644 --- a/test/test.rs +++ b/test/test.rs @@ -1,3 +1,4 @@ +extern crate bytes; #[macro_use] extern crate cfg_if; #[macro_use] From ec935e6ef6c4ec09ab2f00b84d2906e3c05c5b55 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Sat, 13 Jan 2018 15:12:08 -0700 Subject: [PATCH 3/4] aio: Clippy cleanup --- src/sys/aio.rs | 24 ++++++++---------------- test/sys/test_aio.rs | 36 ++++++++++++++++++------------------ 2 files changed, 26 insertions(+), 34 deletions(-) diff --git a/src/sys/aio.rs b/src/sys/aio.rs index 11defd61df..687458e5c8 100644 --- a/src/sys/aio.rs +++ b/src/sys/aio.rs @@ -90,24 +90,24 @@ pub enum Buffer<'a> { impl<'a> Buffer<'a> { /// Return the inner `Bytes`, if any pub fn bytes(&self) -> Option<&Bytes> { - match self { - &Buffer::Bytes(ref x) => Some(x), + match *self { + Buffer::Bytes(ref x) => Some(x), _ => None } } /// Return the inner `BytesMut`, if any pub fn bytes_mut(&self) -> Option<&BytesMut> { - match self { - &Buffer::BytesMut(ref x) => Some(x), + match *self { + Buffer::BytesMut(ref x) => Some(x), _ => None } } /// Is this `Buffer` `None`? pub fn is_none(&self) -> bool { - match self { - &Buffer::None => true, + match *self { + Buffer::None => true, _ => false, } } @@ -535,11 +535,7 @@ pub fn aio_cancel_all(fd: RawFd) -> Result { /// has completed, a signal is delivered, or the timeout has passed. If /// `timeout` is `None`, `aio_suspend` will block indefinitely. pub fn aio_suspend(list: &[&AioCb], timeout: Option) -> Result<()> { - // We must use transmute because Rust doesn't understand that a pointer to a - // Struct is the same as a pointer to its first element. - let plist = unsafe { - mem::transmute::<&[&AioCb], *const [*const libc::aiocb]>(list) - }; + let plist = list as *const [&AioCb] as *const [*const libc::aiocb]; let p = plist as *const *const libc::aiocb; let timep = match timeout { None => null::(), @@ -558,11 +554,7 @@ pub fn lio_listio(mode: LioMode, list: &[&mut AioCb], sigev_notify: SigevNotify) -> Result<()> { let sigev = SigEvent::new(sigev_notify); let sigevp = &mut sigev.sigevent() as *mut libc::sigevent; - // We must use transmute because Rust doesn't understand that a pointer to a - // Struct is the same as a pointer to its first element. - let plist = unsafe { - mem::transmute::<&[&mut AioCb], *const [*mut libc::aiocb]>(list) - }; + let plist = list as *const [&mut AioCb] as *const [*mut libc::aiocb]; let p = plist as *const *mut libc::aiocb; Errno::result(unsafe { libc::lio_listio(mode as i32, p, list.len() as i32, sigevp) diff --git a/test/sys/test_aio.rs b/test/sys/test_aio.rs index de4a920f61..ed9d37619b 100644 --- a/test/sys/test_aio.rs +++ b/test/sys/test_aio.rs @@ -110,7 +110,7 @@ fn test_fsync() { aiocb.aio_return().unwrap(); } -/// `AioCb::fsync` should not modify the `AioCb` object if libc::aio_fsync returns +/// `AioCb::fsync` should not modify the `AioCb` object if `libc::aio_fsync` returns /// an error // Skip on Linux, because Linux's AIO implementation can't detect errors // synchronously @@ -199,8 +199,8 @@ fn test_read() { assert!(EXPECT == rbuf.deref().deref()); } -/// `AioCb::read` should not modify the `AioCb` object if libc::aio_read returns -/// an error +/// `AioCb::read` should not modify the `AioCb` object if `libc::aio_read` +/// returns an error // Skip on Linux, because Linux's AIO implementation can't detect errors // synchronously #[test] @@ -249,11 +249,11 @@ fn test_read_into_mut_slice() { #[test] #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] fn test_read_into_pointer() { - const INITIAL: &'static [u8] = b"abcdef123456"; + const INITIAL: &[u8] = b"abcdef123456"; let mut rbuf = vec![0; 4]; - const EXPECT: &'static [u8] = b"cdef"; + const EXPECT: &[u8] = b"cdef"; let mut f = tempfile().unwrap(); - f.write(INITIAL).unwrap(); + f.write_all(INITIAL).unwrap(); { // Safety: ok because rbuf lives until after poll_aio let mut aiocb = unsafe { @@ -333,7 +333,7 @@ fn test_write_bytes() { const EXPECT: &[u8] = b"abCDEF123456"; let mut f = tempfile().unwrap(); - f.write(INITIAL).unwrap(); + f.write_all(INITIAL).unwrap(); let mut aiocb = AioCb::from_bytes( f.as_raw_fd(), 2, //offset wbuf.clone(), @@ -356,13 +356,13 @@ fn test_write_bytes() { #[test] #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] fn test_read_bytes_mut_big() { - const INITIAL: &'static [u8] = b"abcdefgh12345678abcdefgh12345678abcdefgh12345678abcdefgh12345678abcdefgh12345678"; + const INITIAL: &[u8] = b"abcdefgh12345678abcdefgh12345678abcdefgh12345678abcdefgh12345678abcdefgh12345678"; // rbuf needs to be larger than 32 bytes (64 on 32-bit systems) so // BytesMut::clone is implemented by reference. let rbuf = BytesMut::from(vec![0; 70]); - const EXPECT: &'static [u8] = b"cdefgh12345678abcdefgh12345678abcdefgh12345678abcdefgh12345678abcdefgh"; + const EXPECT: &[u8] = b"cdefgh12345678abcdefgh12345678abcdefgh12345678abcdefgh12345678abcdefgh"; let mut f = tempfile().unwrap(); - f.write(INITIAL).unwrap(); + f.write_all(INITIAL).unwrap(); let mut aiocb = AioCb::from_bytes_mut( f.as_raw_fd(), 2, //offset @@ -383,13 +383,13 @@ fn test_read_bytes_mut_big() { #[test] #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] fn test_read_bytes_mut_small() { - const INITIAL: &'static [u8] = b"abcdef"; + const INITIAL: &[u8] = b"abcdef"; // rbuf needs to be no more than 32 bytes (64 on 32-bit systems) so // BytesMut::clone is implemented inline. let rbuf = BytesMut::from(vec![0; 4]); - const EXPECT: &'static [u8] = b"cdef"; + const EXPECT: &[u8] = b"cdef"; let mut f = tempfile().unwrap(); - f.write(INITIAL).unwrap(); + f.write_all(INITIAL).unwrap(); let mut aiocb = AioCb::from_bytes_mut( f.as_raw_fd(), 2, //offset @@ -410,13 +410,13 @@ fn test_read_bytes_mut_small() { #[test] #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] fn test_write_from_pointer() { - const INITIAL: &'static [u8] = b"abcdef123456"; + const INITIAL: &[u8] = b"abcdef123456"; let wbuf = "CDEF".to_string().into_bytes(); let mut rbuf = Vec::new(); - const EXPECT: &'static [u8] = b"abCDEF123456"; + const EXPECT: &[u8] = b"abCDEF123456"; let mut f = tempfile().unwrap(); - f.write(INITIAL).unwrap(); + f.write_all(INITIAL).unwrap(); // Safety: ok because aiocb outlives poll_aio let mut aiocb = unsafe { AioCb::from_ptr( f.as_raw_fd(), @@ -439,8 +439,8 @@ fn test_write_from_pointer() { assert!(rbuf == EXPECT); } -/// `AioCb::write` should not modify the `AioCb` object if libc::aio_write returns -/// an error +/// `AioCb::write` should not modify the `AioCb` object if `libc::aio_write` +/// returns an error // Skip on Linux, because Linux's AIO implementation can't detect errors // synchronously #[test] From c3ff37b0942f527295a7213f44eee13bf2590ddf Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Sat, 13 Jan 2018 20:47:38 -0700 Subject: [PATCH 4/4] aio: more documentation --- src/sys/aio.rs | 643 +++++++++++++++++++++++++++++++++++++------ test/sys/test_aio.rs | 4 +- 2 files changed, 554 insertions(+), 93 deletions(-) diff --git a/src/sys/aio.rs b/src/sys/aio.rs index 687458e5c8..d5561e9cf6 100644 --- a/src/sys/aio.rs +++ b/src/sys/aio.rs @@ -1,3 +1,25 @@ +//! POSIX Asynchronous I/O +//! +//! The POSIX AIO interface is used for asynchronous I/O on files and disk-like +//! devices. It supports [`read`](struct.AioCb.html#method.read), +//! [`write`](struct.AioCb.html#method.write), and +//! [`fsync`](struct.AioCb.html#method.fsync) operations. Completion +//! notifications can optionally be delivered via +//! [signals](../signal/enum.SigevNotify.html#variant.SigevSignal), via the +//! [`aio_suspend`](fn.aio_suspend.html) function, or via polling. Some +//! platforms support other completion +//! notifications, such as +//! [kevent](../signal/enum.SigevNotify.html#variant.SigevKevent). +//! +//! Multiple operations may be submitted in a batch with +//! [`lio_listio`](fn.lio_listio.html), though the standard does not guarantee +//! that they will be executed atomically. +//! +//! Outstanding operations may be cancelled with +//! [`cancel`](struct.AioCb.html#method.cancel) or +//! [`aio_cancel_all`](fn.aio_cancel_all.html), though the operating system may +//! not support this for all filesystems and devices. + use {Error, Result}; use bytes::{Bytes, BytesMut}; use errno::Errno; @@ -13,9 +35,9 @@ use std::ptr::{null, null_mut}; use sys::signal::*; use sys::time::TimeSpec; -/// Mode for `AioCb::fsync`. Controls whether only data or both data and -/// metadata are synced. libc_enum! { + /// Mode for `AioCb::fsync`. Controls whether only data or both data and + /// metadata are synced. #[repr(i32)] pub enum AioFsyncMode { /// do it like `fsync` @@ -31,9 +53,9 @@ libc_enum! { } libc_enum! { - /// When used with `lio_listio`, determines whether a given `aiocb` should be - /// used for a read operation, a write operation, or ignored. Has no effect for - /// any other aio functions. + /// When used with [`lio_listio`](fn.lio_listio.html), determines whether a + /// given `aiocb` should be used for a read operation, a write operation, or + /// ignored. Has no effect for any other aio functions. #[repr(i32)] pub enum LioOpcode { LIO_NOP, @@ -43,18 +65,19 @@ libc_enum! { } libc_enum! { - /// Mode for `lio_listio`. + /// Mode for [`lio_listio`](fn.lio_listio.html) #[repr(i32)] pub enum LioMode { - /// Requests that `lio_listio` block until all requested operations have - /// been completed + /// Requests that [`lio_listio`](fn.lio_listio.html) block until all + /// requested operations have been completed LIO_WAIT, - /// Requests that `lio_listio` return immediately + /// Requests that [`lio_listio`](fn.lio_listio.html) return immediately LIO_NOWAIT, } } -/// Return values for `AioCb::cancel and aio_cancel_all` +/// Return values for [`AioCb::cancel`](struct.AioCb.html#method.cancel) and +/// [`aio_cancel_all`](fn.aio_cancel_all.html) #[repr(i32)] #[derive(Clone, Copy, Debug, PartialEq)] pub enum AioCancelStat { @@ -113,15 +136,20 @@ impl<'a> Buffer<'a> { } } -/// The basic structure used by all aio functions. Each `aiocb` represents one +/// AIO Control Block. +/// +/// The basic structure used by all aio functions. Each `AioCb` represents one /// I/O request. pub struct AioCb<'a> { aiocb: libc::aiocb, - /// Tracks whether the buffer pointed to by aiocb.aio_buf is mutable + /// Tracks whether the buffer pointed to by `libc::aiocb.aio_buf` is mutable mutable: bool, /// Could this `AioCb` potentially have any in-kernel state? in_progress: bool, - /// Used to keep buffers from Drop'ing + /// Optionally keeps a reference to the data. + /// + /// Used to keep buffers from `Drop`'ing, and may be returned once the + /// `AioCb` is completed by `into_buffer`. buffer: Buffer<'a> } @@ -146,11 +174,40 @@ impl<'a> AioCb<'a> { /// /// The resulting `AioCb` structure is suitable for use with `AioCb::fsync`. /// - /// * `fd` File descriptor. Required for all aio functions. - /// * `prio` If POSIX Prioritized IO is supported, then the operation will - /// be prioritized at the process's priority level minus `prio` - /// * `sigev_notify` Determines how you will be notified of event - /// completion. + /// # Parameters + /// + /// * `fd`: File descriptor. Required for all aio functions. + /// * `prio`: If POSIX Prioritized IO is supported, then the + /// operation will be prioritized at the process's + /// priority level minus `prio`. + /// * `sigev_notify`: Determines how you will be notified of event + /// completion. + /// + /// # Examples + /// + /// Create an `AioCb` from a raw file descriptor and use it for an + /// [`fsync`](#method.from_bytes_mut) operation. + /// + /// ``` + /// # extern crate tempfile; + /// # extern crate nix; + /// # use nix::errno::Errno; + /// # use nix::Error; + /// # use nix::sys::aio::*; + /// # use nix::sys::signal::SigevNotify::SigevNone; + /// # use std::{thread, time}; + /// # use std::os::unix::io::AsRawFd; + /// # use tempfile::tempfile; + /// # fn main() { + /// let f = tempfile().unwrap(); + /// let mut aiocb = AioCb::from_fd( f.as_raw_fd(), 0, SigevNone); + /// aiocb.fsync(AioFsyncMode::O_SYNC).expect("aio_fsync failed early"); + /// while (aiocb.error() == Err(Error::from(Errno::EINPROGRESS))) { + /// thread::sleep(time::Duration::from_millis(10)); + /// } + /// aiocb.aio_return().expect("aio_fsync failed late"); + /// # } + /// ``` pub fn from_fd(fd: RawFd, prio: libc::c_int, sigev_notify: SigevNotify) -> AioCb<'a> { let mut a = AioCb::common_init(fd, prio, sigev_notify); @@ -166,17 +223,65 @@ impl<'a> AioCb<'a> { } } - /// Constructs a new `AioCb`. + /// Constructs a new `AioCb` from a mutable slice. + /// + /// The resulting `AioCb` will be suitable for both read and write + /// operations, but only if the borrow checker can guarantee that the slice + /// will outlive the `AioCb`. That will usually be the case if the `AioCb` + /// is stack-allocated. If the borrow checker gives you trouble, try using + /// [`from_bytes_mut`](#method.from_bytes_mut) instead. /// - /// * `fd` File descriptor. Required for all aio functions. - /// * `offs` File offset - /// * `buf` A memory buffer - /// * `prio` If POSIX Prioritized IO is supported, then the operation will - /// be prioritized at the process's priority level minus `prio` - /// * `sigev_notify` Determines how you will be notified of event - /// completion. - /// * `opcode` This field is only used for `lio_listio`. It determines - /// which operation to use for this individual aiocb + /// # Parameters + /// + /// * `fd`: File descriptor. Required for all aio functions. + /// * `offs`: File offset + /// * `buf`: A memory buffer + /// * `prio`: If POSIX Prioritized IO is supported, then the + /// operation will be prioritized at the process's + /// priority level minus `prio` + /// * `sigev_notify`: Determines how you will be notified of event + /// completion. + /// * `opcode`: This field is only used for `lio_listio`. It + /// determines which operation to use for this individual + /// aiocb + /// + /// # Examples + /// + /// Create an `AioCb` from a mutable slice and read into it. + /// + /// ``` + /// # extern crate tempfile; + /// # extern crate nix; + /// # use nix::errno::Errno; + /// # use nix::Error; + /// # use nix::sys::aio::*; + /// # use nix::sys::signal::SigevNotify; + /// # use std::{thread, time}; + /// # use std::io::Write; + /// # use std::os::unix::io::AsRawFd; + /// # use tempfile::tempfile; + /// # fn main() { + /// const INITIAL: &[u8] = b"abcdef123456"; + /// const LEN: usize = 4; + /// let mut rbuf = vec![0; LEN]; + /// let mut f = tempfile().unwrap(); + /// f.write_all(INITIAL).unwrap(); + /// { + /// let mut aiocb = AioCb::from_mut_slice( f.as_raw_fd(), + /// 2, //offset + /// &mut rbuf, + /// 0, //priority + /// SigevNotify::SigevNone, + /// LioOpcode::LIO_NOP); + /// aiocb.read().unwrap(); + /// while (aiocb.error() == Err(Error::from(Errno::EINPROGRESS))) { + /// thread::sleep(time::Duration::from_millis(10)); + /// } + /// assert_eq!(aiocb.aio_return().unwrap() as usize, LEN); + /// } + /// assert_eq!(rbuf, b"cdef"); + /// # } + /// ``` pub fn from_mut_slice(fd: RawFd, offs: off_t, buf: &'a mut [u8], prio: libc::c_int, sigev_notify: SigevNotify, opcode: LioOpcode) -> AioCb<'a> { @@ -194,24 +299,62 @@ impl<'a> AioCb<'a> { } } - /// Constructs a new `AioCb`. + /// Constructs a new `AioCb` from a `Bytes` object. /// - /// Unlike `from_mut_slice`, this method returns a structure suitable for + /// Unlike `from_slice`, this method returns a structure suitable for /// placement on the heap. It may be used for write operations, but not /// read operations. /// - /// * `fd` File descriptor. Required for all aio functions. - /// * `offs` File offset - /// * `buf` A shared memory buffer - /// * `prio` If POSIX Prioritized IO is supported, then the operation will - /// be prioritized at the process's priority level minus `prio` - /// * `sigev_notify` Determines how you will be notified of event - /// completion. - /// * `opcode` This field is only used for `lio_listio`. It determines - /// which operation to use for this individual aiocb + /// # Parameters + /// + /// * `fd`: File descriptor. Required for all aio functions. + /// * `offs`: File offset + /// * `buf`: A shared memory buffer + /// * `prio`: If POSIX Prioritized IO is supported, then the + /// operation will be prioritized at the process's + /// priority level minus `prio` + /// * `sigev_notify`: Determines how you will be notified of event + /// completion. + /// * `opcode`: This field is only used for `lio_listio`. It + /// determines which operation to use for this individual + /// aiocb + /// + /// # Examples + /// + /// Create an `AioCb` from a `Bytes` object and use it for writing. + /// + /// ``` + /// # extern crate bytes; + /// # extern crate tempfile; + /// # extern crate nix; + /// # use nix::errno::Errno; + /// # use nix::Error; + /// # use bytes::Bytes; + /// # use nix::sys::aio::*; + /// # use nix::sys::signal::SigevNotify; + /// # use std::{thread, time}; + /// # use std::io::Write; + /// # use std::os::unix::io::AsRawFd; + /// # use tempfile::tempfile; + /// # fn main() { + /// let wbuf = Bytes::from(&b"CDEF"[..]); + /// let mut f = tempfile().unwrap(); + /// let mut aiocb = AioCb::from_bytes( f.as_raw_fd(), + /// 2, //offset + /// wbuf.clone(), + /// 0, //priority + /// SigevNotify::SigevNone, + /// LioOpcode::LIO_NOP); + /// aiocb.write().unwrap(); + /// while (aiocb.error() == Err(Error::from(Errno::EINPROGRESS))) { + /// thread::sleep(time::Duration::from_millis(10)); + /// } + /// assert_eq!(aiocb.aio_return().unwrap() as usize, wbuf.len()); + /// # } + /// ``` pub fn from_bytes(fd: RawFd, offs: off_t, buf: Bytes, prio: libc::c_int, sigev_notify: SigevNotify, - opcode: LioOpcode) -> AioCb<'a> { + opcode: LioOpcode) -> AioCb<'a> { // Small BytesMuts are stored inline. Inline storage is a no-no, // because we store a pointer to the buffer in the AioCb before // returning the Buffer by move. If the buffer is too small, reallocate @@ -240,20 +383,66 @@ impl<'a> AioCb<'a> { } } - /// Constructs a new `AioCb`. + /// Constructs a new `AioCb` from a `BytesMut` object. /// /// Unlike `from_mut_slice`, this method returns a structure suitable for /// placement on the heap. It may be used for both reads and writes. /// - /// * `fd` File descriptor. Required for all aio functions. - /// * `offs` File offset - /// * `buf` A shared memory buffer - /// * `prio` If POSIX Prioritized IO is supported, then the operation will - /// be prioritized at the process's priority level minus `prio` - /// * `sigev_notify` Determines how you will be notified of event - /// completion. - /// * `opcode` This field is only used for `lio_listio`. It determines - /// which operation to use for this individual aiocb + /// # Parameters + /// + /// * `fd`: File descriptor. Required for all aio functions. + /// * `offs`: File offset + /// * `buf`: An owned memory buffer + /// * `prio`: If POSIX Prioritized IO is supported, then the + /// operation will be prioritized at the process's + /// priority level minus `prio` + /// * `sigev_notify`: Determines how you will be notified of event + /// completion. + /// * `opcode`: This field is only used for `lio_listio`. It + /// determines which operation to use for this individual + /// aiocb + /// + /// # Examples + /// + /// Create an `AioCb` from a `BytesMut` and use it for reading. In this + /// example the `AioCb` is stack-allocated, so we could've used + /// `from_mut_slice` instead. + /// + /// ``` + /// # extern crate bytes; + /// # extern crate tempfile; + /// # extern crate nix; + /// # use nix::errno::Errno; + /// # use nix::Error; + /// # use bytes::BytesMut; + /// # use nix::sys::aio::*; + /// # use nix::sys::signal::SigevNotify; + /// # use std::{thread, time}; + /// # use std::io::Write; + /// # use std::os::unix::io::AsRawFd; + /// # use tempfile::tempfile; + /// # fn main() { + /// const INITIAL: &[u8] = b"abcdef123456"; + /// const LEN: usize = 4; + /// let rbuf = BytesMut::from(vec![0; LEN]); + /// let mut f = tempfile().unwrap(); + /// f.write_all(INITIAL).unwrap(); + /// let mut aiocb = AioCb::from_bytes_mut( f.as_raw_fd(), + /// 2, //offset + /// rbuf, + /// 0, //priority + /// SigevNotify::SigevNone, + /// LioOpcode::LIO_NOP); + /// aiocb.read().unwrap(); + /// while (aiocb.error() == Err(Error::from(Errno::EINPROGRESS))) { + /// thread::sleep(time::Duration::from_millis(10)); + /// } + /// assert_eq!(aiocb.aio_return().unwrap() as usize, LEN); + /// let buffer = aiocb.into_buffer(); + /// const EXPECT: &[u8] = b"cdef"; + /// assert_eq!(buffer.bytes_mut().unwrap(), EXPECT); + /// # } + /// ``` pub fn from_bytes_mut(fd: RawFd, offs: off_t, buf: BytesMut, prio: libc::c_int, sigev_notify: SigevNotify, opcode: LioOpcode) -> AioCb<'a> { @@ -281,22 +470,31 @@ impl<'a> AioCb<'a> { /// Constructs a new `AioCb` from a mutable raw pointer /// - /// * `fd` File descriptor. Required for all aio functions. - /// * `offs` File offset - /// * `buf` Pointer to the memory buffer - /// * `len` Length of the buffer pointed to by `buf` - /// * `prio` If POSIX Prioritized IO is supported, then the operation will - /// be prioritized at the process's priority level minus `prio` - /// * `sigev_notify` Determines how you will be notified of event - /// completion. - /// * `opcode` This field is only used for `lio_listio`. It determines - /// which operation to use for this individual aiocb + /// Unlike `from_mut_slice`, this method returns a structure suitable for + /// placement on the heap. It may be used for both reads and writes. Due + /// to its unsafety, this method is not recommended. It is most useful when + /// heap allocation is required but for some reason the data cannot be + /// converted to a `BytesMut`. + /// + /// # Parameters + /// + /// * `fd`: File descriptor. Required for all aio functions. + /// * `offs`: File offset + /// * `buf`: Pointer to the memory buffer + /// * `len`: Length of the buffer pointed to by `buf` + /// * `prio`: If POSIX Prioritized IO is supported, then the + /// operation will be prioritized at the process's + /// priority level minus `prio` + /// * `sigev_notify`: Determines how you will be notified of event + /// completion. + /// * `opcode`: This field is only used for `lio_listio`. It + /// determines which operation to use for this individual + /// aiocb /// /// # Safety /// - /// Unsafe because using this `AioCb` will cause `libc::aio_read` or - /// `libc::aio_write` to dereference a raw pointer, without type, bounds, or - /// lifetime checking. + /// The caller must ensure that the storage pointed to by `buf` outlives the + /// `AioCb`. The lifetime checker can't help here. pub unsafe fn from_mut_ptr(fd: RawFd, offs: off_t, buf: *mut c_void, len: usize, prio: libc::c_int, sigev_notify: SigevNotify, @@ -315,23 +513,32 @@ impl<'a> AioCb<'a> { } } - /// Constructs a new `AioCb` from a raw pointer + /// Constructs a new `AioCb` from a raw pointer. + /// + /// Unlike `from_slice`, this method returns a structure suitable for + /// placement on the heap. Due to its unsafety, this method is not + /// recommended. It is most useful when heap allocation is required but for + /// some reason the data cannot be converted to a `Bytes`. /// - /// * `fd` File descriptor. Required for all aio functions. - /// * `offs` File offset - /// * `buf` Pointer to the memory buffer - /// * `len` Length of the buffer pointed to by `buf` - /// * `prio` If POSIX Prioritized IO is supported, then the operation will - /// be prioritized at the process's priority level minus `prio` - /// * `sigev_notify` Determines how you will be notified of event - /// completion. - /// * `opcode` This field is only used for `lio_listio`. It determines - /// which operation to use for this individual aiocb + /// # Parameters + /// + /// * `fd`: File descriptor. Required for all aio functions. + /// * `offs`: File offset + /// * `buf`: Pointer to the memory buffer + /// * `len`: Length of the buffer pointed to by `buf` + /// * `prio`: If POSIX Prioritized IO is supported, then the + /// operation will be prioritized at the process's + /// priority level minus `prio` + /// * `sigev_notify`: Determines how you will be notified of event + /// completion. + /// * `opcode`: This field is only used for `lio_listio`. It + /// determines which operation to use for this individual + /// aiocb /// /// # Safety /// - /// Unsafe because using this `AioCb` will cause `libc::aio_write` to - /// dereference a raw pointer, without type, bounds, or lifetime checking. + /// The caller must ensure that the storage pointed to by `buf` outlives the + /// `AioCb`. The lifetime checker can't help here. pub unsafe fn from_ptr(fd: RawFd, offs: off_t, buf: *const c_void, len: usize, prio: libc::c_int, sigev_notify: SigevNotify, @@ -357,14 +564,44 @@ impl<'a> AioCb<'a> { /// /// An `AioCb` created this way cannot be used with `read`, and its /// `LioOpcode` cannot be set to `LIO_READ`. This method is useful when - /// writing a const buffer with `AioCb::write`, since from_mut_slice can't + /// writing a const buffer with `AioCb::write`, since `from_mut_slice` can't /// work with const buffers. + /// + /// # Examples + /// + /// Construct an `AioCb` from a slice and use it for writing. + /// + /// ``` + /// # extern crate tempfile; + /// # extern crate nix; + /// # use nix::errno::Errno; + /// # use nix::Error; + /// # use nix::sys::aio::*; + /// # use nix::sys::signal::SigevNotify; + /// # use std::{thread, time}; + /// # use std::os::unix::io::AsRawFd; + /// # use tempfile::tempfile; + /// # fn main() { + /// const WBUF: &[u8] = b"abcdef123456"; + /// let mut f = tempfile().unwrap(); + /// let mut aiocb = AioCb::from_slice( f.as_raw_fd(), + /// 2, //offset + /// WBUF, + /// 0, //priority + /// SigevNotify::SigevNone, + /// LioOpcode::LIO_NOP); + /// aiocb.write().unwrap(); + /// while (aiocb.error() == Err(Error::from(Errno::EINPROGRESS))) { + /// thread::sleep(time::Duration::from_millis(10)); + /// } + /// assert_eq!(aiocb.aio_return().unwrap() as usize, WBUF.len()); + /// # } + /// ``` // Note: another solution to the problem of writing const buffers would be // to genericize AioCb for both &mut [u8] and &[u8] buffers. AioCb::read // could take the former and AioCb::write could take the latter. However, // then lio_listio wouldn't work, because that function needs a slice of - // AioCb, and they must all be the same type. We're basically stuck with - // using an unsafe function, since aio (as designed in C) is an unsafe API. + // AioCb, and they must all be of the same type. pub fn from_slice(fd: RawFd, offs: off_t, buf: &'a [u8], prio: libc::c_int, sigev_notify: SigevNotify, opcode: LioOpcode) -> AioCb { @@ -418,6 +655,56 @@ impl<'a> AioCb<'a> { } /// Cancels an outstanding AIO request. + /// + /// The operating system is not required to implement cancellation for all + /// file and device types. Even if it does, there is no guarantee that the + /// operation has not already completed. So the caller must check the + /// result and handle operations that were not canceled or that have already + /// completed. + /// + /// # Examples + /// + /// Cancel an outstanding aio operation. Note that we must still call + /// `aio_return` to free resources, even though we don't care about the + /// result. + /// + /// ``` + /// # extern crate bytes; + /// # extern crate tempfile; + /// # extern crate nix; + /// # use nix::errno::Errno; + /// # use nix::Error; + /// # use bytes::Bytes; + /// # use nix::sys::aio::*; + /// # use nix::sys::signal::SigevNotify; + /// # use std::{thread, time}; + /// # use std::io::Write; + /// # use std::os::unix::io::AsRawFd; + /// # use tempfile::tempfile; + /// # fn main() { + /// let wbuf = Bytes::from(&b"CDEF"[..]); + /// let mut f = tempfile().unwrap(); + /// let mut aiocb = AioCb::from_bytes( f.as_raw_fd(), + /// 2, //offset + /// wbuf.clone(), + /// 0, //priority + /// SigevNotify::SigevNone, + /// LioOpcode::LIO_NOP); + /// aiocb.write().unwrap(); + /// let cs = aiocb.cancel().unwrap(); + /// if cs == AioCancelStat::AioNotCanceled { + /// while (aiocb.error() == Err(Error::from(Errno::EINPROGRESS))) { + /// thread::sleep(time::Duration::from_millis(10)); + /// } + /// } + /// // Must call `aio_return`, but ignore the result + /// let _ = aiocb.aio_return(); + /// # } + /// ``` + /// + /// # References + /// + /// [aio_cancel](http://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_cancel.html) pub fn cancel(&mut self) -> Result { match unsafe { libc::aio_cancel(self.aiocb.aio_fildes, &mut self.aiocb) } { libc::AIO_CANCELED => Ok(AioCancelStat::AioCanceled), @@ -428,9 +715,46 @@ impl<'a> AioCb<'a> { } } - /// Retrieve error status of an asynchronous operation. If the request has - /// not yet completed, returns `EINPROGRESS`. Otherwise, returns `Ok` or - /// any other error. + /// Retrieve error status of an asynchronous operation. + /// + /// If the request has not yet completed, returns `EINPROGRESS`. Otherwise, + /// returns `Ok` or any other error. + /// + /// # Examples + /// + /// Issue an aio operation and use `error` to poll for completion. Polling + /// is an alternative to `aio_suspend`, used by most of the other examples. + /// + /// ``` + /// # extern crate tempfile; + /// # extern crate nix; + /// # use nix::errno::Errno; + /// # use nix::Error; + /// # use nix::sys::aio::*; + /// # use nix::sys::signal::SigevNotify; + /// # use std::{thread, time}; + /// # use std::os::unix::io::AsRawFd; + /// # use tempfile::tempfile; + /// # fn main() { + /// const WBUF: &[u8] = b"abcdef123456"; + /// let mut f = tempfile().unwrap(); + /// let mut aiocb = AioCb::from_slice( f.as_raw_fd(), + /// 2, //offset + /// WBUF, + /// 0, //priority + /// SigevNotify::SigevNone, + /// LioOpcode::LIO_NOP); + /// aiocb.write().unwrap(); + /// while (aiocb.error() == Err(Error::from(Errno::EINPROGRESS))) { + /// thread::sleep(time::Duration::from_millis(10)); + /// } + /// assert_eq!(aiocb.aio_return().unwrap() as usize, WBUF.len()); + /// # } + /// ``` + /// + /// # References + /// + /// [aio_error](http://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_error.html) pub fn error(&mut self) -> Result<()> { match unsafe { libc::aio_error(&mut self.aiocb as *mut libc::aiocb) } { 0 => Ok(()), @@ -440,7 +764,11 @@ impl<'a> AioCb<'a> { } } - /// An asynchronous version of `fsync`. + /// An asynchronous version of `fsync(2)`. + /// + /// # References + /// + /// [aio_fsync](http://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_fsync.html) pub fn fsync(&mut self, mode: AioFsyncMode) -> Result<()> { let p: *mut libc::aiocb = &mut self.aiocb; Errno::result(unsafe { @@ -483,6 +811,10 @@ impl<'a> AioCb<'a> { } /// Asynchronously reads from a file descriptor into a buffer + /// + /// # References + /// + /// [aio_read](http://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_read.html) pub fn read(&mut self) -> Result<()> { assert!(self.mutable, "Can't read into an immutable buffer"); let p: *mut libc::aiocb = &mut self.aiocb; @@ -498,9 +830,15 @@ impl<'a> AioCb<'a> { SigEvent::from(&self.aiocb.aio_sigevent) } - /// Retrieve return status of an asynchronous operation. Should only be - /// called once for each `AioCb`, after `AioCb::error` indicates that it has - /// completed. The result is the same as for `read`, `write`, of `fsync`. + /// Retrieve return status of an asynchronous operation. + /// + /// Should only be called once for each `AioCb`, after `AioCb::error` + /// indicates that it has completed. The result is the same as for the + /// synchronous `read(2)`, `write(2)`, of `fsync(2)` functions. + /// + /// # References + /// + /// [aio_return](http://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_return.html) // Note: this should be just `return`, but that's a reserved word pub fn aio_return(&mut self) -> Result { let p: *mut libc::aiocb = &mut self.aiocb; @@ -509,6 +847,10 @@ impl<'a> AioCb<'a> { } /// Asynchronously writes from a buffer to a file descriptor + /// + /// # References + /// + /// [aio_write](http://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_write.html) pub fn write(&mut self) -> Result<()> { let p: *mut libc::aiocb = &mut self.aiocb; Errno::result(unsafe { @@ -520,7 +862,50 @@ impl<'a> AioCb<'a> { } -/// Cancels outstanding AIO requests. All requests for `fd` will be cancelled. +/// Cancels outstanding AIO requests for a given file descriptor. +/// +/// # Examples +/// +/// Issue an aio operation, then cancel all outstanding operations on that file +/// descriptor. +/// +/// ``` +/// # extern crate bytes; +/// # extern crate tempfile; +/// # extern crate nix; +/// # use nix::errno::Errno; +/// # use nix::Error; +/// # use bytes::Bytes; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use std::{thread, time}; +/// # use std::io::Write; +/// # use std::os::unix::io::AsRawFd; +/// # use tempfile::tempfile; +/// # fn main() { +/// let wbuf = Bytes::from(&b"CDEF"[..]); +/// let mut f = tempfile().unwrap(); +/// let mut aiocb = AioCb::from_bytes( f.as_raw_fd(), +/// 2, //offset +/// wbuf.clone(), +/// 0, //priority +/// SigevNotify::SigevNone, +/// LioOpcode::LIO_NOP); +/// aiocb.write().unwrap(); +/// let cs = aio_cancel_all(f.as_raw_fd()).unwrap(); +/// if cs == AioCancelStat::AioNotCanceled { +/// while (aiocb.error() == Err(Error::from(Errno::EINPROGRESS))) { +/// thread::sleep(time::Duration::from_millis(10)); +/// } +/// } +/// // Must call `aio_return`, but ignore the result +/// let _ = aiocb.aio_return(); +/// # } +/// ``` +/// +/// # References +/// +/// [`aio_cancel`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_cancel.html) pub fn aio_cancel_all(fd: RawFd) -> Result { match unsafe { libc::aio_cancel(fd, null_mut()) } { libc::AIO_CANCELED => Ok(AioCancelStat::AioCanceled), @@ -532,8 +917,42 @@ pub fn aio_cancel_all(fd: RawFd) -> Result { } /// Suspends the calling process until at least one of the specified `AioCb`s -/// has completed, a signal is delivered, or the timeout has passed. If -/// `timeout` is `None`, `aio_suspend` will block indefinitely. +/// has completed, a signal is delivered, or the timeout has passed. +/// +/// If `timeout` is `None`, `aio_suspend` will block indefinitely. +/// +/// # Examples +/// +/// Use `aio_suspend` to block until an aio operation completes. +/// +// Disable doctest due to a known bug in FreeBSD's 32-bit emulation. The fix +// will be included in release 11.2. +// FIXME reenable the doc test when the CI machine gets upgraded to that release. +// https://svnweb.freebsd.org/base?view=revision&revision=325018 +/// ```no_run +/// # extern crate tempfile; +/// # extern crate nix; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use std::os::unix::io::AsRawFd; +/// # use tempfile::tempfile; +/// # fn main() { +/// const WBUF: &[u8] = b"abcdef123456"; +/// let mut f = tempfile().unwrap(); +/// let mut aiocb = AioCb::from_slice( f.as_raw_fd(), +/// 2, //offset +/// WBUF, +/// 0, //priority +/// SigevNotify::SigevNone, +/// LioOpcode::LIO_NOP); +/// aiocb.write().unwrap(); +/// aio_suspend(&[&aiocb], None).expect("aio_suspend failed"); +/// assert_eq!(aiocb.aio_return().unwrap() as usize, WBUF.len()); +/// # } +/// ``` +/// # References +/// +/// [`aio_suspend`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_suspend.html) pub fn aio_suspend(list: &[&AioCb], timeout: Option) -> Result<()> { let plist = list as *const [&AioCb] as *const [*const libc::aiocb]; let p = plist as *const *const libc::aiocb; @@ -547,8 +966,50 @@ pub fn aio_suspend(list: &[&AioCb], timeout: Option) -> Result<()> { } -/// Submits multiple asynchronous I/O requests with a single system call. The -/// order in which the requests are carried out is not specified. +/// Submits multiple asynchronous I/O requests with a single system call. +/// +/// They are not guaranteed to complete atomically, and the order in which the +/// requests are carried out is not specified. Reads, writes, and fsyncs may be +/// freely mixed. +/// +/// This function is useful for reducing the context-switch overhead of +/// submitting many AIO operations. It can also be used with +/// `LioMode::LIO_WAIT` to block on the result of several independent +/// operations. Used that way, it is often useful in programs that otherwise +/// make little use of AIO. +/// +/// # Examples +/// +/// Use `lio_listio` to submit an aio operation and wait for its completion. In +/// this case, there is no need to use `aio_suspend` to wait or `AioCb#error` to +/// poll. +/// +/// ``` +/// # extern crate tempfile; +/// # extern crate nix; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use std::os::unix::io::AsRawFd; +/// # use tempfile::tempfile; +/// # fn main() { +/// const WBUF: &[u8] = b"abcdef123456"; +/// let mut f = tempfile().unwrap(); +/// let mut aiocb = AioCb::from_slice( f.as_raw_fd(), +/// 2, //offset +/// WBUF, +/// 0, //priority +/// SigevNotify::SigevNone, +/// LioOpcode::LIO_WRITE); +/// lio_listio(LioMode::LIO_WAIT, +/// &[&mut aiocb], +/// SigevNotify::SigevNone).unwrap(); +/// assert_eq!(aiocb.aio_return().unwrap() as usize, WBUF.len()); +/// # } +/// ``` +/// +/// # References +/// +/// [`lio_listio`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/lio_listio.html) #[cfg(not(any(target_os = "ios", target_os = "macos")))] pub fn lio_listio(mode: LioMode, list: &[&mut AioCb], sigev_notify: SigevNotify) -> Result<()> { diff --git a/test/sys/test_aio.rs b/test/sys/test_aio.rs index ed9d37619b..f88fc2688d 100644 --- a/test/sys/test_aio.rs +++ b/test/sys/test_aio.rs @@ -43,8 +43,8 @@ fn test_accessors() { assert_eq!(99, sev.sigev_value.sival_ptr as i64); } -// Tests AioCb.cancel. We aren't trying to test the OS's implementation, only our -// bindings. So it's sufficient to check that AioCb.cancel returned any +// Tests AioCb.cancel. We aren't trying to test the OS's implementation, only +// our bindings. So it's sufficient to check that AioCb.cancel returned any // AioCancelStat value. #[test] #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]