use crate::{ dir::{DirCaps, DirEntry, DirEntryExt, DirFdStat, ReaddirCursor, ReaddirEntity, TableDirExt}, file::{ Advice, FdFlags, FdStat, FileCaps, FileEntry, FileEntryExt, FileType, Filestat, OFlags, RiFlags, RoFlags, SdFlags, SiFlags, TableFileExt, WasiFile, }, sched::{ subscription::{RwEventFlags, SubscriptionResult}, Poll, Userdata, }, I32Exit, SystemTimeSpec, WasiCtx, }; use cap_std::time::{Duration, SystemClock}; use std::convert::{TryFrom, TryInto}; use std::io::{IoSlice, IoSliceMut}; use std::ops::Deref; use std::sync::Arc; use wiggle::GuestPtr; pub mod error; use error::{Error, ErrorExt}; // Limit the size of intermediate buffers when copying to WebAssembly shared // memory. pub(crate) const MAX_SHARED_BUFFER_SIZE: usize = 1 << 16; wiggle::from_witx!({ witx: ["$WASI_ROOT/phases/snapshot/witx/wasi_snapshot_preview1.witx"], errors: { errno => trappable Error }, // Note: not every function actually needs to be async, however, nearly all of them do, and // keeping that set the same in this macro and the wasmtime_wiggle / lucet_wiggle macros is // tedious, and there is no cost to having a sync function be async in this case. async: *, wasmtime: false, }); impl wiggle::GuestErrorType for types::Errno { fn success() -> Self { Self::Success } } #[wiggle::async_trait] impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { async fn args_get<'b>( &mut self, argv: &GuestPtr<'b, GuestPtr<'b, u8>>, argv_buf: &GuestPtr<'b, u8>, ) -> Result<(), Error> { self.args.write_to_guest(argv_buf, argv) } async fn args_sizes_get(&mut self) -> Result<(types::Size, types::Size), Error> { Ok((self.args.number_elements(), self.args.cumulative_size())) } async fn environ_get<'b>( &mut self, environ: &GuestPtr<'b, GuestPtr<'b, u8>>, environ_buf: &GuestPtr<'b, u8>, ) -> Result<(), Error> { self.env.write_to_guest(environ_buf, environ) } async fn environ_sizes_get(&mut self) -> Result<(types::Size, types::Size), Error> { Ok((self.env.number_elements(), self.env.cumulative_size())) } async fn clock_res_get(&mut self, id: types::Clockid) -> Result { let resolution = match id { types::Clockid::Realtime => Ok(self.clocks.system()?.resolution()), types::Clockid::Monotonic => Ok(self.clocks.monotonic()?.abs_clock.resolution()), types::Clockid::ProcessCputimeId | types::Clockid::ThreadCputimeId => { Err(Error::badf().context("process and thread clocks are not supported")) } }?; Ok(resolution.as_nanos().try_into()?) } async fn clock_time_get( &mut self, id: types::Clockid, precision: types::Timestamp, ) -> Result { let precision = Duration::from_nanos(precision); match id { types::Clockid::Realtime => { let now = self.clocks.system()?.now(precision).into_std(); let d = now .duration_since(std::time::SystemTime::UNIX_EPOCH) .map_err(|_| { Error::trap(anyhow::Error::msg("current time before unix epoch")) })?; Ok(d.as_nanos().try_into()?) } types::Clockid::Monotonic => { let clock = self.clocks.monotonic()?; let now = clock.abs_clock.now(precision); let d = now.duration_since(clock.creation_time); Ok(d.as_nanos().try_into()?) } types::Clockid::ProcessCputimeId | types::Clockid::ThreadCputimeId => { Err(Error::badf().context("process and thread clocks are not supported")) } } } async fn fd_advise( &mut self, fd: types::Fd, offset: types::Filesize, len: types::Filesize, advice: types::Advice, ) -> Result<(), Error> { self.table() .get_file(u32::from(fd))? .get_cap(FileCaps::ADVISE)? .advise(offset, len, advice.into()) .await?; Ok(()) } async fn fd_allocate( &mut self, fd: types::Fd, _offset: types::Filesize, _len: types::Filesize, ) -> Result<(), Error> { // Check if fd is a file, and has rights, just to reject those cases // with the errors expected: let _ = self .table() .get_file(u32::from(fd))? .get_cap(FileCaps::ALLOCATE)?; // This operation from cloudabi is linux-specific, isn't even // supported across all linux filesystems, and has no support on macos // or windows. Rather than ship spotty support, it has been removed // from preview 2, and we are no longer supporting it in preview 1 as // well. Err(Error::not_supported()) } async fn fd_close(&mut self, fd: types::Fd) -> Result<(), Error> { let table = self.table(); let fd = u32::from(fd); // Fail fast: If not present in table, Badf if !table.contains_key(fd) { return Err(Error::badf().context("key not in table")); } // fd_close must close either a File or a Dir handle if table.is::(fd) { let _ = table.delete::(fd); } else if table.is::(fd) { let _ = table.delete::(fd); } else { return Err(Error::badf().context("key does not refer to file or directory")); } Ok(()) } async fn fd_datasync(&mut self, fd: types::Fd) -> Result<(), Error> { self.table() .get_file(u32::from(fd))? .get_cap(FileCaps::DATASYNC)? .datasync() .await?; Ok(()) } async fn fd_fdstat_get(&mut self, fd: types::Fd) -> Result { let table = self.table(); let fd = u32::from(fd); if table.is::(fd) { let file_entry: Arc = table.get(fd)?; let fdstat = file_entry.get_fdstat().await?; Ok(types::Fdstat::from(&fdstat)) } else if table.is::(fd) { let dir_entry: Arc = table.get(fd)?; let dir_fdstat = dir_entry.get_dir_fdstat(); Ok(types::Fdstat::from(&dir_fdstat)) } else { Err(Error::badf()) } } async fn fd_fdstat_set_flags( &mut self, fd: types::Fd, flags: types::Fdflags, ) -> Result<(), Error> { if let Some(table) = self.table_mut() { table .get_file_mut(u32::from(fd))? .get_cap_mut(FileCaps::FDSTAT_SET_FLAGS)? .set_fdflags(FdFlags::from(flags)) .await } else { log::warn!("`fd_fdstat_set_flags` does not work with wasi-threads enabled; see https://github.com/bytecodealliance/wasmtime/issues/5643"); Err(Error::not_supported()) } } async fn fd_fdstat_set_rights( &mut self, fd: types::Fd, fs_rights_base: types::Rights, fs_rights_inheriting: types::Rights, ) -> Result<(), Error> { let table = self.table(); let fd = u32::from(fd); if table.is::(fd) { let file_entry: Arc = table.get(fd)?; let file_caps = FileCaps::from(&fs_rights_base); file_entry.drop_caps_to(file_caps) } else if table.is::(fd) { let dir_entry: Arc = table.get(fd)?; let dir_caps = DirCaps::from(&fs_rights_base); let file_caps = FileCaps::from(&fs_rights_inheriting); dir_entry.drop_caps_to(dir_caps, file_caps) } else { Err(Error::badf()) } } async fn fd_filestat_get(&mut self, fd: types::Fd) -> Result { let table = self.table(); let fd = u32::from(fd); if table.is::(fd) { let filestat = table .get_file(fd)? .get_cap(FileCaps::FILESTAT_GET)? .get_filestat() .await?; Ok(filestat.into()) } else if table.is::(fd) { let filestat = table .get_dir(fd)? .get_cap(DirCaps::FILESTAT_GET)? .get_filestat() .await?; Ok(filestat.into()) } else { Err(Error::badf()) } } async fn fd_filestat_set_size( &mut self, fd: types::Fd, size: types::Filesize, ) -> Result<(), Error> { self.table() .get_file(u32::from(fd))? .get_cap(FileCaps::FILESTAT_SET_SIZE)? .set_filestat_size(size) .await?; Ok(()) } async fn fd_filestat_set_times( &mut self, fd: types::Fd, atim: types::Timestamp, mtim: types::Timestamp, fst_flags: types::Fstflags, ) -> Result<(), Error> { let fd = u32::from(fd); let table = self.table(); // Validate flags let set_atim = fst_flags.contains(types::Fstflags::ATIM); let set_atim_now = fst_flags.contains(types::Fstflags::ATIM_NOW); let set_mtim = fst_flags.contains(types::Fstflags::MTIM); let set_mtim_now = fst_flags.contains(types::Fstflags::MTIM_NOW); let atim = systimespec(set_atim, atim, set_atim_now).map_err(|e| e.context("atim"))?; let mtim = systimespec(set_mtim, mtim, set_mtim_now).map_err(|e| e.context("mtim"))?; if table.is::(fd) { table .get_file(fd) .expect("checked that entry is file") .get_cap(FileCaps::FILESTAT_SET_TIMES)? .set_times(atim, mtim) .await } else if table.is::(fd) { table .get_dir(fd) .expect("checked that entry is dir") .get_cap(DirCaps::FILESTAT_SET_TIMES)? .set_times(".", atim, mtim, false) .await } else { Err(Error::badf()) } } async fn fd_read<'a>( &mut self, fd: types::Fd, iovs: &types::IovecArray<'a>, ) -> Result { let f = self.table().get_file(u32::from(fd))?; let f = f.get_cap(FileCaps::READ)?; let iovs: Vec> = iovs .iter() .map(|iov_ptr| { let iov_ptr = iov_ptr?; let iov: types::Iovec = iov_ptr.read()?; Ok(iov.buf.as_array(iov.buf_len)) }) .collect::>()?; // If the first iov structure is from shared memory we can safely assume // all the rest will be. We then read into memory based on the memory's // shared-ness: // - if not shared, we copy directly into the Wasm memory // - if shared, we use an intermediate buffer; this avoids Rust unsafety // due to holding on to a `&mut [u8]` of Wasm memory when we cannot // guarantee the `&mut` exclusivity--other threads could be modifying // the data as this functions writes to it. Though likely there is no // issue with OS writing to io structs in multi-threaded scenarios, // since we do not know here if `&dyn WasiFile` does anything else // (e.g., read), we cautiously incur some performance overhead by // copying twice. let is_shared_memory = iovs .iter() .next() .and_then(|s| Some(s.is_shared_memory())) .unwrap_or(false); let bytes_read: u64 = if is_shared_memory { // For shared memory, read into an intermediate buffer. Only the // first iov will be filled and even then the read is capped by the // `MAX_SHARED_BUFFER_SIZE`, so users are expected to re-call. let iov = iovs.into_iter().next(); if let Some(iov) = iov { let mut buffer = vec![0; (iov.len() as usize).min(MAX_SHARED_BUFFER_SIZE)]; let bytes_read = f.read_vectored(&mut [IoSliceMut::new(&mut buffer)]).await?; iov.get_range(0..bytes_read.try_into()?) .expect("it should always be possible to slice the iov smaller") .copy_from_slice(&buffer[0..bytes_read.try_into()?])?; bytes_read } else { return Ok(0); } } else { // Convert all of the unsafe guest slices to safe ones--this uses // Wiggle's internal borrow checker to ensure no overlaps. We assume // here that, because the memory is not shared, there are no other // threads to access it while it is written to. let mut guest_slices: Vec> = iovs .into_iter() .map(|iov| Ok(iov.as_slice_mut()?.unwrap())) .collect::>()?; // Read directly into the Wasm memory. let mut ioslices: Vec = guest_slices .iter_mut() .map(|s| IoSliceMut::new(&mut *s)) .collect(); f.read_vectored(&mut ioslices).await? }; Ok(types::Size::try_from(bytes_read)?) } async fn fd_pread<'a>( &mut self, fd: types::Fd, iovs: &types::IovecArray<'a>, offset: types::Filesize, ) -> Result { let f = self.table().get_file(u32::from(fd))?; let f = f.get_cap(FileCaps::READ | FileCaps::SEEK)?; let iovs: Vec> = iovs .iter() .map(|iov_ptr| { let iov_ptr = iov_ptr?; let iov: types::Iovec = iov_ptr.read()?; Ok(iov.buf.as_array(iov.buf_len)) }) .collect::>()?; // If the first iov structure is from shared memory we can safely assume // all the rest will be. We then read into memory based on the memory's // shared-ness: // - if not shared, we copy directly into the Wasm memory // - if shared, we use an intermediate buffer; this avoids Rust unsafety // due to holding on to a `&mut [u8]` of Wasm memory when we cannot // guarantee the `&mut` exclusivity--other threads could be modifying // the data as this functions writes to it. Though likely there is no // issue with OS writing to io structs in multi-threaded scenarios, // since we do not know here if `&dyn WasiFile` does anything else // (e.g., read), we cautiously incur some performance overhead by // copying twice. let is_shared_memory = iovs .iter() .next() .and_then(|s| Some(s.is_shared_memory())) .unwrap_or(false); let bytes_read: u64 = if is_shared_memory { // For shared memory, read into an intermediate buffer. Only the // first iov will be filled and even then the read is capped by the // `MAX_SHARED_BUFFER_SIZE`, so users are expected to re-call. let iov = iovs.into_iter().next(); if let Some(iov) = iov { let mut buffer = vec![0; (iov.len() as usize).min(MAX_SHARED_BUFFER_SIZE)]; let bytes_read = f .read_vectored_at(&mut [IoSliceMut::new(&mut buffer)], offset) .await?; iov.get_range(0..bytes_read.try_into()?) .expect("it should always be possible to slice the iov smaller") .copy_from_slice(&buffer[0..bytes_read.try_into()?])?; bytes_read } else { return Ok(0); } } else { // Convert all of the unsafe guest slices to safe ones--this uses // Wiggle's internal borrow checker to ensure no overlaps. We assume // here that, because the memory is not shared, there are no other // threads to access it while it is written to. let mut guest_slices: Vec> = iovs .into_iter() .map(|iov| Ok(iov.as_slice_mut()?.unwrap())) .collect::>()?; // Read directly into the Wasm memory. let mut ioslices: Vec = guest_slices .iter_mut() .map(|s| IoSliceMut::new(&mut *s)) .collect(); f.read_vectored_at(&mut ioslices, offset).await? }; Ok(types::Size::try_from(bytes_read)?) } async fn fd_write<'a>( &mut self, fd: types::Fd, ciovs: &types::CiovecArray<'a>, ) -> Result { let f = self.table().get_file(u32::from(fd))?; let f = f.get_cap(FileCaps::WRITE)?; let guest_slices: Vec> = ciovs .iter() .map(|iov_ptr| { let iov_ptr = iov_ptr?; let iov: types::Ciovec = iov_ptr.read()?; Ok(iov.buf.as_array(iov.buf_len).as_cow()?) }) .collect::>()?; let ioslices: Vec = guest_slices .iter() .map(|s| IoSlice::new(s.deref())) .collect(); let bytes_written = f.write_vectored(&ioslices).await?; Ok(types::Size::try_from(bytes_written)?) } async fn fd_pwrite<'a>( &mut self, fd: types::Fd, ciovs: &types::CiovecArray<'a>, offset: types::Filesize, ) -> Result { let f = self.table().get_file(u32::from(fd))?; let f = f.get_cap(FileCaps::WRITE | FileCaps::SEEK)?; let guest_slices: Vec> = ciovs .iter() .map(|iov_ptr| { let iov_ptr = iov_ptr?; let iov: types::Ciovec = iov_ptr.read()?; Ok(iov.buf.as_array(iov.buf_len).as_cow()?) }) .collect::>()?; let ioslices: Vec = guest_slices .iter() .map(|s| IoSlice::new(s.deref())) .collect(); let bytes_written = f.write_vectored_at(&ioslices, offset).await?; Ok(types::Size::try_from(bytes_written)?) } async fn fd_prestat_get(&mut self, fd: types::Fd) -> Result { let table = self.table(); let dir_entry: Arc = table.get(u32::from(fd)).map_err(|_| Error::badf())?; if let Some(ref preopen) = dir_entry.preopen_path() { let path_str = preopen.to_str().ok_or_else(|| Error::not_supported())?; let pr_name_len = u32::try_from(path_str.as_bytes().len())?; Ok(types::Prestat::Dir(types::PrestatDir { pr_name_len })) } else { Err(Error::not_supported().context("file is not a preopen")) } } async fn fd_prestat_dir_name<'a>( &mut self, fd: types::Fd, path: &GuestPtr<'a, u8>, path_max_len: types::Size, ) -> Result<(), Error> { let table = self.table(); let dir_entry: Arc = table.get(u32::from(fd)).map_err(|_| Error::not_dir())?; if let Some(ref preopen) = dir_entry.preopen_path() { let path_bytes = preopen .to_str() .ok_or_else(|| Error::not_supported())? .as_bytes(); let path_len = path_bytes.len(); if path_len < path_max_len as usize { return Err(Error::name_too_long()); } path.as_array(path_len as u32).copy_from_slice(path_bytes)?; Ok(()) } else { Err(Error::not_supported()) } } async fn fd_renumber(&mut self, from: types::Fd, to: types::Fd) -> Result<(), Error> { let table = self.table(); let from = u32::from(from); let to = u32::from(to); if !table.contains_key(from) { return Err(Error::badf()); } table.renumber(from, to) } async fn fd_seek( &mut self, fd: types::Fd, offset: types::Filedelta, whence: types::Whence, ) -> Result { use std::io::SeekFrom; let required_caps = if offset == 0 && whence == types::Whence::Cur { FileCaps::TELL } else { FileCaps::TELL | FileCaps::SEEK }; let whence = match whence { types::Whence::Cur => SeekFrom::Current(offset), types::Whence::End => SeekFrom::End(offset), types::Whence::Set => SeekFrom::Start(offset as u64), }; let newoffset = self .table() .get_file(u32::from(fd))? .get_cap(required_caps)? .seek(whence) .await?; Ok(newoffset) } async fn fd_sync(&mut self, fd: types::Fd) -> Result<(), Error> { self.table() .get_file(u32::from(fd))? .get_cap(FileCaps::SYNC)? .sync() .await?; Ok(()) } async fn fd_tell(&mut self, fd: types::Fd) -> Result { // XXX should this be stream_position? let offset = self .table() .get_file(u32::from(fd))? .get_cap(FileCaps::TELL)? .seek(std::io::SeekFrom::Current(0)) .await?; Ok(offset) } async fn fd_readdir<'a>( &mut self, fd: types::Fd, buf: &GuestPtr<'a, u8>, buf_len: types::Size, cookie: types::Dircookie, ) -> Result { let mut bufused = 0; let mut buf = buf.clone(); for entity in self .table() .get_dir(u32::from(fd))? .get_cap(DirCaps::READDIR)? .readdir(ReaddirCursor::from(cookie)) .await? { let entity = entity?; let dirent_raw = dirent_bytes(types::Dirent::try_from(&entity)?); let dirent_len: types::Size = dirent_raw.len().try_into()?; let name_raw = entity.name.as_bytes(); let name_len: types::Size = name_raw.len().try_into()?; // Copy as many bytes of the dirent as we can, up to the end of the buffer let dirent_copy_len = std::cmp::min(dirent_len, buf_len - bufused); buf.as_array(dirent_copy_len) .copy_from_slice(&dirent_raw[..dirent_copy_len as usize])?; // If the dirent struct wasnt compied entirely, return that we filled the buffer, which // tells libc that we're not at EOF. if dirent_copy_len < dirent_len { return Ok(buf_len); } buf = buf.add(dirent_copy_len)?; bufused += dirent_copy_len; // Copy as many bytes of the name as we can, up to the end of the buffer let name_copy_len = std::cmp::min(name_len, buf_len - bufused); buf.as_array(name_copy_len) .copy_from_slice(&name_raw[..name_copy_len as usize])?; // If the dirent struct wasn't copied entirely, return that we filled the buffer, which // tells libc that we're not at EOF if name_copy_len < name_len { return Ok(buf_len); } buf = buf.add(name_copy_len)?; bufused += name_copy_len; } Ok(bufused) } async fn path_create_directory<'a>( &mut self, dirfd: types::Fd, path: &GuestPtr<'a, str>, ) -> Result<(), Error> { self.table() .get_dir(u32::from(dirfd))? .get_cap(DirCaps::CREATE_DIRECTORY)? .create_dir(path.as_cow()?.deref()) .await } async fn path_filestat_get<'a>( &mut self, dirfd: types::Fd, flags: types::Lookupflags, path: &GuestPtr<'a, str>, ) -> Result { let filestat = self .table() .get_dir(u32::from(dirfd))? .get_cap(DirCaps::PATH_FILESTAT_GET)? .get_path_filestat( path.as_cow()?.deref(), flags.contains(types::Lookupflags::SYMLINK_FOLLOW), ) .await?; Ok(types::Filestat::from(filestat)) } async fn path_filestat_set_times<'a>( &mut self, dirfd: types::Fd, flags: types::Lookupflags, path: &GuestPtr<'a, str>, atim: types::Timestamp, mtim: types::Timestamp, fst_flags: types::Fstflags, ) -> Result<(), Error> { let set_atim = fst_flags.contains(types::Fstflags::ATIM); let set_atim_now = fst_flags.contains(types::Fstflags::ATIM_NOW); let set_mtim = fst_flags.contains(types::Fstflags::MTIM); let set_mtim_now = fst_flags.contains(types::Fstflags::MTIM_NOW); let atim = systimespec(set_atim, atim, set_atim_now).map_err(|e| e.context("atim"))?; let mtim = systimespec(set_mtim, mtim, set_mtim_now).map_err(|e| e.context("mtim"))?; self.table() .get_dir(u32::from(dirfd))? .get_cap(DirCaps::PATH_FILESTAT_SET_TIMES)? .set_times( path.as_cow()?.deref(), atim, mtim, flags.contains(types::Lookupflags::SYMLINK_FOLLOW), ) .await } async fn path_link<'a>( &mut self, src_fd: types::Fd, src_flags: types::Lookupflags, src_path: &GuestPtr<'a, str>, target_fd: types::Fd, target_path: &GuestPtr<'a, str>, ) -> Result<(), Error> { let table = self.table(); let src_dir = table.get_dir(u32::from(src_fd))?; let src_dir = src_dir.get_cap(DirCaps::LINK_SOURCE)?; let target_dir = table.get_dir(u32::from(target_fd))?; let target_dir = target_dir.get_cap(DirCaps::LINK_TARGET)?; let symlink_follow = src_flags.contains(types::Lookupflags::SYMLINK_FOLLOW); if symlink_follow { return Err(Error::invalid_argument() .context("symlink following on path_link is not supported")); } src_dir .hard_link( src_path.as_cow()?.deref(), target_dir.deref(), target_path.as_cow()?.deref(), ) .await } async fn path_open<'a>( &mut self, dirfd: types::Fd, dirflags: types::Lookupflags, path: &GuestPtr<'a, str>, oflags: types::Oflags, fs_rights_base: types::Rights, fs_rights_inheriting: types::Rights, fdflags: types::Fdflags, ) -> Result { let table = self.table(); let dirfd = u32::from(dirfd); if table.is::(dirfd) { return Err(Error::not_dir()); } let dir_entry = table.get_dir(dirfd)?; let symlink_follow = dirflags.contains(types::Lookupflags::SYMLINK_FOLLOW); let oflags = OFlags::from(&oflags); let fdflags = FdFlags::from(fdflags); let path = path.as_cow()?; if oflags.contains(OFlags::DIRECTORY) { if oflags.contains(OFlags::CREATE) || oflags.contains(OFlags::EXCLUSIVE) || oflags.contains(OFlags::TRUNCATE) { return Err(Error::invalid_argument().context("directory oflags")); } let dir_caps = dir_entry.child_dir_caps(DirCaps::from(&fs_rights_base)); let file_caps = dir_entry.child_file_caps(FileCaps::from(&fs_rights_inheriting)); let dir = dir_entry.get_cap(DirCaps::OPEN)?; let child_dir = dir.open_dir(symlink_follow, path.deref()).await?; drop(dir); let fd = table.push(Arc::new(DirEntry::new( dir_caps, file_caps, None, child_dir, )))?; Ok(types::Fd::from(fd)) } else { let mut required_caps = DirCaps::OPEN; if oflags.contains(OFlags::CREATE) { required_caps = required_caps | DirCaps::CREATE_FILE; } let file_caps = dir_entry.child_file_caps(FileCaps::from(&fs_rights_base)); let dir = dir_entry.get_cap(required_caps)?; let read = file_caps.contains(FileCaps::READ); let write = file_caps.contains(FileCaps::WRITE) || file_caps.contains(FileCaps::ALLOCATE) || file_caps.contains(FileCaps::FILESTAT_SET_SIZE); let file = dir .open_file(symlink_follow, path.deref(), oflags, read, write, fdflags) .await?; drop(dir); let fd = table.push(Arc::new(FileEntry::new(file_caps, file)))?; Ok(types::Fd::from(fd)) } } async fn path_readlink<'a>( &mut self, dirfd: types::Fd, path: &GuestPtr<'a, str>, buf: &GuestPtr<'a, u8>, buf_len: types::Size, ) -> Result { let link = self .table() .get_dir(u32::from(dirfd))? .get_cap(DirCaps::READLINK)? .read_link(path.as_cow()?.deref()) .await? .into_os_string() .into_string() .map_err(|_| Error::illegal_byte_sequence().context("link contents"))?; let link_bytes = link.as_bytes(); // Like posix readlink(2), silently truncate links when they are larger than the // destination buffer: let link_len = std::cmp::min(link_bytes.len(), buf_len as usize); buf.as_array(link_len as u32) .copy_from_slice(&link_bytes[..link_len])?; Ok(link_len as types::Size) } async fn path_remove_directory<'a>( &mut self, dirfd: types::Fd, path: &GuestPtr<'a, str>, ) -> Result<(), Error> { self.table() .get_dir(u32::from(dirfd))? .get_cap(DirCaps::REMOVE_DIRECTORY)? .remove_dir(path.as_cow()?.deref()) .await } async fn path_rename<'a>( &mut self, src_fd: types::Fd, src_path: &GuestPtr<'a, str>, dest_fd: types::Fd, dest_path: &GuestPtr<'a, str>, ) -> Result<(), Error> { let table = self.table(); let src_dir = table.get_dir(u32::from(src_fd))?; let src_dir = src_dir.get_cap(DirCaps::RENAME_SOURCE)?; let dest_dir = table.get_dir(u32::from(dest_fd))?; let dest_dir = dest_dir.get_cap(DirCaps::RENAME_TARGET)?; src_dir .rename( src_path.as_cow()?.deref(), dest_dir.deref(), dest_path.as_cow()?.deref(), ) .await } async fn path_symlink<'a>( &mut self, src_path: &GuestPtr<'a, str>, dirfd: types::Fd, dest_path: &GuestPtr<'a, str>, ) -> Result<(), Error> { self.table() .get_dir(u32::from(dirfd))? .get_cap(DirCaps::SYMLINK)? .symlink(src_path.as_cow()?.deref(), dest_path.as_cow()?.deref()) .await } async fn path_unlink_file<'a>( &mut self, dirfd: types::Fd, path: &GuestPtr<'a, str>, ) -> Result<(), Error> { self.table() .get_dir(u32::from(dirfd))? .get_cap(DirCaps::UNLINK_FILE)? .unlink_file(path.as_cow()?.deref()) .await } async fn poll_oneoff<'a>( &mut self, subs: &GuestPtr<'a, types::Subscription>, events: &GuestPtr<'a, types::Event>, nsubscriptions: types::Size, ) -> Result { if nsubscriptions == 0 { return Err(Error::invalid_argument().context("nsubscriptions must be nonzero")); } // Special-case a `poll_oneoff` which is just sleeping on a single // relative timer event, such as what WASI libc uses to implement sleep // functions. This supports all clock IDs, because POSIX says that // `clock_settime` doesn't effect relative sleeps. if nsubscriptions == 1 { let sub = subs.read()?; if let types::SubscriptionU::Clock(clocksub) = sub.u { if !clocksub .flags .contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME) { self.sched .sleep(Duration::from_nanos(clocksub.timeout)) .await?; events.write(types::Event { userdata: sub.userdata, error: types::Errno::Success, type_: types::Eventtype::Clock, fd_readwrite: fd_readwrite_empty(), })?; return Ok(1); } } } let table = &self.table; // We need these refmuts to outlive Poll, which will hold the &mut dyn WasiFile inside let mut read_refs: Vec<(Arc, Option)> = Vec::new(); let mut write_refs: Vec<(Arc, Option)> = Vec::new(); let mut poll = Poll::new(); let subs = subs.as_array(nsubscriptions); for sub_elem in subs.iter() { let sub_ptr = sub_elem?; let sub = sub_ptr.read()?; match sub.u { types::SubscriptionU::Clock(clocksub) => match clocksub.id { types::Clockid::Monotonic => { let clock = self.clocks.monotonic()?; let precision = Duration::from_nanos(clocksub.precision); let duration = Duration::from_nanos(clocksub.timeout); let start = if clocksub .flags .contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME) { clock.creation_time } else { clock.abs_clock.now(precision) }; let deadline = start .checked_add(duration) .ok_or_else(|| Error::overflow().context("deadline"))?; poll.subscribe_monotonic_clock( &*clock.abs_clock, deadline, precision, sub.userdata.into(), ) } types::Clockid::Realtime => { // POSIX specifies that functions like `nanosleep` and others use the // `REALTIME` clock. But it also says that `clock_settime` has no effect // on threads waiting in these functions. MONOTONIC should always have // resolution at least as good as REALTIME, so we can translate a // non-absolute `REALTIME` request into a `MONOTONIC` request. let clock = self.clocks.monotonic()?; let precision = Duration::from_nanos(clocksub.precision); let duration = Duration::from_nanos(clocksub.timeout); let deadline = if clocksub .flags .contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME) { return Err(Error::not_supported()); } else { clock .abs_clock .now(precision) .checked_add(duration) .ok_or_else(|| Error::overflow().context("deadline"))? }; poll.subscribe_monotonic_clock( &*clock.abs_clock, deadline, precision, sub.userdata.into(), ) } _ => Err(Error::invalid_argument() .context("timer subscriptions only support monotonic timer"))?, }, types::SubscriptionU::FdRead(readsub) => { let fd = readsub.file_descriptor; let file_ref = table.get_file(u32::from(fd))?; let _file = file_ref.get_cap(FileCaps::POLL_READWRITE)?; read_refs.push((file_ref, Some(sub.userdata.into()))); } types::SubscriptionU::FdWrite(writesub) => { let fd = writesub.file_descriptor; let file_ref = table.get_file(u32::from(fd))?; let _file = file_ref.get_cap(FileCaps::POLL_READWRITE)?; write_refs.push((file_ref, Some(sub.userdata.into()))); } } } let mut read_mut_refs: Vec<(&dyn WasiFile, Userdata)> = Vec::new(); for (file_lock, userdata) in read_refs.iter_mut() { let file = file_lock.get_cap(FileCaps::POLL_READWRITE)?; read_mut_refs.push((file, userdata.take().unwrap())); } for (f, ud) in read_mut_refs.iter_mut() { poll.subscribe_read(*f, *ud); } let mut write_mut_refs: Vec<(&dyn WasiFile, Userdata)> = Vec::new(); for (file_lock, userdata) in write_refs.iter_mut() { let file = file_lock.get_cap(FileCaps::POLL_READWRITE)?; write_mut_refs.push((file, userdata.take().unwrap())); } for (f, ud) in write_mut_refs.iter_mut() { poll.subscribe_write(*f, *ud); } self.sched.poll_oneoff(&mut poll).await?; let results = poll.results(); let num_results = results.len(); assert!( num_results <= nsubscriptions as usize, "results exceeds subscriptions" ); let events = events.as_array( num_results .try_into() .expect("not greater than nsubscriptions"), ); for ((result, userdata), event_elem) in results.into_iter().zip(events.iter()) { let event_ptr = event_elem?; let userdata: types::Userdata = userdata.into(); event_ptr.write(match result { SubscriptionResult::Read(r) => { let type_ = types::Eventtype::FdRead; match r { Ok((nbytes, flags)) => types::Event { userdata, error: types::Errno::Success, type_, fd_readwrite: types::EventFdReadwrite { nbytes, flags: types::Eventrwflags::from(&flags), }, }, Err(e) => types::Event { userdata, error: e.downcast().map_err(Error::trap)?, type_, fd_readwrite: fd_readwrite_empty(), }, } } SubscriptionResult::Write(r) => { let type_ = types::Eventtype::FdWrite; match r { Ok((nbytes, flags)) => types::Event { userdata, error: types::Errno::Success, type_, fd_readwrite: types::EventFdReadwrite { nbytes, flags: types::Eventrwflags::from(&flags), }, }, Err(e) => types::Event { userdata, error: e.downcast().map_err(Error::trap)?, type_, fd_readwrite: fd_readwrite_empty(), }, } } SubscriptionResult::MonotonicClock(r) => { let type_ = types::Eventtype::Clock; types::Event { userdata, error: match r { Ok(()) => types::Errno::Success, Err(e) => e.downcast().map_err(Error::trap)?, }, type_, fd_readwrite: fd_readwrite_empty(), } } })?; } Ok(num_results.try_into().expect("results fit into memory")) } async fn proc_exit(&mut self, status: types::Exitcode) -> anyhow::Error { // Check that the status is within WASI's range. if status < 126 { I32Exit(status as i32).into() } else { anyhow::Error::msg("exit with invalid exit status outside of [0..126)") } } async fn proc_raise(&mut self, _sig: types::Signal) -> Result<(), Error> { Err(Error::trap(anyhow::Error::msg("proc_raise unsupported"))) } async fn sched_yield(&mut self) -> Result<(), Error> { self.sched.sched_yield().await } async fn random_get<'a>( &mut self, buf: &GuestPtr<'a, u8>, buf_len: types::Size, ) -> Result<(), Error> { let buf = buf.as_array(buf_len); if buf.is_shared_memory() { // If the Wasm memory is shared, copy to an intermediate buffer to // avoid Rust unsafety (i.e., the called function could rely on // `&mut [u8]`'s exclusive ownership which is not guaranteed due to // potential access from other threads). let mut copied: u32 = 0; while copied < buf.len() { let len = (buf.len() - copied).min(MAX_SHARED_BUFFER_SIZE as u32); let mut tmp = vec![0; len as usize]; self.random.lock().unwrap().try_fill_bytes(&mut tmp)?; let dest = buf .get_range(copied..copied + len) .unwrap() .as_unsafe_slice_mut()?; dest.copy_from_slice(&tmp)?; copied += len; } } else { // If the Wasm memory is non-shared, copy directly into the linear // memory. let mem = &mut buf.as_slice_mut()?.unwrap(); self.random.lock().unwrap().try_fill_bytes(mem)?; } Ok(()) } async fn sock_accept( &mut self, fd: types::Fd, flags: types::Fdflags, ) -> Result { let table = self.table(); let f = table.get_file(u32::from(fd))?; let f = f.get_cap(FileCaps::READ)?; let file = f.sock_accept(FdFlags::from(flags)).await?; let file_caps = FileCaps::READ | FileCaps::WRITE | FileCaps::FDSTAT_SET_FLAGS | FileCaps::POLL_READWRITE | FileCaps::FILESTAT_GET; let fd = table.push(Arc::new(FileEntry::new(file_caps, file)))?; Ok(types::Fd::from(fd)) } async fn sock_recv<'a>( &mut self, fd: types::Fd, ri_data: &types::IovecArray<'a>, ri_flags: types::Riflags, ) -> Result<(types::Size, types::Roflags), Error> { let f = self.table().get_file(u32::from(fd))?; let f = f.get_cap(FileCaps::READ)?; let iovs: Vec> = ri_data .iter() .map(|iov_ptr| { let iov_ptr = iov_ptr?; let iov: types::Iovec = iov_ptr.read()?; Ok(iov.buf.as_array(iov.buf_len)) }) .collect::>()?; // If the first iov structure is from shared memory we can safely assume // all the rest will be. We then read into memory based on the memory's // shared-ness: // - if not shared, we copy directly into the Wasm memory // - if shared, we use an intermediate buffer; this avoids Rust unsafety // due to holding on to a `&mut [u8]` of Wasm memory when we cannot // guarantee the `&mut` exclusivity--other threads could be modifying // the data as this functions writes to it. Though likely there is no // issue with OS writing to io structs in multi-threaded scenarios, // since we do not know here if `&dyn WasiFile` does anything else // (e.g., read), we cautiously incur some performance overhead by // copying twice. let is_shared_memory = iovs .iter() .next() .and_then(|s| Some(s.is_shared_memory())) .unwrap_or(false); let (bytes_read, ro_flags) = if is_shared_memory { // For shared memory, read into an intermediate buffer. Only the // first iov will be filled and even then the read is capped by the // `MAX_SHARED_BUFFER_SIZE`, so users are expected to re-call. let iov = iovs.into_iter().next(); if let Some(iov) = iov { let mut buffer = vec![0; (iov.len() as usize).min(MAX_SHARED_BUFFER_SIZE)]; let (bytes_read, ro_flags) = f .sock_recv(&mut [IoSliceMut::new(&mut buffer)], RiFlags::from(ri_flags)) .await?; iov.get_range(0..bytes_read.try_into()?) .expect("it should always be possible to slice the iov smaller") .copy_from_slice(&buffer[0..bytes_read.try_into()?])?; (bytes_read, ro_flags) } else { return Ok((0, RoFlags::empty().into())); } } else { // Convert all of the unsafe guest slices to safe ones--this uses // Wiggle's internal borrow checker to ensure no overlaps. We assume // here that, because the memory is not shared, there are no other // threads to access it while it is written to. let mut guest_slices: Vec> = iovs .into_iter() .map(|iov| Ok(iov.as_slice_mut()?.unwrap())) .collect::>()?; // Read directly into the Wasm memory. let mut ioslices: Vec = guest_slices .iter_mut() .map(|s| IoSliceMut::new(&mut *s)) .collect(); f.sock_recv(&mut ioslices, RiFlags::from(ri_flags)).await? }; Ok((types::Size::try_from(bytes_read)?, ro_flags.into())) } async fn sock_send<'a>( &mut self, fd: types::Fd, si_data: &types::CiovecArray<'a>, _si_flags: types::Siflags, ) -> Result { let f = self.table().get_file(u32::from(fd))?; let f = f.get_cap(FileCaps::WRITE)?; let guest_slices: Vec> = si_data .iter() .map(|iov_ptr| { let iov_ptr = iov_ptr?; let iov: types::Ciovec = iov_ptr.read()?; Ok(iov.buf.as_array(iov.buf_len).as_cow()?) }) .collect::>()?; let ioslices: Vec = guest_slices .iter() .map(|s| IoSlice::new(s.deref())) .collect(); let bytes_written = f.sock_send(&ioslices, SiFlags::empty()).await?; Ok(types::Size::try_from(bytes_written)?) } async fn sock_shutdown(&mut self, fd: types::Fd, how: types::Sdflags) -> Result<(), Error> { let f = self.table().get_file(u32::from(fd))?; let f = f.get_cap(FileCaps::FDSTAT_SET_FLAGS)?; f.sock_shutdown(SdFlags::from(how)).await } } impl From for Advice { fn from(advice: types::Advice) -> Advice { match advice { types::Advice::Normal => Advice::Normal, types::Advice::Sequential => Advice::Sequential, types::Advice::Random => Advice::Random, types::Advice::Willneed => Advice::WillNeed, types::Advice::Dontneed => Advice::DontNeed, types::Advice::Noreuse => Advice::NoReuse, } } } impl From<&FdStat> for types::Fdstat { fn from(fdstat: &FdStat) -> types::Fdstat { types::Fdstat { fs_filetype: types::Filetype::from(&fdstat.filetype), fs_rights_base: types::Rights::from(&fdstat.caps), fs_rights_inheriting: types::Rights::empty(), fs_flags: types::Fdflags::from(fdstat.flags), } } } impl From<&DirFdStat> for types::Fdstat { fn from(dirstat: &DirFdStat) -> types::Fdstat { let fs_rights_base = types::Rights::from(&dirstat.dir_caps); let fs_rights_inheriting = types::Rights::from(&dirstat.file_caps) | fs_rights_base; types::Fdstat { fs_filetype: types::Filetype::Directory, fs_rights_base, fs_rights_inheriting, fs_flags: types::Fdflags::empty(), } } } // FileCaps can always be represented as wasi Rights impl From<&FileCaps> for types::Rights { fn from(caps: &FileCaps) -> types::Rights { let mut rights = types::Rights::empty(); if caps.contains(FileCaps::DATASYNC) { rights = rights | types::Rights::FD_DATASYNC; } if caps.contains(FileCaps::READ) { rights = rights | types::Rights::FD_READ; } if caps.contains(FileCaps::SEEK) { rights = rights | types::Rights::FD_SEEK; } if caps.contains(FileCaps::FDSTAT_SET_FLAGS) { rights = rights | types::Rights::FD_FDSTAT_SET_FLAGS; } if caps.contains(FileCaps::SYNC) { rights = rights | types::Rights::FD_SYNC; } if caps.contains(FileCaps::TELL) { rights = rights | types::Rights::FD_TELL; } if caps.contains(FileCaps::WRITE) { rights = rights | types::Rights::FD_WRITE; } if caps.contains(FileCaps::ADVISE) { rights = rights | types::Rights::FD_ADVISE; } if caps.contains(FileCaps::ALLOCATE) { rights = rights | types::Rights::FD_ALLOCATE; } if caps.contains(FileCaps::FILESTAT_GET) { rights = rights | types::Rights::FD_FILESTAT_GET; } if caps.contains(FileCaps::FILESTAT_SET_SIZE) { rights = rights | types::Rights::FD_FILESTAT_SET_SIZE; } if caps.contains(FileCaps::FILESTAT_SET_TIMES) { rights = rights | types::Rights::FD_FILESTAT_SET_TIMES; } if caps.contains(FileCaps::POLL_READWRITE) { rights = rights | types::Rights::POLL_FD_READWRITE; } rights } } // FileCaps are a subset of wasi Rights - not all Rights have a valid representation as FileCaps impl From<&types::Rights> for FileCaps { fn from(rights: &types::Rights) -> FileCaps { let mut caps = FileCaps::empty(); if rights.contains(types::Rights::FD_DATASYNC) { caps = caps | FileCaps::DATASYNC; } if rights.contains(types::Rights::FD_READ) { caps = caps | FileCaps::READ; } if rights.contains(types::Rights::FD_SEEK) { caps = caps | FileCaps::SEEK; } if rights.contains(types::Rights::FD_FDSTAT_SET_FLAGS) { caps = caps | FileCaps::FDSTAT_SET_FLAGS; } if rights.contains(types::Rights::FD_SYNC) { caps = caps | FileCaps::SYNC; } if rights.contains(types::Rights::FD_TELL) { caps = caps | FileCaps::TELL; } if rights.contains(types::Rights::FD_WRITE) { caps = caps | FileCaps::WRITE; } if rights.contains(types::Rights::FD_ADVISE) { caps = caps | FileCaps::ADVISE; } if rights.contains(types::Rights::FD_ALLOCATE) { caps = caps | FileCaps::ALLOCATE; } if rights.contains(types::Rights::FD_FILESTAT_GET) { caps = caps | FileCaps::FILESTAT_GET; } if rights.contains(types::Rights::FD_FILESTAT_SET_SIZE) { caps = caps | FileCaps::FILESTAT_SET_SIZE; } if rights.contains(types::Rights::FD_FILESTAT_SET_TIMES) { caps = caps | FileCaps::FILESTAT_SET_TIMES; } if rights.contains(types::Rights::POLL_FD_READWRITE) { caps = caps | FileCaps::POLL_READWRITE; } caps } } // DirCaps can always be represented as wasi Rights impl From<&DirCaps> for types::Rights { fn from(caps: &DirCaps) -> types::Rights { let mut rights = types::Rights::empty(); if caps.contains(DirCaps::CREATE_DIRECTORY) { rights = rights | types::Rights::PATH_CREATE_DIRECTORY; } if caps.contains(DirCaps::CREATE_FILE) { rights = rights | types::Rights::PATH_CREATE_FILE; } if caps.contains(DirCaps::LINK_SOURCE) { rights = rights | types::Rights::PATH_LINK_SOURCE; } if caps.contains(DirCaps::LINK_TARGET) { rights = rights | types::Rights::PATH_LINK_TARGET; } if caps.contains(DirCaps::OPEN) { rights = rights | types::Rights::PATH_OPEN; } if caps.contains(DirCaps::READDIR) { rights = rights | types::Rights::FD_READDIR; } if caps.contains(DirCaps::READLINK) { rights = rights | types::Rights::PATH_READLINK; } if caps.contains(DirCaps::RENAME_SOURCE) { rights = rights | types::Rights::PATH_RENAME_SOURCE; } if caps.contains(DirCaps::RENAME_TARGET) { rights = rights | types::Rights::PATH_RENAME_TARGET; } if caps.contains(DirCaps::SYMLINK) { rights = rights | types::Rights::PATH_SYMLINK; } if caps.contains(DirCaps::REMOVE_DIRECTORY) { rights = rights | types::Rights::PATH_REMOVE_DIRECTORY; } if caps.contains(DirCaps::UNLINK_FILE) { rights = rights | types::Rights::PATH_UNLINK_FILE; } if caps.contains(DirCaps::PATH_FILESTAT_GET) { rights = rights | types::Rights::PATH_FILESTAT_GET; } if caps.contains(DirCaps::PATH_FILESTAT_SET_TIMES) { rights = rights | types::Rights::PATH_FILESTAT_SET_TIMES; } if caps.contains(DirCaps::FILESTAT_GET) { rights = rights | types::Rights::FD_FILESTAT_GET; } if caps.contains(DirCaps::FILESTAT_SET_TIMES) { rights = rights | types::Rights::FD_FILESTAT_SET_TIMES; } rights } } // DirCaps are a subset of wasi Rights - not all Rights have a valid representation as DirCaps impl From<&types::Rights> for DirCaps { fn from(rights: &types::Rights) -> DirCaps { let mut caps = DirCaps::empty(); if rights.contains(types::Rights::PATH_CREATE_DIRECTORY) { caps = caps | DirCaps::CREATE_DIRECTORY; } if rights.contains(types::Rights::PATH_CREATE_FILE) { caps = caps | DirCaps::CREATE_FILE; } if rights.contains(types::Rights::PATH_LINK_SOURCE) { caps = caps | DirCaps::LINK_SOURCE; } if rights.contains(types::Rights::PATH_LINK_TARGET) { caps = caps | DirCaps::LINK_TARGET; } if rights.contains(types::Rights::PATH_OPEN) { caps = caps | DirCaps::OPEN; } if rights.contains(types::Rights::FD_READDIR) { caps = caps | DirCaps::READDIR; } if rights.contains(types::Rights::PATH_READLINK) { caps = caps | DirCaps::READLINK; } if rights.contains(types::Rights::PATH_RENAME_SOURCE) { caps = caps | DirCaps::RENAME_SOURCE; } if rights.contains(types::Rights::PATH_RENAME_TARGET) { caps = caps | DirCaps::RENAME_TARGET; } if rights.contains(types::Rights::PATH_SYMLINK) { caps = caps | DirCaps::SYMLINK; } if rights.contains(types::Rights::PATH_REMOVE_DIRECTORY) { caps = caps | DirCaps::REMOVE_DIRECTORY; } if rights.contains(types::Rights::PATH_UNLINK_FILE) { caps = caps | DirCaps::UNLINK_FILE; } if rights.contains(types::Rights::PATH_FILESTAT_GET) { caps = caps | DirCaps::PATH_FILESTAT_GET; } if rights.contains(types::Rights::PATH_FILESTAT_SET_TIMES) { caps = caps | DirCaps::PATH_FILESTAT_SET_TIMES; } if rights.contains(types::Rights::FD_FILESTAT_GET) { caps = caps | DirCaps::FILESTAT_GET; } if rights.contains(types::Rights::FD_FILESTAT_SET_TIMES) { caps = caps | DirCaps::FILESTAT_SET_TIMES; } caps } } impl From<&FileType> for types::Filetype { fn from(ft: &FileType) -> types::Filetype { match ft { FileType::Directory => types::Filetype::Directory, FileType::BlockDevice => types::Filetype::BlockDevice, FileType::CharacterDevice => types::Filetype::CharacterDevice, FileType::RegularFile => types::Filetype::RegularFile, FileType::SocketDgram => types::Filetype::SocketDgram, FileType::SocketStream => types::Filetype::SocketStream, FileType::SymbolicLink => types::Filetype::SymbolicLink, FileType::Unknown => types::Filetype::Unknown, FileType::Pipe => types::Filetype::Unknown, } } } macro_rules! convert_flags { ($from:ty, $to:ty, $($flag:ident),+) => { impl From<$from> for $to { fn from(f: $from) -> $to { let mut out = <$to>::empty(); $( if f.contains(<$from>::$flag) { out |= <$to>::$flag; } )+ out } } } } macro_rules! convert_flags_bidirectional { ($from:ty, $to:ty, $($rest:tt)*) => { convert_flags!($from, $to, $($rest)*); convert_flags!($to, $from, $($rest)*); } } convert_flags_bidirectional!( FdFlags, types::Fdflags, APPEND, DSYNC, NONBLOCK, RSYNC, SYNC ); convert_flags_bidirectional!(RiFlags, types::Riflags, RECV_PEEK, RECV_WAITALL); convert_flags_bidirectional!(RoFlags, types::Roflags, RECV_DATA_TRUNCATED); convert_flags_bidirectional!(SdFlags, types::Sdflags, RD, WR); impl From<&types::Oflags> for OFlags { fn from(oflags: &types::Oflags) -> OFlags { let mut out = OFlags::empty(); if oflags.contains(types::Oflags::CREAT) { out = out | OFlags::CREATE; } if oflags.contains(types::Oflags::DIRECTORY) { out = out | OFlags::DIRECTORY; } if oflags.contains(types::Oflags::EXCL) { out = out | OFlags::EXCLUSIVE; } if oflags.contains(types::Oflags::TRUNC) { out = out | OFlags::TRUNCATE; } out } } impl From<&OFlags> for types::Oflags { fn from(oflags: &OFlags) -> types::Oflags { let mut out = types::Oflags::empty(); if oflags.contains(OFlags::CREATE) { out = out | types::Oflags::CREAT; } if oflags.contains(OFlags::DIRECTORY) { out = out | types::Oflags::DIRECTORY; } if oflags.contains(OFlags::EXCLUSIVE) { out = out | types::Oflags::EXCL; } if oflags.contains(OFlags::TRUNCATE) { out = out | types::Oflags::TRUNC; } out } } impl From for types::Filestat { fn from(stat: Filestat) -> types::Filestat { types::Filestat { dev: stat.device_id, ino: stat.inode, filetype: types::Filetype::from(&stat.filetype), nlink: stat.nlink, size: stat.size, atim: stat .atim .map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() as u64) .unwrap_or(0), mtim: stat .mtim .map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() as u64) .unwrap_or(0), ctim: stat .ctim .map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() as u64) .unwrap_or(0), } } } impl TryFrom<&ReaddirEntity> for types::Dirent { type Error = Error; fn try_from(e: &ReaddirEntity) -> Result { Ok(types::Dirent { d_ino: e.inode, d_namlen: e.name.as_bytes().len().try_into()?, d_type: types::Filetype::from(&e.filetype), d_next: e.next.into(), }) } } fn dirent_bytes(dirent: types::Dirent) -> Vec { use wiggle::GuestType; assert_eq!( types::Dirent::guest_size(), std::mem::size_of::() as _, "Dirent guest repr and host repr should match" ); assert_eq!( 1, std::mem::size_of_val(&dirent.d_type), "Dirent member d_type should be endian-invariant" ); let size = types::Dirent::guest_size() .try_into() .expect("Dirent is smaller than 2^32"); let mut bytes = Vec::with_capacity(size); bytes.resize(size, 0); let ptr = bytes.as_mut_ptr().cast::(); let guest_dirent = types::Dirent { d_ino: dirent.d_ino.to_le(), d_namlen: dirent.d_namlen.to_le(), d_type: dirent.d_type, // endian-invariant d_next: dirent.d_next.to_le(), }; unsafe { ptr.write_unaligned(guest_dirent) }; bytes } impl From<&RwEventFlags> for types::Eventrwflags { fn from(flags: &RwEventFlags) -> types::Eventrwflags { let mut out = types::Eventrwflags::empty(); if flags.contains(RwEventFlags::HANGUP) { out = out | types::Eventrwflags::FD_READWRITE_HANGUP; } out } } fn fd_readwrite_empty() -> types::EventFdReadwrite { types::EventFdReadwrite { nbytes: 0, flags: types::Eventrwflags::empty(), } } fn systimespec( set: bool, ts: types::Timestamp, now: bool, ) -> Result, Error> { if set && now { Err(Error::invalid_argument()) } else if set { Ok(Some(SystemTimeSpec::Absolute( SystemClock::UNIX_EPOCH + Duration::from_nanos(ts), ))) } else if now { Ok(Some(SystemTimeSpec::SymbolicNow)) } else { Ok(None) } }