Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e84747b
tests(wasix): Add tests for mutliple syscalls
artemyarulin Jan 14, 2026
5df1d80
test(wasix): Add random_get, fd_prestat_dir_name
artemyarulin Jan 15, 2026
85b30ba
tests(wasix): closure_free, fd_fdflags_get, fd_fdstat_get, sched_yield
artemyarulin Jan 16, 2026
04e5cac
tests(wasix): Add getcwd syscalls, tests and fixes for it
artemyarulin Jan 20, 2026
b87cd1d
tests(wasix): Syscall tests for fd_datasync, fd_advise, clock_time, f…
artemyarulin Jan 20, 2026
d93bc48
tests(wasix): Add fd_fdstat, fd_filestat, fd_sync, fd_renumber, proc_…
artemyarulin Jan 22, 2026
bb74ff3
tests: Add tty_get, sock_addr_perr, reflect_signature, port_mac, fd_d…
artemyarulin Jan 27, 2026
eab0a5c
tests: clock_time_set, closure_allocate, context_destroy, epoll_creat…
artemyarulin Jan 28, 2026
67d37c5
tests: port_addr_list, port_route_list
artemyarulin Jan 29, 2026
0335a48
virtual-net: add host ip/route list; fix WASIX C ABI layout
artemyarulin Jan 29, 2026
b112133
tests: fd_advice, fd_close, fd_dup, fd_fdstat_get, fd_seek, proc_exit…
artemyarulin Jan 31, 2026
a6dadc5
tests: fd_filestate_set_size
artemyarulin Jan 31, 2026
e90a394
tests: fd_event, fed_read, fd_seek, fd_write, tty_set
artemyarulin Feb 1, 2026
3927fa1
test: fd_allocate, fd_read, fd_write, path_open2
artemyarulin Feb 1, 2026
1b02465
tests: callback_signal, closure_prepare, fd_pipe, patj_filestat_set_t…
artemyarulin Feb 1, 2026
85b9183
tests: sock_leave_multicast, sock_open, sock_recv, sock_send
artemyarulin Feb 1, 2026
5431b51
tests: thread_signal, thread_exit, tty_set
artemyarulin Feb 2, 2026
25ce21d
tests: call_dynamic, context_create, dlsym, fd_readdir, path_rename
artemyarulin Feb 4, 2026
8e7f4d1
tests: epoll_ctl, futex_wake, futex_wake_all, proc_signal, proc_join,…
artemyarulin Feb 4, 2026
0ca4c07
tests: context_switch, dlopen_libc, epoll_wait, futex_wait, path_open…
artemyarulin Feb 5, 2026
5e1c62c
tests: poll_oneoff, port_bridge, port_route_add, proc_spawn, proc_spa…
artemyarulin Feb 5, 2026
d239123
tests: Port litebox tests
artemyarulin Feb 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

193 changes: 193 additions & 0 deletions WASIX_SYSCALLS.md

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions lib/cli/src/commands/run/wasi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,10 +377,12 @@ impl Wasi {
.unwrap();

if have_current_dir {
builder.map_dir(".", MAPPED_CURRENT_DIR_DEFAULT_PATH)?
builder = builder.map_dir(".", MAPPED_CURRENT_DIR_DEFAULT_PATH)?;
} else {
builder.map_dir(".", "/")?
builder = builder.map_dir(".", "/")?;
}

builder
};

*builder.capabilities_mut() = self.capabilities();
Expand Down
19 changes: 16 additions & 3 deletions lib/virtual-fs/src/host_fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ impl crate::FileSystem for FileSystem {
.to_owned();
let path = Path::new("/").join(path);

let metadata = entry.metadata()?;
let metadata = fs::symlink_metadata(entry.path())?;

Ok(DirEntry {
path,
Expand Down Expand Up @@ -519,8 +519,21 @@ impl VirtualFile for File {
}

fn set_times(&mut self, atime: Option<u64>, mtime: Option<u64>) -> crate::Result<()> {
let atime = atime.map(|t| filetime::FileTime::from_unix_time(t as i64, 0));
let mtime = mtime.map(|t| filetime::FileTime::from_unix_time(t as i64, 0));
let to_filetime = |t: u64| -> crate::Result<filetime::FileTime> {
let secs = t / 1_000_000_000;
let nanos = (t % 1_000_000_000) as u32;
let secs = i64::try_from(secs).map_err(|_| crate::FsError::IOError)?;
Ok(filetime::FileTime::from_unix_time(secs, nanos))
};

let atime = match atime {
Some(t) => Some(to_filetime(t)?),
None => None,
};
let mtime = match mtime {
Some(t) => Some(to_filetime(t)?),
None => None,
};

filetime::set_file_handle_times(&self.inner_std, atime, mtime)
.map_err(|_| crate::FsError::IOError)
Expand Down
79 changes: 47 additions & 32 deletions lib/virtual-fs/src/pipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ use virtual_mio::{InterestHandler, InterestType};

use crate::{ArcFile, FsError, VirtualFile};

const PIPE_MAX_BYTES: usize = 64 * 1024;

// Each pipe end is separately cloneable. The overall pipe
// remains open as long as at least one tx end and one rx
// end are still alive.
Expand Down Expand Up @@ -53,19 +55,19 @@ impl PipeRx {
// Should return how much actual data was read from the provided slice
write: impl FnOnce(&[u8]) -> Option<usize>,
) -> Option<usize> {
rx.buffer.as_mut().and_then(|read_buffer| {
if let Some(read_buffer) = rx.buffer.as_mut() {
let buf_len = read_buffer.len();
if buf_len > 0 {
let mut read = buf_len.min(max_len);
let inner_buf = &read_buffer[..read];
// read = ?;
read = write(inner_buf)?;
read_buffer.advance(read);
Some(read)
} else {
None
rx.pending_bytes = rx.pending_bytes.saturating_sub(read);
return Some(read);
}
})
}
None
}

pub fn close(&mut self) {
Expand Down Expand Up @@ -151,6 +153,7 @@ struct PipeReceiver {
// actual receiver, we can't make use of an mpmc channel
chan: mpsc::UnboundedReceiver<Vec<u8>>,
buffer: Option<Bytes>,
pending_bytes: usize,
interest_handler: Option<Box<dyn InterestHandler>>,
}

Expand All @@ -161,6 +164,7 @@ impl Pipe {
let recv = Arc::new(Mutex::new(PipeReceiver {
chan: rx,
buffer: None,
pending_bytes: 0,
interest_handler: None,
}));
Pipe {
Expand Down Expand Up @@ -218,6 +222,42 @@ impl PipeTx {
_ = self.tx.take();
}

fn send_bytes(&self, buf: &[u8]) -> std::io::Result<usize> {
let Some(ref tx) = self.tx else {
return Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"PipeTx is closed",
));
};

tx.send(buf.to_vec())
.map_err(|_| Into::<std::io::Error>::into(std::io::ErrorKind::BrokenPipe))?;

if let Some(rx_end) = self.rx_end.upgrade() {
let mut rx = rx_end.lock().unwrap();
rx.pending_bytes = rx.pending_bytes.saturating_add(buf.len());
}

self.mark_other_end_readable();
Ok(buf.len())
}

pub fn try_write_nonblocking(&self, buf: &[u8]) -> std::io::Result<usize> {
let Some(rx_end) = self.rx_end.upgrade() else {
return Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"PipeRx is closed",
));
};
{
let rx = rx_end.lock().unwrap();
if rx.pending_bytes.saturating_add(buf.len()) > PIPE_MAX_BYTES {
return Err(std::io::ErrorKind::WouldBlock.into());
}
}
self.send_bytes(buf)
Comment on lines +246 to +258
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a race condition between checking pending_bytes (line 254) and calling send_bytes (line 258). Another thread could write to the pipe between these operations, causing the buffer to exceed PIPE_MAX_BYTES. The lock should be held across both operations.

Suggested change
let Some(rx_end) = self.rx_end.upgrade() else {
return Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"PipeRx is closed",
));
};
{
let rx = rx_end.lock().unwrap();
if rx.pending_bytes.saturating_add(buf.len()) > PIPE_MAX_BYTES {
return Err(std::io::ErrorKind::WouldBlock.into());
}
}
self.send_bytes(buf)
let Some(ref tx) = self.tx else {
return Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"PipeTx is closed",
));
};
let Some(rx_end) = self.rx_end.upgrade() else {
return Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"PipeRx is closed",
));
};
{
let mut rx = rx_end.lock().unwrap();
let new_pending = rx.pending_bytes.saturating_add(buf.len());
if new_pending > PIPE_MAX_BYTES {
return Err(std::io::ErrorKind::WouldBlock.into());
}
rx.pending_bytes = new_pending;
}
if let Err(_) = tx.send(buf.to_vec()) {
if let Some(rx_end) = self.rx_end.upgrade() {
let mut rx = rx_end.lock().unwrap();
rx.pending_bytes = rx.pending_bytes.saturating_sub(buf.len());
}
return Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"PipeTx is closed",
));
}
self.mark_other_end_readable();
Ok(buf.len())

Copilot uses AI. Check for mistakes.
}

pub fn poll_write_ready(self: Pin<&mut Self>) -> Poll<io::Result<usize>> {
let Some(ref tx) = self.tx else {
return Poll::Ready(Err(std::io::Error::new(
Expand Down Expand Up @@ -321,17 +361,7 @@ impl std::io::Write for Pipe {

impl std::io::Write for PipeTx {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let Some(ref tx) = self.tx else {
return Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"PipeTx is closed",
));
};

tx.send(buf.to_vec())
.map_err(|_| Into::<std::io::Error>::into(std::io::ErrorKind::BrokenPipe))?;
self.mark_other_end_readable();
Ok(buf.len())
self.send_bytes(buf)
}

fn flush(&mut self) -> std::io::Result<()> {
Expand Down Expand Up @@ -408,22 +438,7 @@ impl AsyncWrite for PipeTx {
_cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
let Some(ref tx) = self.tx else {
return Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"PipeTx is closed",
)));
};

match tx.send(buf.to_vec()) {
Ok(()) => {
self.mark_other_end_readable();
Poll::Ready(Ok(buf.len()))
}
Err(_) => Poll::Ready(Err(Into::<std::io::Error>::into(
std::io::ErrorKind::BrokenPipe,
))),
}
Poll::Ready(self.send_bytes(buf))
}

fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Expand Down
1 change: 1 addition & 0 deletions lib/virtual-net/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ libc = { workspace = true, optional = true }
mio = { workspace = true, optional = true }
socket2 = { workspace = true, optional = true }
derive_more.workspace = true
interfaces.workspace = true
virtual-mio = { path = "../virtual-io", version = "0.701.0", default-features = false }
bincode = { workspace = true, features = ["serde"] }
serde = { workspace = true, default-features = false, features = ["derive"] }
Expand Down
120 changes: 119 additions & 1 deletion lib/virtual-net/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
};
use crate::{VirtualIoSource, io_err_into_net_error};
use bytes::{Buf, BytesMut};
use std::collections::VecDeque;
use std::collections::{HashSet, VecDeque};
use std::io::{self, Read, Write};
use std::mem::MaybeUninit;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr};
Expand All @@ -29,6 +29,37 @@ use virtual_mio::{
HandlerGuardState, InterestGuard, InterestHandler, InterestType, Selector, state_as_waker_map,
};

fn prefix_from_mask(ip: IpAddr, mask: Option<SocketAddr>) -> u8 {
let default_prefix = match ip {
IpAddr::V4(_) => 32,
IpAddr::V6(_) => 128,
};
let Some(mask) = mask else {
return default_prefix;
};

match (ip, mask.ip()) {
(IpAddr::V4(_), IpAddr::V4(m)) => {
let bits = u32::from_be_bytes(m.octets()).count_ones();
bits as u8
}
(IpAddr::V6(_), IpAddr::V6(m)) => {
let bits: u32 = m.octets().iter().map(|b| b.count_ones()).sum();
bits as u8
}
_ => default_prefix,
}
}

fn default_loopback_cidrs() -> Vec<IpCidr> {
vec![
IpCidr {
ip: IpAddr::V4(Ipv4Addr::LOCALHOST),
prefix: 8,
},
]
}

#[derive(Debug)]
pub struct LocalNetworking {
selector: Arc<Selector>,
Expand Down Expand Up @@ -69,6 +100,44 @@ impl Default for LocalNetworking {
#[async_trait::async_trait]
#[allow(unused_variables)]
impl VirtualNetworking for LocalNetworking {
async fn ip_list(&self) -> Result<Vec<IpCidr>> {
let mut cidrs = HashSet::new();
if let Ok(ifaces) = interfaces::Interface::get_all() {
for iface in ifaces {
for addr in iface.addresses.iter() {
if let Some(sock) = addr.addr {
let ip = sock.ip();
if ip.is_ipv6() {
continue;
}
let prefix = prefix_from_mask(ip, addr.mask);
cidrs.insert(IpCidr { ip, prefix });
}
}
}
}

let mut cidrs: Vec<IpCidr> = cidrs.into_iter().collect();
if cidrs.is_empty() {
cidrs = default_loopback_cidrs();
}
Ok(cidrs)
}

async fn route_list(&self) -> Result<Vec<IpRoute>> {
let cidrs = self.ip_list().await?;
let routes = cidrs
.into_iter()
.map(|cidr| IpRoute {
cidr,
via_router: cidr.ip,
preferred_until: None,
expires_at: None,
})
.collect();
Ok(routes)
}

async fn listen_tcp(
&self,
addr: SocketAddr,
Expand Down Expand Up @@ -1033,3 +1102,52 @@ impl VirtualIoSource for LocalUdpSocket {
Poll::Pending
}
}


#[cfg(test)]
mod udp_tests {
use super::*;
use std::mem::MaybeUninit;
use std::net::{Ipv4Addr, SocketAddr};

#[tokio::test]
async fn udp_send_recv_loopback() {
let net = LocalNetworking::new();
let mut sock = net
.bind_udp(SocketAddr::from((Ipv4Addr::LOCALHOST, 0)), false, false)
.await
.unwrap();
let local = sock.addr_local().unwrap();

// Empty recv should wouldblock
let mut buf: [MaybeUninit<u8>; 16] = [MaybeUninit::uninit(); 16];
let err = sock.try_recv_from(&mut buf, false).unwrap_err();
assert_eq!(err, NetworkError::WouldBlock);

// Send to self and read back
let sent = sock.try_send_to(b"hi", local).unwrap();
assert_eq!(sent, 2);

let mut read = None;
let mut addr = None;
for _ in 0..50 {
match sock.try_recv_from(&mut buf, false) {
Ok((n, a)) => {
read = Some(n);
addr = Some(a);
break;
}
Err(NetworkError::WouldBlock) => {
tokio::time::sleep(std::time::Duration::from_millis(2)).await;
}
Err(err) => panic!("unexpected recv error: {err:?}"),
}
}
let read = read.expect("no udp data received");
let addr = addr.expect("no udp addr received");
assert_eq!(read, 2);
assert_eq!(addr, local);
let got = unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const u8, read) };
assert_eq!(got, b"hi");
}
}
5 changes: 5 additions & 0 deletions lib/virtual-net/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,9 @@ pub enum NetworkError {
/// Insufficient memory
#[error("Insufficient memory")]
InsufficientMemory,
/// The message is too large for the socket
#[error("message too large")]
MessageTooLarge,
/// The connection was aborted
#[error("connection aborted")]
ConnectionAborted,
Expand Down Expand Up @@ -890,6 +893,7 @@ pub fn io_err_into_net_error(net_error: std::io::Error) -> NetworkError {
libc::ENODEV => NetworkError::NoDevice,
libc::EINVAL => NetworkError::InvalidInput,
libc::EPIPE => NetworkError::BrokenPipe,
libc::EMSGSIZE => NetworkError::MessageTooLarge,
err => {
tracing::trace!("unknown os error {}", err);
NetworkError::UnknownError
Expand Down Expand Up @@ -920,6 +924,7 @@ pub fn net_error_into_io_err(net_error: NetworkError) -> std::io::Error {
NetworkError::Interrupted => ErrorKind::Interrupted.into(),
NetworkError::InvalidData => ErrorKind::InvalidData.into(),
NetworkError::InvalidInput => ErrorKind::InvalidInput.into(),
NetworkError::MessageTooLarge => ErrorKind::InvalidInput.into(),
NetworkError::NotConnected => ErrorKind::NotConnected.into(),
NetworkError::NoDevice => ErrorKind::BrokenPipe.into(),
NetworkError::PermissionDenied => ErrorKind::PermissionDenied.into(),
Expand Down
Loading
Loading