blob: 98bc2cf19c0be0e94057664b6747477739192c51 [file] [log] [blame]
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause
//! Safe wrapper over [`Linux AIO`](http://man7.org/linux/man-pages/man7/aio.7.html).
#![allow(non_camel_case_types)]
/* automatically generated by rust-bindgen from file linux/include/uapi/linux/aio_abi.h
* of commit 69973b8 and then manually edited */
use std::io::{Error, Result};
use std::os::raw::{c_int, c_long, c_uint, c_ulong};
use std::ptr::null_mut;
type __s16 = ::std::os::raw::c_short;
type __u16 = ::std::os::raw::c_ushort;
type __u32 = ::std::os::raw::c_uint;
type __s64 = ::std::os::raw::c_longlong;
type __u64 = ::std::os::raw::c_ulonglong;
/// Read from a file descriptor at a given offset.
pub const IOCB_CMD_PREAD: u32 = 0;
/// Write to a file descriptor at a given offset.
pub const IOCB_CMD_PWRITE: u32 = 1;
/// Synchronize a file's in-core metadata and data to storage device.
pub const IOCB_CMD_FSYNC: u32 = 2;
/// Synchronize a file's in-core data to storage device.
pub const IOCB_CMD_FDSYNC: u32 = 3;
/// Noop, this defined by never used by linux kernel.
pub const IOCB_CMD_NOOP: u32 = 6;
/// Read from a file descriptor at a given offset into multiple buffers.
pub const IOCB_CMD_PREADV: u32 = 7;
/// Write to a file descriptor at a given offset from multiple buffers.
pub const IOCB_CMD_PWRITEV: u32 = 8;
/// Valid flags for the "aio_flags" member of the "struct iocb".
/// Set if the "aio_resfd" member of the "struct iocb" is valid.
pub const IOCB_FLAG_RESFD: u32 = 1;
/// Maximum number of concurrent requests.
pub const MAX_REQUESTS: usize = 0x10000;
/// Wrapper over the [`iocb`](https://elixir.bootlin.com/linux/v4.9/source/include/uapi/linux/aio_abi.h#L79) structure.
#[allow(missing_docs)]
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct IoControlBlock {
pub aio_data: __u64,
pub aio_key: __u32,
pub aio_reserved1: __u32,
pub aio_lio_opcode: __u16,
pub aio_reqprio: __s16,
pub aio_fildes: __u32,
pub aio_buf: __u64,
pub aio_nbytes: __u64,
pub aio_offset: __s64,
pub aio_reserved2: __u64,
pub aio_flags: __u32,
pub aio_resfd: __u32,
}
/// Wrapper over the [`io_event`](https://elixir.bootlin.com/linux/v4.9/source/include/uapi/linux/aio_abi.h#L58) structure.
#[allow(missing_docs)]
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct IoEvent {
pub data: __u64,
pub obj: __u64,
pub res: __s64,
pub res2: __s64,
}
/// Newtype for [`aio_context_t`](https://elixir.bootlin.com/linux/v4.9/source/include/uapi/linux/aio_abi.h#L33).
#[repr(transparent)]
#[derive(Debug)]
pub struct IoContext(::std::os::raw::c_ulong);
impl IoContext {
/// Create a new aio context instance.
///
/// Refer to Linux [`io_setup`](http://man7.org/linux/man-pages/man2/io_setup.2.html).
///
/// # Arguments
/// * `nr_events`: maximum number of concurrently processing IO operations.
#[allow(clippy::new_ret_no_self)]
pub fn new(nr_events: c_uint) -> Result<Self> {
if nr_events as usize > MAX_REQUESTS {
return Err(Error::from_raw_os_error(libc::EINVAL));
}
let mut ctx = IoContext(0);
let rc =
// SAFETY: Safe because we use valid parameters and check the result.
unsafe { libc::syscall(libc::SYS_io_setup, nr_events, &mut ctx as *mut Self) as c_int };
if rc < 0 {
Err(Error::last_os_error())
} else {
Ok(ctx)
}
}
/// Submit asynchronous I/O blocks for processing.
///
/// Refer to Linux [`io_submit`](http://man7.org/linux/man-pages/man2/io_submit.2.html).
///
/// # Arguments
/// * `iocbs`: array of AIO control blocks, which will be submitted to the context.
///
/// # Examples
/// ```
/// extern crate vmm_sys_util;
/// use vmm_sys_util::aio::*;
/// # use std::fs::File;
/// # use std::os::unix::io::AsRawFd;
///
/// let file = File::open("/dev/zero").unwrap();
/// let ctx = IoContext::new(128).unwrap();
/// let mut buf: [u8; 4096] = unsafe { std::mem::uninitialized() };
/// let iocbs = [&mut IoControlBlock {
/// aio_fildes: file.as_raw_fd() as u32,
/// aio_lio_opcode: IOCB_CMD_PREAD as u16,
/// aio_buf: buf.as_mut_ptr() as u64,
/// aio_nbytes: buf.len() as u64,
/// ..Default::default()
/// }];
/// assert_eq!(ctx.submit(&iocbs[..]).unwrap(), 1);
/// ```
pub fn submit(&self, iocbs: &[&mut IoControlBlock]) -> Result<usize> {
// SAFETY: It's safe because parameters are valid and we have checked the result.
let rc = unsafe {
libc::syscall(
libc::SYS_io_submit,
self.0,
iocbs.len() as c_ulong,
iocbs.as_ptr(),
) as c_int
};
if rc < 0 {
Err(Error::last_os_error())
} else {
Ok(rc as usize)
}
}
/// Cancel an outstanding asynchronous I/O operation.
///
/// Refer to Linux [`io_cancel`](http://man7.org/linux/man-pages/man2/io_cancel.2.html).
/// Note: according to current Linux kernel implementation(v4.19), libc::SYS_io_cancel always
/// return failure, thus rendering it useless.
///
/// # Arguments
/// * `iocb`: The iocb for the operation to be canceled.
/// * `result`: If the operation is successfully canceled, the event will be copied into the
/// memory pointed to by result without being placed into the completion queue.
pub fn cancel(&self, iocb: &IoControlBlock, result: &mut IoEvent) -> Result<()> {
// SAFETY: It's safe because parameters are valid and we have checked the result.
let rc = unsafe {
libc::syscall(
libc::SYS_io_cancel,
self.0,
iocb as *const IoControlBlock,
result as *mut IoEvent,
) as c_int
};
if rc < 0 {
Err(Error::last_os_error())
} else {
Ok(())
}
}
/// Read asynchronous I/O events from the completion queue.
///
/// Refer to Linux [`io_getevents`](http://man7.org/linux/man-pages/man2/io_getevents.2.html).
///
/// # Arguments
/// * `min_nr`: read at least min_nr events.
/// * `events`: array to receive the io operation results.
/// * `timeout`: optional amount of time to wait for events.
///
/// # Examples
///
/// ```
/// extern crate vmm_sys_util;
/// use vmm_sys_util::aio::*;
/// # use std::fs::File;
/// # use std::os::unix::io::AsRawFd;
///
/// let file = File::open("/dev/zero").unwrap();
/// let ctx = IoContext::new(128).unwrap();
/// let mut buf: [u8; 4096] = unsafe { std::mem::uninitialized() };
/// let iocbs = [
/// &mut IoControlBlock {
/// aio_fildes: file.as_raw_fd() as u32,
/// aio_lio_opcode: IOCB_CMD_PREAD as u16,
/// aio_buf: buf.as_mut_ptr() as u64,
/// aio_nbytes: buf.len() as u64,
/// ..Default::default()
/// },
/// &mut IoControlBlock {
/// aio_fildes: file.as_raw_fd() as u32,
/// aio_lio_opcode: IOCB_CMD_PREAD as u16,
/// aio_buf: buf.as_mut_ptr() as u64,
/// aio_nbytes: buf.len() as u64,
/// ..Default::default()
/// },
/// ];
///
/// let mut rc = ctx.submit(&iocbs[..]).unwrap();
/// let mut events = [unsafe { std::mem::uninitialized::<IoEvent>() }];
/// rc = ctx.get_events(1, &mut events, None).unwrap();
/// assert_eq!(rc, 1);
/// assert!(events[0].res > 0);
/// rc = ctx.get_events(1, &mut events, None).unwrap();
/// assert_eq!(rc, 1);
/// assert!(events[0].res > 0);
/// ```
pub fn get_events(
&self,
min_nr: c_long,
events: &mut [IoEvent],
timeout: Option<&mut libc::timespec>,
) -> Result<usize> {
let to = match timeout {
Some(val) => val as *mut libc::timespec,
None => null_mut(),
};
// SAFETY: It's safe because parameters are valid and we have checked the result.
let rc = unsafe {
libc::syscall(
libc::SYS_io_getevents,
self.0,
min_nr,
events.len() as c_long,
events.as_mut_ptr(),
to,
) as c_int
};
if rc < 0 {
Err(Error::last_os_error())
} else {
Ok(rc as usize)
}
}
}
impl Drop for IoContext {
fn drop(&mut self) {
if self.0 != 0 {
// SAFETY: It's safe because the context is created by us.
let _ = unsafe { libc::syscall(libc::SYS_io_destroy, self.0) as c_int };
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::fs::File;
use std::os::unix::io::AsRawFd;
#[test]
fn test_new_context() {
let _ = IoContext::new(0).unwrap_err();
}
#[test]
fn test_cancel_request() {
let file = File::open("/dev/zero").unwrap();
let ctx = IoContext::new(128).unwrap();
let mut buf: [u8; 16384] = [0u8; 16384];
let iocbs = [&mut IoControlBlock {
aio_fildes: file.as_raw_fd() as u32,
aio_lio_opcode: IOCB_CMD_PREAD as u16,
aio_buf: buf.as_mut_ptr() as u64,
aio_nbytes: buf.len() as u64,
..Default::default()
}];
let mut rc = ctx.submit(&iocbs).unwrap();
assert_eq!(rc, 1);
let mut result = Default::default();
let err = ctx
.cancel(iocbs[0], &mut result)
.unwrap_err()
.raw_os_error()
.unwrap();
assert_eq!(err, libc::EINVAL);
let mut events = [IoEvent::default()];
rc = ctx.get_events(1, &mut events, None).unwrap();
assert_eq!(rc, 1);
assert!(events[0].res > 0);
}
#[test]
fn test_read_zero() {
let file = File::open("/dev/zero").unwrap();
let ctx = IoContext::new(128).unwrap();
let mut buf: [u8; 4096] = [0u8; 4096];
let iocbs = [
&mut IoControlBlock {
aio_fildes: file.as_raw_fd() as u32,
aio_lio_opcode: IOCB_CMD_PREAD as u16,
aio_buf: buf.as_mut_ptr() as u64,
aio_nbytes: buf.len() as u64,
..Default::default()
},
&mut IoControlBlock {
aio_fildes: file.as_raw_fd() as u32,
aio_lio_opcode: IOCB_CMD_PREAD as u16,
aio_buf: buf.as_mut_ptr() as u64,
aio_nbytes: buf.len() as u64,
..Default::default()
},
];
let mut rc = ctx.submit(&iocbs[..]).unwrap();
assert_eq!(rc, 2);
let mut events = [IoEvent::default()];
rc = ctx.get_events(1, &mut events, None).unwrap();
assert_eq!(rc, 1);
assert!(events[0].res > 0);
rc = ctx.get_events(1, &mut events, None).unwrap();
assert_eq!(rc, 1);
assert!(events[0].res > 0);
}
#[test]
fn bindgen_test_layout_io_event() {
assert_eq!(
::std::mem::size_of::<IoEvent>(),
32usize,
concat!("Size of: ", stringify!(IoEvent))
);
assert_eq!(
::std::mem::align_of::<IoEvent>(),
8usize,
concat!("Alignment of", stringify!(IoEvent))
);
}
#[test]
fn bindgen_test_layout_iocb() {
assert_eq!(
::std::mem::size_of::<IoControlBlock>(),
64usize,
concat!("Size of:", stringify!(IoControlBlock))
);
assert_eq!(
::std::mem::align_of::<IoControlBlock>(),
8usize,
concat!("Alignment of", stringify!(IoControlBlock))
);
}
}