wove/src/plat/linux.rs

252 lines
5.4 KiB
Rust

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<std::cell::Cell<()>>,
}
pub type FileHandle = io_uring::types::Fd;
impl PlatformLinux {
pub fn new() -> IoResult<Self> {
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<FileHandle> {
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<FileHandle> {
// 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<Box<[u8]>> {
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<usize> {
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<Box<CqueueEntryReceiver>>,
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<TcpStream>;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
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<TcpStream>;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
AsyncIterator::poll_next(self, cx)
}
}
impl Drop for Incoming<'_> {
fn drop(&mut self) {
cancel(self.plat, self.cb_id);
}
}
pub type Platform = PlatformLinux;