use std::{ async_iter::AsyncIterator, ffi::CString, future::Future, net::{IpAddr, SocketAddr}, os::fd::IntoRawFd, path::PathBuf, pin::Pin, }; use futures_lite::Stream; use io_uring::IoUring; use crate::{aliases::IoResult, net::TcpStream}; pub struct PlatformLinux { uring: io_uring::IoUring, _pd: core::marker::PhantomData>, } pub type FileHandle = io_uring::types::Fd; impl PlatformLinux { pub fn new() -> IoResult { Ok(Self { uring: IoUring::new(256)?, _pd: Default::default(), }) } pub async fn tick(&self) -> IoResult<()> { self.uring.submit()?; let cq = unsafe { self.uring.completion_shared() }; for entry in cq { let ud = unsafe { UserData::from_u64(entry.user_data()) }; ud.tx.send(entry).await.unwrap(); if ud.persist { Box::leak(ud); } } Ok(()) } } pub async fn open_file(p: &PlatformLinux, path: PathBuf) -> IoResult { let (tx, rx) = async_channel::bounded(1); let path = CString::new(path.into_os_string().into_encoded_bytes())?; let entry = io_uring::opcode::OpenAt::new(io_uring::types::Fd(libc::AT_FDCWD), path.as_ptr()) .build() .user_data(UserData::new_boxed(tx, false).into_u64()); unsafe { p.uring.submission_shared().push(&entry).unwrap(); } let entry = rx.recv().await.unwrap(); let fd = handle_error(entry.result())?; let fd = io_uring::types::Fd(fd); Ok(fd) } pub async fn open_tcp_socket( p: &PlatformLinux, socket_addr: SocketAddr, ) -> IoResult { // FIXME(Blocking) // There is some some magic missing in the commented out code to make the // socket clean up properly on process exit and whatnot. For now, just use // the std implementation and cast it to a FileHandle let listener = std::net::TcpListener::bind(socket_addr)?; Ok(io_uring::types::Fd(listener.into_raw_fd())) /* // let (tx, rx) = async_channel::bounded(1); let domain = match () { _ if socket_addr.is_ipv4() => libc::AF_INET, _ if socket_addr.is_ipv6() => libc::AF_INET6, _ => return Err(std::io::Error::other("Unsupported domain")), }; let entry = io_uring::opcode::Socket::new(domain, libc::SOCK_STREAM, 0) .build() .user_data(UserData::new_boxed(tx, false).into_u64()); unsafe { p.uring.submission_shared().push(&entry).unwrap(); } let entry = rx.recv().await.unwrap(); let fd = handle_error(entry.result())?; let sock = libc::sockaddr_in { sin_family: domain as _, sin_port: socket_addr.port().to_be(), sin_addr: libc::in_addr { s_addr: match socket_addr.ip() { IpAddr::V4(v) => v.to_bits().to_be(), IpAddr::V6(_) => panic!(), }, }, sin_zero: Default::default(), }; // FIXME(Blocking) handle_error(unsafe { libc::bind( fd, &sock as *const _ as *const _, core::mem::size_of_val(&sock) as u32, ) })?; // FIXME(Blocking) handle_error(unsafe { libc::listen(fd, libc::SOMAXCONN) })?; Ok(io_uring::types::Fd(fd)) */ } pub async fn read( p: &PlatformLinux, f: &mut FileHandle, offset: usize, mut buf: Box<[u8]>, ) -> IoResult> { let (tx, rx) = async_channel::bounded(1); let entry = io_uring::opcode::Read::new(*f, buf.as_mut_ptr(), buf.len() as _) .offset(offset as _) .build() .user_data(UserData::new_boxed(tx, false).into_u64()); unsafe { p.uring.submission_shared().push(&entry).unwrap(); } let entry = rx.recv().await.unwrap(); let read_amt = handle_error(entry.result())?; let mut buf = buf.into_vec(); buf.truncate(read_amt as _); Ok(buf.into_boxed_slice()) } pub async fn write( p: &PlatformLinux, f: &mut FileHandle, offset: usize, mut buf: Box<[u8]>, ) -> IoResult { let (tx, rx) = async_channel::bounded(1); let entry = io_uring::opcode::Write::new(*f, buf.as_mut_ptr(), buf.len() as _) .offset(offset as _) .build() .user_data(UserData::new_boxed(tx, false).into_u64()); unsafe { p.uring.submission_shared().push(&entry).unwrap(); } let entry = rx.recv().await.unwrap(); let write_amt = handle_error(entry.result())?; Ok(write_amt as usize) } pub(crate) struct Incoming<'a> { plat: &'a PlatformLinux, rx: Pin>, cb_id: u64, } pub fn accept_many<'a>(p: &'a PlatformLinux, f: &mut FileHandle) -> Incoming<'a> { let (tx, rx) = async_channel::unbounded(); let cb_id = UserData::new_boxed(tx, true).into_u64(); let entry = io_uring::opcode::AcceptMulti::new(*f) .build() .user_data(cb_id); unsafe { p.uring.submission_shared().push(&entry).unwrap(); } Incoming { plat: p, rx: Box::pin(rx), cb_id, } } pub fn cancel(p: &PlatformLinux, id: u64) { let entry = io_uring::opcode::AsyncCancel::new(id).build(); unsafe { p.uring.submission_shared().push(&entry).unwrap(); } } impl AsyncIterator for Incoming<'_> { type Item = IoResult; fn poll_next( self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { let rx = unsafe { self.map_unchecked_mut(|s| &mut s.rx) }; let mut fut = rx.recv(); let pinned = unsafe { Pin::new_unchecked(&mut fut) }; pinned .poll(cx) .map(|entry| { let fd = handle_error(entry.unwrap().result())?; Ok(crate::net::TcpStream(io_uring::types::Fd(fd))) }) .map(Some) } } impl Stream for Incoming<'_> { type Item = IoResult; fn poll_next( self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { AsyncIterator::poll_next(self, cx) } } impl Drop for Incoming<'_> { fn drop(&mut self) { cancel(self.plat, self.cb_id); } } pub type Platform = PlatformLinux;