blob: ba5ad02ec39b828ee4b8d451fda3b3b1d1d39bb3 [file] [log] [blame] [edit]
use std::{
io::{Read, Seek, Write},
ops::Deref,
os::unix::io::AsRawFd,
pin::Pin,
sync::atomic::{AtomicBool, Ordering},
thread, time,
};
use libc::c_int;
use nix::{
errno::*,
sys::{
aio::*,
signal::{
sigaction, SaFlags, SigAction, SigHandler, SigSet, SigevNotify,
Signal,
},
time::{TimeSpec, TimeValLike},
},
};
use tempfile::tempfile;
pub static SIGNALED: AtomicBool = AtomicBool::new(false);
extern "C" fn sigfunc(_: c_int) {
SIGNALED.store(true, Ordering::Relaxed);
}
// Helper that polls an AioCb for completion or error
macro_rules! poll_aio {
($aiocb: expr) => {
loop {
let err = $aiocb.as_mut().error();
if err != Err(Errno::EINPROGRESS) {
break err;
};
thread::sleep(time::Duration::from_millis(10));
}
};
}
mod aio_fsync {
use super::*;
#[test]
fn test_accessors() {
let aiocb = AioFsync::new(
1001,
AioFsyncMode::O_SYNC,
42,
SigevNotify::SigevSignal {
signal: Signal::SIGUSR2,
si_value: 99,
},
);
assert_eq!(1001, aiocb.fd());
assert_eq!(AioFsyncMode::O_SYNC, aiocb.mode());
assert_eq!(42, aiocb.priority());
let sev = aiocb.sigevent().sigevent();
assert_eq!(Signal::SIGUSR2 as i32, sev.sigev_signo);
assert_eq!(99, sev.sigev_value.sival_ptr as i64);
}
/// `AioFsync::submit` 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
#[test]
#[cfg(any(target_os = "freebsd", apple_targets))]
fn error() {
use std::mem;
const INITIAL: &[u8] = b"abcdef123456";
// Create an invalid AioFsyncMode
let mode = unsafe { mem::transmute(666) };
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
let mut aiof = Box::pin(AioFsync::new(
f.as_raw_fd(),
mode,
0,
SigevNotify::SigevNone,
));
let err = aiof.as_mut().submit();
err.expect_err("assertion failed");
}
#[test]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
fn ok() {
const INITIAL: &[u8] = b"abcdef123456";
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
let fd = f.as_raw_fd();
let mut aiof = Box::pin(AioFsync::new(
fd,
AioFsyncMode::O_SYNC,
0,
SigevNotify::SigevNone,
));
aiof.as_mut().submit().unwrap();
poll_aio!(&mut aiof).unwrap();
aiof.as_mut().aio_return().unwrap();
}
}
mod aio_read {
use super::*;
#[test]
fn test_accessors() {
let mut rbuf = vec![0; 4];
let aiocb = AioRead::new(
1001,
2, //offset
&mut rbuf,
42, //priority
SigevNotify::SigevSignal {
signal: Signal::SIGUSR2,
si_value: 99,
},
);
assert_eq!(1001, aiocb.fd());
assert_eq!(4, aiocb.nbytes());
assert_eq!(2, aiocb.offset());
assert_eq!(42, aiocb.priority());
let sev = aiocb.sigevent().sigevent();
assert_eq!(Signal::SIGUSR2 as i32, sev.sigev_signo);
assert_eq!(99, sev.sigev_value.sival_ptr as i64);
}
// Tests AioWrite.cancel. We aren't trying to test the OS's implementation,
// only our bindings. So it's sufficient to check that cancel
// returned any AioCancelStat value.
#[test]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
fn cancel() {
const INITIAL: &[u8] = b"abcdef123456";
let mut rbuf = vec![0; 4];
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
let fd = f.as_raw_fd();
let mut aior =
Box::pin(AioRead::new(fd, 2, &mut rbuf, 0, SigevNotify::SigevNone));
aior.as_mut().submit().unwrap();
aior.as_mut().cancel().unwrap();
// Wait for aiow to complete, but don't care whether it succeeded
let _ = poll_aio!(&mut aior);
let _ = aior.as_mut().aio_return();
}
/// `AioRead::submit` 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]
#[cfg(any(target_os = "freebsd", apple_targets))]
fn error() {
const INITIAL: &[u8] = b"abcdef123456";
let mut rbuf = vec![0; 4];
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
let mut aior = Box::pin(AioRead::new(
f.as_raw_fd(),
-1, //an invalid offset
&mut rbuf,
0, //priority
SigevNotify::SigevNone,
));
aior.as_mut().submit().expect_err("assertion failed");
}
// Test a simple aio operation with no completion notification. We must
// poll for completion
#[test]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
fn ok() {
const INITIAL: &[u8] = b"abcdef123456";
let mut rbuf = vec![0; 4];
const EXPECT: &[u8] = b"cdef";
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
{
let fd = f.as_raw_fd();
let mut aior = Box::pin(AioRead::new(
fd,
2,
&mut rbuf,
0,
SigevNotify::SigevNone,
));
aior.as_mut().submit().unwrap();
let err = poll_aio!(&mut aior);
assert_eq!(err, Ok(()));
assert_eq!(aior.as_mut().aio_return().unwrap(), EXPECT.len());
}
assert_eq!(EXPECT, rbuf.deref());
}
// Like ok, but allocates the structure on the stack.
#[test]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
fn on_stack() {
const INITIAL: &[u8] = b"abcdef123456";
let mut rbuf = vec![0; 4];
const EXPECT: &[u8] = b"cdef";
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
{
let fd = f.as_raw_fd();
let mut aior =
AioRead::new(fd, 2, &mut rbuf, 0, SigevNotify::SigevNone);
let mut aior = unsafe { Pin::new_unchecked(&mut aior) };
aior.as_mut().submit().unwrap();
let err = poll_aio!(&mut aior);
assert_eq!(err, Ok(()));
assert_eq!(aior.as_mut().aio_return().unwrap(), EXPECT.len());
}
assert_eq!(EXPECT, rbuf.deref());
}
}
#[cfg(target_os = "freebsd")]
#[cfg(fbsd14)]
mod aio_readv {
use std::io::IoSliceMut;
use super::*;
#[test]
fn test_accessors() {
let mut rbuf0 = vec![0; 4];
let mut rbuf1 = vec![0; 8];
let mut rbufs =
[IoSliceMut::new(&mut rbuf0), IoSliceMut::new(&mut rbuf1)];
let aiocb = AioReadv::new(
1001,
2, //offset
&mut rbufs,
42, //priority
SigevNotify::SigevSignal {
signal: Signal::SIGUSR2,
si_value: 99,
},
);
assert_eq!(1001, aiocb.fd());
assert_eq!(2, aiocb.iovlen());
assert_eq!(2, aiocb.offset());
assert_eq!(42, aiocb.priority());
let sev = aiocb.sigevent().sigevent();
assert_eq!(Signal::SIGUSR2 as i32, sev.sigev_signo);
assert_eq!(99, sev.sigev_value.sival_ptr as i64);
}
#[test]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
fn ok() {
const INITIAL: &[u8] = b"abcdef123456";
let mut rbuf0 = vec![0; 4];
let mut rbuf1 = vec![0; 2];
let mut rbufs =
[IoSliceMut::new(&mut rbuf0), IoSliceMut::new(&mut rbuf1)];
const EXPECT0: &[u8] = b"cdef";
const EXPECT1: &[u8] = b"12";
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
{
let fd = f.as_raw_fd();
let mut aior = Box::pin(AioReadv::new(
fd,
2,
&mut rbufs,
0,
SigevNotify::SigevNone,
));
aior.as_mut().submit().unwrap();
let err = poll_aio!(&mut aior);
assert_eq!(err, Ok(()));
assert_eq!(
aior.as_mut().aio_return().unwrap(),
EXPECT0.len() + EXPECT1.len()
);
}
assert_eq!(&EXPECT0, &rbuf0);
assert_eq!(&EXPECT1, &rbuf1);
}
}
mod aio_write {
use super::*;
#[test]
fn test_accessors() {
let wbuf = vec![0; 4];
let aiocb = AioWrite::new(
1001,
2, //offset
&wbuf,
42, //priority
SigevNotify::SigevSignal {
signal: Signal::SIGUSR2,
si_value: 99,
},
);
assert_eq!(1001, aiocb.fd());
assert_eq!(4, aiocb.nbytes());
assert_eq!(2, aiocb.offset());
assert_eq!(42, aiocb.priority());
let sev = aiocb.sigevent().sigevent();
assert_eq!(Signal::SIGUSR2 as i32, sev.sigev_signo);
assert_eq!(99, sev.sigev_value.sival_ptr as i64);
}
// Tests AioWrite.cancel. We aren't trying to test the OS's implementation,
// only our bindings. So it's sufficient to check that cancel
// returned any AioCancelStat value.
#[test]
#[cfg_attr(target_env = "musl", ignore)]
fn cancel() {
let wbuf: &[u8] = b"CDEF";
let f = tempfile().unwrap();
let mut aiow = Box::pin(AioWrite::new(
f.as_raw_fd(),
0,
wbuf,
0,
SigevNotify::SigevNone,
));
aiow.as_mut().submit().unwrap();
let err = aiow.as_mut().error();
assert!(err == Ok(()) || err == Err(Errno::EINPROGRESS));
aiow.as_mut().cancel().unwrap();
// Wait for aiow to complete, but don't care whether it succeeded
let _ = poll_aio!(&mut aiow);
let _ = aiow.as_mut().aio_return();
}
// Test a simple aio operation with no completion notification. We must
// poll for completion.
#[test]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
fn ok() {
const INITIAL: &[u8] = b"abcdef123456";
let wbuf = "CDEF".to_string().into_bytes();
let mut rbuf = Vec::new();
const EXPECT: &[u8] = b"abCDEF123456";
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
let mut aiow = Box::pin(AioWrite::new(
f.as_raw_fd(),
2,
&wbuf,
0,
SigevNotify::SigevNone,
));
aiow.as_mut().submit().unwrap();
let err = poll_aio!(&mut aiow);
assert_eq!(err, Ok(()));
assert_eq!(aiow.as_mut().aio_return().unwrap(), wbuf.len());
f.rewind().unwrap();
let len = f.read_to_end(&mut rbuf).unwrap();
assert_eq!(len, EXPECT.len());
assert_eq!(rbuf, EXPECT);
}
// Like ok, but allocates the structure on the stack.
#[test]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
fn on_stack() {
const INITIAL: &[u8] = b"abcdef123456";
let wbuf = "CDEF".to_string().into_bytes();
let mut rbuf = Vec::new();
const EXPECT: &[u8] = b"abCDEF123456";
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
let mut aiow = AioWrite::new(
f.as_raw_fd(),
2, //offset
&wbuf,
0, //priority
SigevNotify::SigevNone,
);
let mut aiow = unsafe { Pin::new_unchecked(&mut aiow) };
aiow.as_mut().submit().unwrap();
let err = poll_aio!(&mut aiow);
assert_eq!(err, Ok(()));
assert_eq!(aiow.as_mut().aio_return().unwrap(), wbuf.len());
f.rewind().unwrap();
let len = f.read_to_end(&mut rbuf).unwrap();
assert_eq!(len, EXPECT.len());
assert_eq!(rbuf, EXPECT);
}
/// `AioWrite::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]
#[cfg(any(target_os = "freebsd", apple_targets))]
fn error() {
let wbuf = "CDEF".to_string().into_bytes();
let mut aiow = Box::pin(AioWrite::new(
666, // An invalid file descriptor
0, //offset
&wbuf,
0, //priority
SigevNotify::SigevNone,
));
aiow.as_mut().submit().expect_err("assertion failed");
// Dropping the AioWrite at this point should not panic
}
}
#[cfg(target_os = "freebsd")]
#[cfg(fbsd14)]
mod aio_writev {
use std::io::IoSlice;
use super::*;
#[test]
fn test_accessors() {
let wbuf0 = vec![0; 4];
let wbuf1 = vec![0; 8];
let wbufs = [IoSlice::new(&wbuf0), IoSlice::new(&wbuf1)];
let aiocb = AioWritev::new(
1001,
2, //offset
&wbufs,
42, //priority
SigevNotify::SigevSignal {
signal: Signal::SIGUSR2,
si_value: 99,
},
);
assert_eq!(1001, aiocb.fd());
assert_eq!(2, aiocb.iovlen());
assert_eq!(2, aiocb.offset());
assert_eq!(42, aiocb.priority());
let sev = aiocb.sigevent().sigevent();
assert_eq!(Signal::SIGUSR2 as i32, sev.sigev_signo);
assert_eq!(99, sev.sigev_value.sival_ptr as i64);
}
// Test a simple aio operation with no completion notification. We must
// poll for completion.
#[test]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
fn ok() {
const INITIAL: &[u8] = b"abcdef123456";
let wbuf0 = b"BC";
let wbuf1 = b"DEF";
let wbufs = [IoSlice::new(wbuf0), IoSlice::new(wbuf1)];
let wlen = wbuf0.len() + wbuf1.len();
let mut rbuf = Vec::new();
const EXPECT: &[u8] = b"aBCDEF123456";
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
let mut aiow = Box::pin(AioWritev::new(
f.as_raw_fd(),
1,
&wbufs,
0,
SigevNotify::SigevNone,
));
aiow.as_mut().submit().unwrap();
let err = poll_aio!(&mut aiow);
assert_eq!(err, Ok(()));
assert_eq!(aiow.as_mut().aio_return().unwrap(), wlen);
f.rewind().unwrap();
let len = f.read_to_end(&mut rbuf).unwrap();
assert_eq!(len, EXPECT.len());
assert_eq!(rbuf, EXPECT);
}
}
// Test an aio operation with completion delivered by a signal
#[test]
#[cfg_attr(
any(
all(target_env = "musl", target_arch = "x86_64"),
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6"
),
ignore
)]
fn sigev_signal() {
let _m = crate::SIGNAL_MTX.lock();
let sa = SigAction::new(
SigHandler::Handler(sigfunc),
SaFlags::SA_RESETHAND,
SigSet::empty(),
);
SIGNALED.store(false, Ordering::Relaxed);
unsafe { sigaction(Signal::SIGUSR2, &sa) }.unwrap();
const INITIAL: &[u8] = b"abcdef123456";
const WBUF: &[u8] = b"CDEF";
let mut rbuf = Vec::new();
const EXPECT: &[u8] = b"abCDEF123456";
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
let mut aiow = Box::pin(AioWrite::new(
f.as_raw_fd(),
2, //offset
WBUF,
0, //priority
SigevNotify::SigevSignal {
signal: Signal::SIGUSR2,
si_value: 0, //TODO: validate in sigfunc
},
));
aiow.as_mut().submit().unwrap();
while !SIGNALED.load(Ordering::Relaxed) {
thread::sleep(time::Duration::from_millis(10));
}
assert_eq!(aiow.as_mut().aio_return().unwrap(), WBUF.len());
f.rewind().unwrap();
let len = f.read_to_end(&mut rbuf).unwrap();
assert_eq!(len, EXPECT.len());
assert_eq!(rbuf, EXPECT);
}
// Tests using aio_cancel_all for all outstanding IOs.
#[test]
#[cfg_attr(target_env = "musl", ignore)]
fn test_aio_cancel_all() {
let wbuf: &[u8] = b"CDEF";
let f = tempfile().unwrap();
let mut aiocb = Box::pin(AioWrite::new(
f.as_raw_fd(),
0, //offset
wbuf,
0, //priority
SigevNotify::SigevNone,
));
aiocb.as_mut().submit().unwrap();
let err = aiocb.as_mut().error();
assert!(err == Ok(()) || err == Err(Errno::EINPROGRESS));
aio_cancel_all(f.as_raw_fd()).unwrap();
// Wait for aiocb to complete, but don't care whether it succeeded
let _ = poll_aio!(&mut aiocb);
let _ = aiocb.as_mut().aio_return();
}
#[test]
fn test_aio_suspend() {
const INITIAL: &[u8] = b"abcdef123456";
const WBUF: &[u8] = b"CDEFG";
let timeout = TimeSpec::seconds(10);
let mut rbuf = vec![0; 4];
let rlen = rbuf.len();
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
let mut wcb = Box::pin(AioWrite::new(
f.as_raw_fd(),
2, //offset
WBUF,
0, //priority
SigevNotify::SigevNone,
));
let mut rcb = Box::pin(AioRead::new(
f.as_raw_fd(),
8, //offset
&mut rbuf,
0, //priority
SigevNotify::SigevNone,
));
wcb.as_mut().submit().unwrap();
rcb.as_mut().submit().unwrap();
loop {
{
let cbbuf = [
&*wcb as &dyn AsRef<libc::aiocb>,
&*rcb as &dyn AsRef<libc::aiocb>,
];
let r = aio_suspend(&cbbuf[..], Some(timeout));
match r {
Err(Errno::EINTR) => continue,
Err(e) => panic!("aio_suspend returned {e:?}"),
Ok(_) => (),
};
}
if rcb.as_mut().error() != Err(Errno::EINPROGRESS)
&& wcb.as_mut().error() != Err(Errno::EINPROGRESS)
{
break;
}
}
assert_eq!(wcb.as_mut().aio_return().unwrap(), WBUF.len());
assert_eq!(rcb.as_mut().aio_return().unwrap(), rlen);
}
/// aio_suspend relies on casting Rust Aio* struct pointers to libc::aiocb
/// pointers. This test ensures that such casts are valid.
#[test]
fn casting() {
let sev = SigevNotify::SigevNone;
let aiof = AioFsync::new(666, AioFsyncMode::O_SYNC, 0, sev);
assert_eq!(
aiof.as_ref() as *const libc::aiocb,
&aiof as *const AioFsync as *const libc::aiocb
);
let mut rbuf = [];
let aior = AioRead::new(666, 0, &mut rbuf, 0, sev);
assert_eq!(
aior.as_ref() as *const libc::aiocb,
&aior as *const AioRead as *const libc::aiocb
);
let wbuf = [];
let aiow = AioWrite::new(666, 0, &wbuf, 0, sev);
assert_eq!(
aiow.as_ref() as *const libc::aiocb,
&aiow as *const AioWrite as *const libc::aiocb
);
}
#[cfg(target_os = "freebsd")]
#[test]
fn casting_vectored() {
use std::io::{IoSlice, IoSliceMut};
let sev = SigevNotify::SigevNone;
let mut rbuf = [];
let mut rbufs = [IoSliceMut::new(&mut rbuf)];
let aiorv = AioReadv::new(666, 0, &mut rbufs[..], 0, sev);
assert_eq!(
aiorv.as_ref() as *const libc::aiocb,
&aiorv as *const AioReadv as *const libc::aiocb
);
let wbuf = [];
let wbufs = [IoSlice::new(&wbuf)];
let aiowv = AioWritev::new(666, 0, &wbufs, 0, sev);
assert_eq!(
aiowv.as_ref() as *const libc::aiocb,
&aiowv as *const AioWritev as *const libc::aiocb
);
}