// 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))
        );
    }
}
