With the addition of `sock_accept()` in `wasi-0.11.0`, wasmtime can now
implement basic networking for pre-opened sockets.
For Windows `AsHandle` was replaced with `AsRawHandleOrSocket` to cope
with the duality of Handles and Sockets.
For Unix a `wasi_cap_std_sync::net::Socket` enum was created to handle
the {Tcp,Unix}{Listener,Stream} more efficiently in
`WasiCtxBuilder::preopened_socket()`.
The addition of that many `WasiFile` implementors was mainly necessary,
because of the difference in the `num_ready_bytes()` function.
A known issue is Windows now busy polling on sockets, because except
for `stdin`, nothing is querying the status of windows handles/sockets.
Another know issue on Windows, is that there is no crate providing
support for `fcntl(fd, F_GETFL, 0)` on a socket.
Signed-off-by: Harald Hoyer <harald@profian.com>
251 lines
9.5 KiB
Rust
251 lines
9.5 KiB
Rust
use crate::block_on_dummy_executor;
|
|
#[cfg(windows)]
|
|
use io_extras::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket};
|
|
#[cfg(not(windows))]
|
|
use io_lifetimes::AsFd;
|
|
use std::any::Any;
|
|
use std::io;
|
|
use wasi_common::{
|
|
file::{Advice, FdFlags, FileType, Filestat, WasiFile},
|
|
Error,
|
|
};
|
|
|
|
pub struct File(wasi_cap_std_sync::file::File);
|
|
|
|
impl File {
|
|
pub(crate) fn from_inner(file: wasi_cap_std_sync::file::File) -> Self {
|
|
File(file)
|
|
}
|
|
pub fn from_cap_std(file: cap_std::fs::File) -> Self {
|
|
Self::from_inner(wasi_cap_std_sync::file::File::from_cap_std(file))
|
|
}
|
|
}
|
|
|
|
pub struct TcpListener(wasi_cap_std_sync::net::TcpListener);
|
|
|
|
impl TcpListener {
|
|
pub(crate) fn from_inner(listener: wasi_cap_std_sync::net::TcpListener) -> Self {
|
|
TcpListener(listener)
|
|
}
|
|
pub fn from_cap_std(listener: cap_std::net::TcpListener) -> Self {
|
|
Self::from_inner(wasi_cap_std_sync::net::TcpListener::from_cap_std(listener))
|
|
}
|
|
}
|
|
|
|
pub struct TcpStream(wasi_cap_std_sync::net::TcpStream);
|
|
|
|
impl TcpStream {
|
|
pub(crate) fn from_inner(stream: wasi_cap_std_sync::net::TcpStream) -> Self {
|
|
TcpStream(stream)
|
|
}
|
|
pub fn from_cap_std(stream: cap_std::net::TcpStream) -> Self {
|
|
Self::from_inner(wasi_cap_std_sync::net::TcpStream::from_cap_std(stream))
|
|
}
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
pub struct UnixListener(wasi_cap_std_sync::net::UnixListener);
|
|
|
|
#[cfg(unix)]
|
|
impl UnixListener {
|
|
pub(crate) fn from_inner(listener: wasi_cap_std_sync::net::UnixListener) -> Self {
|
|
UnixListener(listener)
|
|
}
|
|
pub fn from_cap_std(listener: cap_std::os::unix::net::UnixListener) -> Self {
|
|
Self::from_inner(wasi_cap_std_sync::net::UnixListener::from_cap_std(listener))
|
|
}
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
pub struct UnixStream(wasi_cap_std_sync::net::UnixStream);
|
|
|
|
#[cfg(unix)]
|
|
impl UnixStream {
|
|
fn from_inner(stream: wasi_cap_std_sync::net::UnixStream) -> Self {
|
|
UnixStream(stream)
|
|
}
|
|
pub fn from_cap_std(stream: cap_std::os::unix::net::UnixStream) -> Self {
|
|
Self::from_inner(wasi_cap_std_sync::net::UnixStream::from_cap_std(stream))
|
|
}
|
|
}
|
|
|
|
pub struct Stdin(wasi_cap_std_sync::stdio::Stdin);
|
|
|
|
pub fn stdin() -> Stdin {
|
|
Stdin(wasi_cap_std_sync::stdio::stdin())
|
|
}
|
|
|
|
pub struct Stdout(wasi_cap_std_sync::stdio::Stdout);
|
|
|
|
pub fn stdout() -> Stdout {
|
|
Stdout(wasi_cap_std_sync::stdio::stdout())
|
|
}
|
|
|
|
pub struct Stderr(wasi_cap_std_sync::stdio::Stderr);
|
|
|
|
pub fn stderr() -> Stderr {
|
|
Stderr(wasi_cap_std_sync::stdio::stderr())
|
|
}
|
|
|
|
macro_rules! wasi_file_impl {
|
|
($ty:ty) => {
|
|
#[wiggle::async_trait]
|
|
impl WasiFile for $ty {
|
|
fn as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
async fn datasync(&self) -> Result<(), Error> {
|
|
block_on_dummy_executor(|| self.0.datasync())
|
|
}
|
|
async fn sync(&self) -> Result<(), Error> {
|
|
block_on_dummy_executor(|| self.0.sync())
|
|
}
|
|
async fn get_filetype(&self) -> Result<FileType, Error> {
|
|
block_on_dummy_executor(|| self.0.get_filetype())
|
|
}
|
|
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
|
|
block_on_dummy_executor(|| self.0.get_fdflags())
|
|
}
|
|
async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> {
|
|
block_on_dummy_executor(|| self.0.set_fdflags(fdflags))
|
|
}
|
|
async fn get_filestat(&self) -> Result<Filestat, Error> {
|
|
block_on_dummy_executor(|| self.0.get_filestat())
|
|
}
|
|
async fn set_filestat_size(&self, size: u64) -> Result<(), Error> {
|
|
block_on_dummy_executor(move || self.0.set_filestat_size(size))
|
|
}
|
|
async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
|
|
block_on_dummy_executor(move || self.0.advise(offset, len, advice))
|
|
}
|
|
async fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
|
|
block_on_dummy_executor(move || self.0.allocate(offset, len))
|
|
}
|
|
async fn read_vectored<'a>(
|
|
&self,
|
|
bufs: &mut [io::IoSliceMut<'a>],
|
|
) -> Result<u64, Error> {
|
|
block_on_dummy_executor(move || self.0.read_vectored(bufs))
|
|
}
|
|
async fn read_vectored_at<'a>(
|
|
&self,
|
|
bufs: &mut [io::IoSliceMut<'a>],
|
|
offset: u64,
|
|
) -> Result<u64, Error> {
|
|
block_on_dummy_executor(move || self.0.read_vectored_at(bufs, offset))
|
|
}
|
|
async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result<u64, Error> {
|
|
block_on_dummy_executor(move || self.0.write_vectored(bufs))
|
|
}
|
|
async fn write_vectored_at<'a>(
|
|
&self,
|
|
bufs: &[io::IoSlice<'a>],
|
|
offset: u64,
|
|
) -> Result<u64, Error> {
|
|
block_on_dummy_executor(move || self.0.write_vectored_at(bufs, offset))
|
|
}
|
|
async fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
|
|
block_on_dummy_executor(move || self.0.seek(pos))
|
|
}
|
|
async fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
|
|
block_on_dummy_executor(move || self.0.peek(buf))
|
|
}
|
|
async fn set_times(
|
|
&self,
|
|
atime: Option<wasi_common::SystemTimeSpec>,
|
|
mtime: Option<wasi_common::SystemTimeSpec>,
|
|
) -> Result<(), Error> {
|
|
block_on_dummy_executor(move || self.0.set_times(atime, mtime))
|
|
}
|
|
async fn num_ready_bytes(&self) -> Result<u64, Error> {
|
|
block_on_dummy_executor(|| self.0.num_ready_bytes())
|
|
}
|
|
fn isatty(&self) -> bool {
|
|
self.0.isatty()
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
async fn readable(&self) -> Result<(), Error> {
|
|
// The Inner impls OwnsRaw, which asserts exclusive use of the handle by the owned object.
|
|
// AsyncFd needs to wrap an owned `impl std::os::unix::io::AsRawFd`. Rather than introduce
|
|
// mutability to let it own the `Inner`, we are depending on the `&mut self` bound on this
|
|
// async method to ensure this is the only Future which can access the RawFd during the
|
|
// lifetime of the AsyncFd.
|
|
use std::os::unix::io::AsRawFd;
|
|
use tokio::io::{unix::AsyncFd, Interest};
|
|
let rawfd = self.0.as_fd().as_raw_fd();
|
|
match AsyncFd::with_interest(rawfd, Interest::READABLE) {
|
|
Ok(asyncfd) => {
|
|
let _ = asyncfd.readable().await?;
|
|
Ok(())
|
|
}
|
|
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
|
|
// if e is EPERM, this file isnt supported by epoll because it is immediately
|
|
// available for reading:
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(e.into()),
|
|
}
|
|
}
|
|
#[cfg(windows)]
|
|
async fn readable(&self) -> Result<(), Error> {
|
|
// Windows uses a rawfd based scheduler :(
|
|
use wasi_common::ErrorExt;
|
|
Err(Error::badf())
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
async fn writable(&self) -> Result<(), Error> {
|
|
// The Inner impls OwnsRaw, which asserts exclusive use of the handle by the owned object.
|
|
// AsyncFd needs to wrap an owned `impl std::os::unix::io::AsRawFd`. Rather than introduce
|
|
// mutability to let it own the `Inner`, we are depending on the `&mut self` bound on this
|
|
// async method to ensure this is the only Future which can access the RawFd during the
|
|
// lifetime of the AsyncFd.
|
|
use std::os::unix::io::AsRawFd;
|
|
use tokio::io::{unix::AsyncFd, Interest};
|
|
let rawfd = self.0.as_fd().as_raw_fd();
|
|
match AsyncFd::with_interest(rawfd, Interest::WRITABLE) {
|
|
Ok(asyncfd) => {
|
|
let _ = asyncfd.writable().await?;
|
|
Ok(())
|
|
}
|
|
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
|
|
// if e is EPERM, this file isnt supported by epoll because it is immediately
|
|
// available for writing:
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(e.into()),
|
|
}
|
|
}
|
|
#[cfg(windows)]
|
|
async fn writable(&self) -> Result<(), Error> {
|
|
// Windows uses a rawfd based scheduler :(
|
|
use wasi_common::ErrorExt;
|
|
Err(Error::badf())
|
|
}
|
|
|
|
async fn sock_accept(&mut self, fdflags: FdFlags) -> Result<Box<dyn WasiFile>, Error> {
|
|
block_on_dummy_executor(|| self.0.sock_accept(fdflags))
|
|
}
|
|
}
|
|
#[cfg(windows)]
|
|
impl AsRawHandleOrSocket for $ty {
|
|
#[inline]
|
|
fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket {
|
|
self.0.as_raw_handle_or_socket()
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
wasi_file_impl!(File);
|
|
wasi_file_impl!(TcpListener);
|
|
wasi_file_impl!(TcpStream);
|
|
#[cfg(unix)]
|
|
wasi_file_impl!(UnixListener);
|
|
#[cfg(unix)]
|
|
wasi_file_impl!(UnixStream);
|
|
wasi_file_impl!(Stdin);
|
|
wasi_file_impl!(Stdout);
|
|
wasi_file_impl!(Stderr);
|