diff options
Diffstat (limited to '')
-rw-r--r-- | library/std/src/sys/wasi/args.rs | 62 | ||||
-rw-r--r-- | library/std/src/sys/wasi/env.rs | 9 | ||||
-rw-r--r-- | library/std/src/sys/wasi/fd.rs | 307 | ||||
-rw-r--r-- | library/std/src/sys/wasi/fs.rs | 798 | ||||
-rw-r--r-- | library/std/src/sys/wasi/io.rs | 73 | ||||
-rw-r--r-- | library/std/src/sys/wasi/mod.rs | 100 | ||||
-rw-r--r-- | library/std/src/sys/wasi/net.rs | 527 | ||||
-rw-r--r-- | library/std/src/sys/wasi/os.rs | 243 | ||||
-rw-r--r-- | library/std/src/sys/wasi/stdio.rs | 112 | ||||
-rw-r--r-- | library/std/src/sys/wasi/thread.rs | 81 | ||||
-rw-r--r-- | library/std/src/sys/wasi/time.rs | 65 |
11 files changed, 2377 insertions, 0 deletions
diff --git a/library/std/src/sys/wasi/args.rs b/library/std/src/sys/wasi/args.rs new file mode 100644 index 000000000..c42c310e3 --- /dev/null +++ b/library/std/src/sys/wasi/args.rs @@ -0,0 +1,62 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use crate::ffi::{CStr, OsStr, OsString}; +use crate::fmt; +use crate::os::wasi::ffi::OsStrExt; +use crate::vec; + +pub struct Args { + iter: vec::IntoIter<OsString>, +} + +impl !Send for Args {} +impl !Sync for Args {} + +/// Returns the command line arguments +pub fn args() -> Args { + Args { iter: maybe_args().unwrap_or(Vec::new()).into_iter() } +} + +fn maybe_args() -> Option<Vec<OsString>> { + unsafe { + let (argc, buf_size) = wasi::args_sizes_get().ok()?; + let mut argv = Vec::with_capacity(argc); + let mut buf = Vec::with_capacity(buf_size); + wasi::args_get(argv.as_mut_ptr(), buf.as_mut_ptr()).ok()?; + argv.set_len(argc); + let mut ret = Vec::with_capacity(argc); + for ptr in argv { + let s = CStr::from_ptr(ptr.cast()); + ret.push(OsStr::from_bytes(s.to_bytes()).to_owned()); + } + Some(ret) + } +} + +impl fmt::Debug for Args { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.iter.as_slice().fmt(f) + } +} + +impl Iterator for Args { + type Item = OsString; + fn next(&mut self) -> Option<OsString> { + self.iter.next() + } + fn size_hint(&self) -> (usize, Option<usize>) { + self.iter.size_hint() + } +} + +impl ExactSizeIterator for Args { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl DoubleEndedIterator for Args { + fn next_back(&mut self) -> Option<OsString> { + self.iter.next_back() + } +} diff --git a/library/std/src/sys/wasi/env.rs b/library/std/src/sys/wasi/env.rs new file mode 100644 index 000000000..730e356d7 --- /dev/null +++ b/library/std/src/sys/wasi/env.rs @@ -0,0 +1,9 @@ +pub mod os { + pub const FAMILY: &str = ""; + pub const OS: &str = ""; + pub const DLL_PREFIX: &str = ""; + pub const DLL_SUFFIX: &str = ".wasm"; + pub const DLL_EXTENSION: &str = "wasm"; + pub const EXE_SUFFIX: &str = ".wasm"; + pub const EXE_EXTENSION: &str = "wasm"; +} diff --git a/library/std/src/sys/wasi/fd.rs b/library/std/src/sys/wasi/fd.rs new file mode 100644 index 000000000..0b9c8e61d --- /dev/null +++ b/library/std/src/sys/wasi/fd.rs @@ -0,0 +1,307 @@ +#![deny(unsafe_op_in_unsafe_fn)] +#![allow(dead_code)] + +use super::err2io; +use crate::io::{self, IoSlice, IoSliceMut, SeekFrom}; +use crate::mem; +use crate::net::Shutdown; +use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; + +#[derive(Debug)] +pub struct WasiFd { + fd: OwnedFd, +} + +fn iovec<'a>(a: &'a mut [IoSliceMut<'_>]) -> &'a [wasi::Iovec] { + assert_eq!(mem::size_of::<IoSliceMut<'_>>(), mem::size_of::<wasi::Iovec>()); + assert_eq!(mem::align_of::<IoSliceMut<'_>>(), mem::align_of::<wasi::Iovec>()); + // SAFETY: `IoSliceMut` and `IoVec` have exactly the same memory layout + unsafe { mem::transmute(a) } +} + +fn ciovec<'a>(a: &'a [IoSlice<'_>]) -> &'a [wasi::Ciovec] { + assert_eq!(mem::size_of::<IoSlice<'_>>(), mem::size_of::<wasi::Ciovec>()); + assert_eq!(mem::align_of::<IoSlice<'_>>(), mem::align_of::<wasi::Ciovec>()); + // SAFETY: `IoSlice` and `CIoVec` have exactly the same memory layout + unsafe { mem::transmute(a) } +} + +impl WasiFd { + pub fn datasync(&self) -> io::Result<()> { + unsafe { wasi::fd_datasync(self.as_raw_fd() as wasi::Fd).map_err(err2io) } + } + + pub fn pread(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> { + unsafe { wasi::fd_pread(self.as_raw_fd() as wasi::Fd, iovec(bufs), offset).map_err(err2io) } + } + + pub fn pwrite(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> { + unsafe { + wasi::fd_pwrite(self.as_raw_fd() as wasi::Fd, ciovec(bufs), offset).map_err(err2io) + } + } + + pub fn read(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> { + unsafe { wasi::fd_read(self.as_raw_fd() as wasi::Fd, iovec(bufs)).map_err(err2io) } + } + + pub fn write(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> { + unsafe { wasi::fd_write(self.as_raw_fd() as wasi::Fd, ciovec(bufs)).map_err(err2io) } + } + + pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> { + let (whence, offset) = match pos { + SeekFrom::Start(pos) => (wasi::WHENCE_SET, pos as i64), + SeekFrom::End(pos) => (wasi::WHENCE_END, pos), + SeekFrom::Current(pos) => (wasi::WHENCE_CUR, pos), + }; + unsafe { wasi::fd_seek(self.as_raw_fd() as wasi::Fd, offset, whence).map_err(err2io) } + } + + pub fn tell(&self) -> io::Result<u64> { + unsafe { wasi::fd_tell(self.as_raw_fd() as wasi::Fd).map_err(err2io) } + } + + // FIXME: __wasi_fd_fdstat_get + + pub fn set_flags(&self, flags: wasi::Fdflags) -> io::Result<()> { + unsafe { wasi::fd_fdstat_set_flags(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) } + } + + pub fn set_rights(&self, base: wasi::Rights, inheriting: wasi::Rights) -> io::Result<()> { + unsafe { + wasi::fd_fdstat_set_rights(self.as_raw_fd() as wasi::Fd, base, inheriting) + .map_err(err2io) + } + } + + pub fn sync(&self) -> io::Result<()> { + unsafe { wasi::fd_sync(self.as_raw_fd() as wasi::Fd).map_err(err2io) } + } + + pub fn advise(&self, offset: u64, len: u64, advice: wasi::Advice) -> io::Result<()> { + unsafe { + wasi::fd_advise(self.as_raw_fd() as wasi::Fd, offset, len, advice).map_err(err2io) + } + } + + pub fn allocate(&self, offset: u64, len: u64) -> io::Result<()> { + unsafe { wasi::fd_allocate(self.as_raw_fd() as wasi::Fd, offset, len).map_err(err2io) } + } + + pub fn create_directory(&self, path: &str) -> io::Result<()> { + unsafe { wasi::path_create_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } + } + + pub fn link( + &self, + old_flags: wasi::Lookupflags, + old_path: &str, + new_fd: &WasiFd, + new_path: &str, + ) -> io::Result<()> { + unsafe { + wasi::path_link( + self.as_raw_fd() as wasi::Fd, + old_flags, + old_path, + new_fd.as_raw_fd() as wasi::Fd, + new_path, + ) + .map_err(err2io) + } + } + + pub fn open( + &self, + dirflags: wasi::Lookupflags, + path: &str, + oflags: wasi::Oflags, + fs_rights_base: wasi::Rights, + fs_rights_inheriting: wasi::Rights, + fs_flags: wasi::Fdflags, + ) -> io::Result<WasiFd> { + unsafe { + wasi::path_open( + self.as_raw_fd() as wasi::Fd, + dirflags, + path, + oflags, + fs_rights_base, + fs_rights_inheriting, + fs_flags, + ) + .map(|fd| WasiFd::from_raw_fd(fd as RawFd)) + .map_err(err2io) + } + } + + pub fn readdir(&self, buf: &mut [u8], cookie: wasi::Dircookie) -> io::Result<usize> { + unsafe { + wasi::fd_readdir(self.as_raw_fd() as wasi::Fd, buf.as_mut_ptr(), buf.len(), cookie) + .map_err(err2io) + } + } + + pub fn readlink(&self, path: &str, buf: &mut [u8]) -> io::Result<usize> { + unsafe { + wasi::path_readlink(self.as_raw_fd() as wasi::Fd, path, buf.as_mut_ptr(), buf.len()) + .map_err(err2io) + } + } + + pub fn rename(&self, old_path: &str, new_fd: &WasiFd, new_path: &str) -> io::Result<()> { + unsafe { + wasi::path_rename( + self.as_raw_fd() as wasi::Fd, + old_path, + new_fd.as_raw_fd() as wasi::Fd, + new_path, + ) + .map_err(err2io) + } + } + + pub fn filestat_get(&self) -> io::Result<wasi::Filestat> { + unsafe { wasi::fd_filestat_get(self.as_raw_fd() as wasi::Fd).map_err(err2io) } + } + + pub fn filestat_set_times( + &self, + atim: wasi::Timestamp, + mtim: wasi::Timestamp, + fstflags: wasi::Fstflags, + ) -> io::Result<()> { + unsafe { + wasi::fd_filestat_set_times(self.as_raw_fd() as wasi::Fd, atim, mtim, fstflags) + .map_err(err2io) + } + } + + pub fn filestat_set_size(&self, size: u64) -> io::Result<()> { + unsafe { wasi::fd_filestat_set_size(self.as_raw_fd() as wasi::Fd, size).map_err(err2io) } + } + + pub fn path_filestat_get( + &self, + flags: wasi::Lookupflags, + path: &str, + ) -> io::Result<wasi::Filestat> { + unsafe { + wasi::path_filestat_get(self.as_raw_fd() as wasi::Fd, flags, path).map_err(err2io) + } + } + + pub fn path_filestat_set_times( + &self, + flags: wasi::Lookupflags, + path: &str, + atim: wasi::Timestamp, + mtim: wasi::Timestamp, + fstflags: wasi::Fstflags, + ) -> io::Result<()> { + unsafe { + wasi::path_filestat_set_times( + self.as_raw_fd() as wasi::Fd, + flags, + path, + atim, + mtim, + fstflags, + ) + .map_err(err2io) + } + } + + pub fn symlink(&self, old_path: &str, new_path: &str) -> io::Result<()> { + unsafe { + wasi::path_symlink(old_path, self.as_raw_fd() as wasi::Fd, new_path).map_err(err2io) + } + } + + pub fn unlink_file(&self, path: &str) -> io::Result<()> { + unsafe { wasi::path_unlink_file(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } + } + + pub fn remove_directory(&self, path: &str) -> io::Result<()> { + unsafe { wasi::path_remove_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } + } + + pub fn sock_accept(&self, flags: wasi::Fdflags) -> io::Result<wasi::Fd> { + unsafe { wasi::sock_accept(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) } + } + + pub fn sock_recv( + &self, + ri_data: &mut [IoSliceMut<'_>], + ri_flags: wasi::Riflags, + ) -> io::Result<(usize, wasi::Roflags)> { + unsafe { + wasi::sock_recv(self.as_raw_fd() as wasi::Fd, iovec(ri_data), ri_flags).map_err(err2io) + } + } + + pub fn sock_send(&self, si_data: &[IoSlice<'_>], si_flags: wasi::Siflags) -> io::Result<usize> { + unsafe { + wasi::sock_send(self.as_raw_fd() as wasi::Fd, ciovec(si_data), si_flags).map_err(err2io) + } + } + + pub fn sock_shutdown(&self, how: Shutdown) -> io::Result<()> { + let how = match how { + Shutdown::Read => wasi::SDFLAGS_RD, + Shutdown::Write => wasi::SDFLAGS_WR, + Shutdown::Both => wasi::SDFLAGS_WR | wasi::SDFLAGS_RD, + }; + unsafe { wasi::sock_shutdown(self.as_raw_fd() as wasi::Fd, how).map_err(err2io) } + } +} + +impl AsInner<OwnedFd> for WasiFd { + fn as_inner(&self) -> &OwnedFd { + &self.fd + } +} + +impl AsInnerMut<OwnedFd> for WasiFd { + fn as_inner_mut(&mut self) -> &mut OwnedFd { + &mut self.fd + } +} + +impl IntoInner<OwnedFd> for WasiFd { + fn into_inner(self) -> OwnedFd { + self.fd + } +} + +impl FromInner<OwnedFd> for WasiFd { + fn from_inner(owned_fd: OwnedFd) -> Self { + Self { fd: owned_fd } + } +} + +impl AsFd for WasiFd { + fn as_fd(&self) -> BorrowedFd<'_> { + self.fd.as_fd() + } +} + +impl AsRawFd for WasiFd { + fn as_raw_fd(&self) -> RawFd { + self.fd.as_raw_fd() + } +} + +impl IntoRawFd for WasiFd { + fn into_raw_fd(self) -> RawFd { + self.fd.into_raw_fd() + } +} + +impl FromRawFd for WasiFd { + unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { + unsafe { Self { fd: FromRawFd::from_raw_fd(raw_fd) } } + } +} diff --git a/library/std/src/sys/wasi/fs.rs b/library/std/src/sys/wasi/fs.rs new file mode 100644 index 000000000..6614ae397 --- /dev/null +++ b/library/std/src/sys/wasi/fs.rs @@ -0,0 +1,798 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use super::fd::WasiFd; +use crate::ffi::{CStr, CString, OsStr, OsString}; +use crate::fmt; +use crate::io::{self, IoSlice, IoSliceMut, ReadBuf, SeekFrom}; +use crate::iter; +use crate::mem::{self, ManuallyDrop}; +use crate::os::raw::c_int; +use crate::os::wasi::ffi::{OsStrExt, OsStringExt}; +use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; +use crate::path::{Path, PathBuf}; +use crate::ptr; +use crate::sync::Arc; +use crate::sys::time::SystemTime; +use crate::sys::unsupported; +use crate::sys_common::{AsInner, FromInner, IntoInner}; + +pub use crate::sys_common::fs::try_exists; + +pub struct File { + fd: WasiFd, +} + +#[derive(Clone)] +pub struct FileAttr { + meta: wasi::Filestat, +} + +pub struct ReadDir { + inner: Arc<ReadDirInner>, + cookie: Option<wasi::Dircookie>, + buf: Vec<u8>, + offset: usize, + cap: usize, +} + +struct ReadDirInner { + root: PathBuf, + dir: File, +} + +pub struct DirEntry { + meta: wasi::Dirent, + name: Vec<u8>, + inner: Arc<ReadDirInner>, +} + +#[derive(Clone, Debug, Default)] +pub struct OpenOptions { + read: bool, + write: bool, + append: bool, + dirflags: wasi::Lookupflags, + fdflags: wasi::Fdflags, + oflags: wasi::Oflags, + rights_base: Option<wasi::Rights>, + rights_inheriting: Option<wasi::Rights>, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct FilePermissions { + readonly: bool, +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct FileTimes { + accessed: Option<wasi::Timestamp>, + modified: Option<wasi::Timestamp>, +} + +#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] +pub struct FileType { + bits: wasi::Filetype, +} + +#[derive(Debug)] +pub struct DirBuilder {} + +impl FileAttr { + pub fn size(&self) -> u64 { + self.meta.size + } + + pub fn perm(&self) -> FilePermissions { + // not currently implemented in wasi yet + FilePermissions { readonly: false } + } + + pub fn file_type(&self) -> FileType { + FileType { bits: self.meta.filetype } + } + + pub fn modified(&self) -> io::Result<SystemTime> { + Ok(SystemTime::from_wasi_timestamp(self.meta.mtim)) + } + + pub fn accessed(&self) -> io::Result<SystemTime> { + Ok(SystemTime::from_wasi_timestamp(self.meta.atim)) + } + + pub fn created(&self) -> io::Result<SystemTime> { + Ok(SystemTime::from_wasi_timestamp(self.meta.ctim)) + } + + pub fn as_wasi(&self) -> &wasi::Filestat { + &self.meta + } +} + +impl FilePermissions { + pub fn readonly(&self) -> bool { + self.readonly + } + + pub fn set_readonly(&mut self, readonly: bool) { + self.readonly = readonly; + } +} + +impl FileTimes { + pub fn set_accessed(&mut self, t: SystemTime) { + self.accessed = Some(t.to_wasi_timestamp_or_panic()); + } + + pub fn set_modified(&mut self, t: SystemTime) { + self.modified = Some(t.to_wasi_timestamp_or_panic()); + } +} + +impl FileType { + pub fn is_dir(&self) -> bool { + self.bits == wasi::FILETYPE_DIRECTORY + } + + pub fn is_file(&self) -> bool { + self.bits == wasi::FILETYPE_REGULAR_FILE + } + + pub fn is_symlink(&self) -> bool { + self.bits == wasi::FILETYPE_SYMBOLIC_LINK + } + + pub fn bits(&self) -> wasi::Filetype { + self.bits + } +} + +impl ReadDir { + fn new(dir: File, root: PathBuf) -> ReadDir { + ReadDir { + cookie: Some(0), + buf: vec![0; 128], + offset: 0, + cap: 0, + inner: Arc::new(ReadDirInner { dir, root }), + } + } +} + +impl fmt::Debug for ReadDir { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ReadDir").finish_non_exhaustive() + } +} + +impl Iterator for ReadDir { + type Item = io::Result<DirEntry>; + + fn next(&mut self) -> Option<io::Result<DirEntry>> { + loop { + // If we've reached the capacity of our buffer then we need to read + // some more from the OS, otherwise we pick up at our old offset. + let offset = if self.offset == self.cap { + let cookie = self.cookie.take()?; + match self.inner.dir.fd.readdir(&mut self.buf, cookie) { + Ok(bytes) => self.cap = bytes, + Err(e) => return Some(Err(e)), + } + self.offset = 0; + self.cookie = Some(cookie); + + // If we didn't actually read anything, this is in theory the + // end of the directory. + if self.cap == 0 { + self.cookie = None; + return None; + } + + 0 + } else { + self.offset + }; + let data = &self.buf[offset..self.cap]; + + // If we're not able to read a directory entry then that means it + // must have been truncated at the end of the buffer, so reset our + // offset so we can go back and reread into the buffer, picking up + // where we last left off. + let dirent_size = mem::size_of::<wasi::Dirent>(); + if data.len() < dirent_size { + assert!(self.cookie.is_some()); + assert!(self.buf.len() >= dirent_size); + self.offset = self.cap; + continue; + } + let (dirent, data) = data.split_at(dirent_size); + let dirent = unsafe { ptr::read_unaligned(dirent.as_ptr() as *const wasi::Dirent) }; + + // If the file name was truncated, then we need to reinvoke + // `readdir` so we truncate our buffer to start over and reread this + // descriptor. Note that if our offset is 0 that means the file name + // is massive and we need a bigger buffer. + if data.len() < dirent.d_namlen as usize { + if offset == 0 { + let amt_to_add = self.buf.capacity(); + self.buf.extend(iter::repeat(0).take(amt_to_add)); + } + assert!(self.cookie.is_some()); + self.offset = self.cap; + continue; + } + self.cookie = Some(dirent.d_next); + self.offset = offset + dirent_size + dirent.d_namlen as usize; + + let name = &data[..(dirent.d_namlen as usize)]; + + // These names are skipped on all other platforms, so let's skip + // them here too + if name == b"." || name == b".." { + continue; + } + + return Some(Ok(DirEntry { + meta: dirent, + name: name.to_vec(), + inner: self.inner.clone(), + })); + } + } +} + +impl DirEntry { + pub fn path(&self) -> PathBuf { + let name = OsStr::from_bytes(&self.name); + self.inner.root.join(name) + } + + pub fn file_name(&self) -> OsString { + OsString::from_vec(self.name.clone()) + } + + pub fn metadata(&self) -> io::Result<FileAttr> { + metadata_at(&self.inner.dir.fd, 0, OsStr::from_bytes(&self.name).as_ref()) + } + + pub fn file_type(&self) -> io::Result<FileType> { + Ok(FileType { bits: self.meta.d_type }) + } + + pub fn ino(&self) -> wasi::Inode { + self.meta.d_ino + } +} + +impl OpenOptions { + pub fn new() -> OpenOptions { + let mut base = OpenOptions::default(); + base.dirflags = wasi::LOOKUPFLAGS_SYMLINK_FOLLOW; + return base; + } + + pub fn read(&mut self, read: bool) { + self.read = read; + } + + pub fn write(&mut self, write: bool) { + self.write = write; + } + + pub fn truncate(&mut self, truncate: bool) { + self.oflag(wasi::OFLAGS_TRUNC, truncate); + } + + pub fn create(&mut self, create: bool) { + self.oflag(wasi::OFLAGS_CREAT, create); + } + + pub fn create_new(&mut self, create_new: bool) { + self.oflag(wasi::OFLAGS_EXCL, create_new); + self.oflag(wasi::OFLAGS_CREAT, create_new); + } + + pub fn directory(&mut self, directory: bool) { + self.oflag(wasi::OFLAGS_DIRECTORY, directory); + } + + fn oflag(&mut self, bit: wasi::Oflags, set: bool) { + if set { + self.oflags |= bit; + } else { + self.oflags &= !bit; + } + } + + pub fn append(&mut self, append: bool) { + self.append = append; + self.fdflag(wasi::FDFLAGS_APPEND, append); + } + + pub fn dsync(&mut self, set: bool) { + self.fdflag(wasi::FDFLAGS_DSYNC, set); + } + + pub fn nonblock(&mut self, set: bool) { + self.fdflag(wasi::FDFLAGS_NONBLOCK, set); + } + + pub fn rsync(&mut self, set: bool) { + self.fdflag(wasi::FDFLAGS_RSYNC, set); + } + + pub fn sync(&mut self, set: bool) { + self.fdflag(wasi::FDFLAGS_SYNC, set); + } + + fn fdflag(&mut self, bit: wasi::Fdflags, set: bool) { + if set { + self.fdflags |= bit; + } else { + self.fdflags &= !bit; + } + } + + pub fn fs_rights_base(&mut self, rights: wasi::Rights) { + self.rights_base = Some(rights); + } + + pub fn fs_rights_inheriting(&mut self, rights: wasi::Rights) { + self.rights_inheriting = Some(rights); + } + + fn rights_base(&self) -> wasi::Rights { + if let Some(rights) = self.rights_base { + return rights; + } + + // If rights haven't otherwise been specified try to pick a reasonable + // set. This can always be overridden by users via extension traits, and + // implementations may give us fewer rights silently than we ask for. So + // given that, just look at `read` and `write` and bucket permissions + // based on that. + let mut base = 0; + if self.read { + base |= wasi::RIGHTS_FD_READ; + base |= wasi::RIGHTS_FD_READDIR; + } + if self.write || self.append { + base |= wasi::RIGHTS_FD_WRITE; + base |= wasi::RIGHTS_FD_DATASYNC; + base |= wasi::RIGHTS_FD_ALLOCATE; + base |= wasi::RIGHTS_FD_FILESTAT_SET_SIZE; + } + + // FIXME: some of these should probably be read-only or write-only... + base |= wasi::RIGHTS_FD_ADVISE; + base |= wasi::RIGHTS_FD_FDSTAT_SET_FLAGS; + base |= wasi::RIGHTS_FD_FILESTAT_GET; + base |= wasi::RIGHTS_FD_FILESTAT_SET_TIMES; + base |= wasi::RIGHTS_FD_SEEK; + base |= wasi::RIGHTS_FD_SYNC; + base |= wasi::RIGHTS_FD_TELL; + base |= wasi::RIGHTS_PATH_CREATE_DIRECTORY; + base |= wasi::RIGHTS_PATH_CREATE_FILE; + base |= wasi::RIGHTS_PATH_FILESTAT_GET; + base |= wasi::RIGHTS_PATH_LINK_SOURCE; + base |= wasi::RIGHTS_PATH_LINK_TARGET; + base |= wasi::RIGHTS_PATH_OPEN; + base |= wasi::RIGHTS_PATH_READLINK; + base |= wasi::RIGHTS_PATH_REMOVE_DIRECTORY; + base |= wasi::RIGHTS_PATH_RENAME_SOURCE; + base |= wasi::RIGHTS_PATH_RENAME_TARGET; + base |= wasi::RIGHTS_PATH_SYMLINK; + base |= wasi::RIGHTS_PATH_UNLINK_FILE; + base |= wasi::RIGHTS_POLL_FD_READWRITE; + + return base; + } + + fn rights_inheriting(&self) -> wasi::Rights { + self.rights_inheriting.unwrap_or_else(|| self.rights_base()) + } + + pub fn lookup_flags(&mut self, flags: wasi::Lookupflags) { + self.dirflags = flags; + } +} + +impl File { + pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> { + let (dir, file) = open_parent(path)?; + open_at(&dir, &file, opts) + } + + pub fn open_at(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> { + open_at(&self.fd, path, opts) + } + + pub fn file_attr(&self) -> io::Result<FileAttr> { + self.fd.filestat_get().map(|meta| FileAttr { meta }) + } + + pub fn metadata_at(&self, flags: wasi::Lookupflags, path: &Path) -> io::Result<FileAttr> { + metadata_at(&self.fd, flags, path) + } + + pub fn fsync(&self) -> io::Result<()> { + self.fd.sync() + } + + pub fn datasync(&self) -> io::Result<()> { + self.fd.datasync() + } + + pub fn truncate(&self, size: u64) -> io::Result<()> { + self.fd.filestat_set_size(size) + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> { + self.read_vectored(&mut [IoSliceMut::new(buf)]) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> { + self.fd.read(bufs) + } + + #[inline] + pub fn is_read_vectored(&self) -> bool { + true + } + + pub fn read_buf(&self, buf: &mut ReadBuf<'_>) -> io::Result<()> { + crate::io::default_read_buf(|buf| self.read(buf), buf) + } + + pub fn write(&self, buf: &[u8]) -> io::Result<usize> { + self.write_vectored(&[IoSlice::new(buf)]) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> { + self.fd.write(bufs) + } + + #[inline] + pub fn is_write_vectored(&self) -> bool { + true + } + + pub fn flush(&self) -> io::Result<()> { + Ok(()) + } + + pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> { + self.fd.seek(pos) + } + + pub fn duplicate(&self) -> io::Result<File> { + // https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-rationale.md#why-no-dup + unsupported() + } + + pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> { + // Permissions haven't been fully figured out in wasi yet, so this is + // likely temporary + unsupported() + } + + pub fn set_times(&self, times: FileTimes) -> io::Result<()> { + self.fd.filestat_set_times( + times.accessed.unwrap_or(0), + times.modified.unwrap_or(0), + times.accessed.map_or(0, |_| wasi::FSTFLAGS_ATIM) + | times.modified.map_or(0, |_| wasi::FSTFLAGS_MTIM), + ) + } + + pub fn read_link(&self, file: &Path) -> io::Result<PathBuf> { + read_link(&self.fd, file) + } +} + +impl AsInner<WasiFd> for File { + fn as_inner(&self) -> &WasiFd { + &self.fd + } +} + +impl IntoInner<WasiFd> for File { + fn into_inner(self) -> WasiFd { + self.fd + } +} + +impl FromInner<WasiFd> for File { + fn from_inner(fd: WasiFd) -> File { + File { fd } + } +} + +impl AsFd for File { + fn as_fd(&self) -> BorrowedFd<'_> { + self.fd.as_fd() + } +} + +impl AsRawFd for File { + fn as_raw_fd(&self) -> RawFd { + self.fd.as_raw_fd() + } +} + +impl IntoRawFd for File { + fn into_raw_fd(self) -> RawFd { + self.fd.into_raw_fd() + } +} + +impl FromRawFd for File { + unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { + unsafe { Self { fd: FromRawFd::from_raw_fd(raw_fd) } } + } +} + +impl DirBuilder { + pub fn new() -> DirBuilder { + DirBuilder {} + } + + pub fn mkdir(&self, p: &Path) -> io::Result<()> { + let (dir, file) = open_parent(p)?; + dir.create_directory(osstr2str(file.as_ref())?) + } +} + +impl fmt::Debug for File { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("File").field("fd", &self.as_raw_fd()).finish() + } +} + +pub fn readdir(p: &Path) -> io::Result<ReadDir> { + let mut opts = OpenOptions::new(); + opts.directory(true); + opts.read(true); + let dir = File::open(p, &opts)?; + Ok(ReadDir::new(dir, p.to_path_buf())) +} + +pub fn unlink(p: &Path) -> io::Result<()> { + let (dir, file) = open_parent(p)?; + dir.unlink_file(osstr2str(file.as_ref())?) +} + +pub fn rename(old: &Path, new: &Path) -> io::Result<()> { + let (old, old_file) = open_parent(old)?; + let (new, new_file) = open_parent(new)?; + old.rename(osstr2str(old_file.as_ref())?, &new, osstr2str(new_file.as_ref())?) +} + +pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { + // Permissions haven't been fully figured out in wasi yet, so this is + // likely temporary + unsupported() +} + +pub fn rmdir(p: &Path) -> io::Result<()> { + let (dir, file) = open_parent(p)?; + dir.remove_directory(osstr2str(file.as_ref())?) +} + +pub fn readlink(p: &Path) -> io::Result<PathBuf> { + let (dir, file) = open_parent(p)?; + read_link(&dir, &file) +} + +fn read_link(fd: &WasiFd, file: &Path) -> io::Result<PathBuf> { + // Try to get a best effort initial capacity for the vector we're going to + // fill. Note that if it's not a symlink we don't use a file to avoid + // allocating gigabytes if you read_link a huge movie file by accident. + // Additionally we add 1 to the initial size so if it doesn't change until + // when we call `readlink` the returned length will be less than the + // capacity, guaranteeing that we got all the data. + let meta = metadata_at(fd, 0, file)?; + let initial_size = if meta.file_type().is_symlink() { + (meta.size() as usize).saturating_add(1) + } else { + 1 // this'll fail in just a moment + }; + + // Now that we have an initial guess of how big to make our buffer, call + // `readlink` in a loop until it fails or reports it filled fewer bytes than + // we asked for, indicating we got everything. + let file = osstr2str(file.as_ref())?; + let mut destination = vec![0u8; initial_size]; + loop { + let len = fd.readlink(file, &mut destination)?; + if len < destination.len() { + destination.truncate(len); + destination.shrink_to_fit(); + return Ok(PathBuf::from(OsString::from_vec(destination))); + } + let amt_to_add = destination.len(); + destination.extend(iter::repeat(0).take(amt_to_add)); + } +} + +pub fn symlink(original: &Path, link: &Path) -> io::Result<()> { + let (link, link_file) = open_parent(link)?; + link.symlink(osstr2str(original.as_ref())?, osstr2str(link_file.as_ref())?) +} + +pub fn link(original: &Path, link: &Path) -> io::Result<()> { + let (original, original_file) = open_parent(original)?; + let (link, link_file) = open_parent(link)?; + // Pass 0 as the flags argument, meaning don't follow symlinks. + original.link(0, osstr2str(original_file.as_ref())?, &link, osstr2str(link_file.as_ref())?) +} + +pub fn stat(p: &Path) -> io::Result<FileAttr> { + let (dir, file) = open_parent(p)?; + metadata_at(&dir, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, &file) +} + +pub fn lstat(p: &Path) -> io::Result<FileAttr> { + let (dir, file) = open_parent(p)?; + metadata_at(&dir, 0, &file) +} + +fn metadata_at(fd: &WasiFd, flags: wasi::Lookupflags, path: &Path) -> io::Result<FileAttr> { + let meta = fd.path_filestat_get(flags, osstr2str(path.as_ref())?)?; + Ok(FileAttr { meta }) +} + +pub fn canonicalize(_p: &Path) -> io::Result<PathBuf> { + // This seems to not be in wasi's API yet, and we may need to end up + // emulating it ourselves. For now just return an error. + unsupported() +} + +fn open_at(fd: &WasiFd, path: &Path, opts: &OpenOptions) -> io::Result<File> { + let fd = fd.open( + opts.dirflags, + osstr2str(path.as_ref())?, + opts.oflags, + opts.rights_base(), + opts.rights_inheriting(), + opts.fdflags, + )?; + Ok(File { fd }) +} + +/// Attempts to open a bare path `p`. +/// +/// WASI has no fundamental capability to do this. All syscalls and operations +/// are relative to already-open file descriptors. The C library, however, +/// manages a map of pre-opened file descriptors to their path, and then the C +/// library provides an API to look at this. In other words, when you want to +/// open a path `p`, you have to find a previously opened file descriptor in a +/// global table and then see if `p` is relative to that file descriptor. +/// +/// This function, if successful, will return two items: +/// +/// * The first is a `ManuallyDrop<WasiFd>`. This represents a pre-opened file +/// descriptor which we don't have ownership of, but we can use. You shouldn't +/// actually drop the `fd`. +/// +/// * The second is a path that should be a part of `p` and represents a +/// relative traversal from the file descriptor specified to the desired +/// location `p`. +/// +/// If successful you can use the returned file descriptor to perform +/// file-descriptor-relative operations on the path returned as well. The +/// `rights` argument indicates what operations are desired on the returned file +/// descriptor, and if successful the returned file descriptor should have the +/// appropriate rights for performing `rights` actions. +/// +/// Note that this can fail if `p` doesn't look like it can be opened relative +/// to any pre-opened file descriptor. +fn open_parent(p: &Path) -> io::Result<(ManuallyDrop<WasiFd>, PathBuf)> { + let p = CString::new(p.as_os_str().as_bytes())?; + let mut buf = Vec::<u8>::with_capacity(512); + loop { + unsafe { + let mut relative_path = buf.as_ptr().cast(); + let mut abs_prefix = ptr::null(); + let fd = __wasilibc_find_relpath( + p.as_ptr(), + &mut abs_prefix, + &mut relative_path, + buf.capacity(), + ); + if fd == -1 { + if io::Error::last_os_error().raw_os_error() == Some(libc::ENOMEM) { + // Trigger the internal buffer resizing logic of `Vec` by requiring + // more space than the current capacity. + let cap = buf.capacity(); + buf.set_len(cap); + buf.reserve(1); + continue; + } + let msg = format!( + "failed to find a pre-opened file descriptor \ + through which {:?} could be opened", + p + ); + return Err(io::Error::new(io::ErrorKind::Uncategorized, msg)); + } + let relative = CStr::from_ptr(relative_path).to_bytes().to_vec(); + + return Ok(( + ManuallyDrop::new(WasiFd::from_raw_fd(fd as c_int)), + PathBuf::from(OsString::from_vec(relative)), + )); + } + } + + extern "C" { + pub fn __wasilibc_find_relpath( + path: *const libc::c_char, + abs_prefix: *mut *const libc::c_char, + relative_path: *mut *const libc::c_char, + relative_path_len: libc::size_t, + ) -> libc::c_int; + } +} + +pub fn osstr2str(f: &OsStr) -> io::Result<&str> { + f.to_str() + .ok_or_else(|| io::const_io_error!(io::ErrorKind::Uncategorized, "input must be utf-8")) +} + +pub fn copy(from: &Path, to: &Path) -> io::Result<u64> { + use crate::fs::File; + + let mut reader = File::open(from)?; + let mut writer = File::create(to)?; + + io::copy(&mut reader, &mut writer) +} + +pub fn remove_dir_all(path: &Path) -> io::Result<()> { + let (parent, path) = open_parent(path)?; + remove_dir_all_recursive(&parent, &path) +} + +fn remove_dir_all_recursive(parent: &WasiFd, path: &Path) -> io::Result<()> { + // Open up a file descriptor for the directory itself. Note that we don't + // follow symlinks here and we specifically open directories. + // + // At the root invocation of this function this will correctly handle + // symlinks passed to the top-level `remove_dir_all`. At the recursive + // level this will double-check that after the `readdir` call deduced this + // was a directory it's still a directory by the time we open it up. + // + // If the opened file was actually a symlink then the symlink is deleted, + // not the directory recursively. + let mut opts = OpenOptions::new(); + opts.lookup_flags(0); + opts.directory(true); + opts.read(true); + let fd = open_at(parent, path, &opts)?; + if fd.file_attr()?.file_type().is_symlink() { + return parent.unlink_file(osstr2str(path.as_ref())?); + } + + // this "root" is only used by `DirEntry::path` which we don't use below so + // it's ok for this to be a bogus value + let dummy_root = PathBuf::new(); + + // Iterate over all the entries in this directory, and travel recursively if + // necessary + for entry in ReadDir::new(fd, dummy_root) { + let entry = entry?; + let path = crate::str::from_utf8(&entry.name).map_err(|_| { + io::const_io_error!(io::ErrorKind::Uncategorized, "invalid utf-8 file name found") + })?; + + if entry.file_type()?.is_dir() { + remove_dir_all_recursive(&entry.inner.dir.fd, path.as_ref())?; + } else { + entry.inner.dir.fd.unlink_file(path)?; + } + } + + // Once all this directory's contents are deleted it should be safe to + // delete the directory tiself. + parent.remove_directory(osstr2str(path.as_ref())?) +} diff --git a/library/std/src/sys/wasi/io.rs b/library/std/src/sys/wasi/io.rs new file mode 100644 index 000000000..ee017d13a --- /dev/null +++ b/library/std/src/sys/wasi/io.rs @@ -0,0 +1,73 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use crate::marker::PhantomData; +use crate::slice; + +#[derive(Copy, Clone)] +#[repr(transparent)] +pub struct IoSlice<'a> { + vec: wasi::Ciovec, + _p: PhantomData<&'a [u8]>, +} + +impl<'a> IoSlice<'a> { + #[inline] + pub fn new(buf: &'a [u8]) -> IoSlice<'a> { + IoSlice { vec: wasi::Ciovec { buf: buf.as_ptr(), buf_len: buf.len() }, _p: PhantomData } + } + + #[inline] + pub fn advance(&mut self, n: usize) { + if self.vec.buf_len < n { + panic!("advancing IoSlice beyond its length"); + } + + unsafe { + self.vec.buf_len -= n; + self.vec.buf = self.vec.buf.add(n); + } + } + + #[inline] + pub fn as_slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.vec.buf as *const u8, self.vec.buf_len) } + } +} + +#[repr(transparent)] +pub struct IoSliceMut<'a> { + vec: wasi::Iovec, + _p: PhantomData<&'a mut [u8]>, +} + +impl<'a> IoSliceMut<'a> { + #[inline] + pub fn new(buf: &'a mut [u8]) -> IoSliceMut<'a> { + IoSliceMut { + vec: wasi::Iovec { buf: buf.as_mut_ptr(), buf_len: buf.len() }, + _p: PhantomData, + } + } + + #[inline] + pub fn advance(&mut self, n: usize) { + if self.vec.buf_len < n { + panic!("advancing IoSlice beyond its length"); + } + + unsafe { + self.vec.buf_len -= n; + self.vec.buf = self.vec.buf.add(n); + } + } + + #[inline] + pub fn as_slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.vec.buf as *const u8, self.vec.buf_len) } + } + + #[inline] + pub fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { slice::from_raw_parts_mut(self.vec.buf as *mut u8, self.vec.buf_len) } + } +} diff --git a/library/std/src/sys/wasi/mod.rs b/library/std/src/sys/wasi/mod.rs new file mode 100644 index 000000000..683a07a34 --- /dev/null +++ b/library/std/src/sys/wasi/mod.rs @@ -0,0 +1,100 @@ +//! System bindings for the wasm/web platform +//! +//! This module contains the facade (aka platform-specific) implementations of +//! OS level functionality for wasm. Note that this wasm is *not* the emscripten +//! wasm, so we have no runtime here. +//! +//! This is all super highly experimental and not actually intended for +//! wide/production use yet, it's still all in the experimental category. This +//! will likely change over time. +//! +//! Currently all functions here are basically stubs that immediately return +//! errors. The hope is that with a portability lint we can turn actually just +//! remove all this and just omit parts of the standard library if we're +//! compiling for wasm. That way it's a compile time error for something that's +//! guaranteed to be a runtime error! + +use crate::io as std_io; +use crate::mem; + +#[path = "../unix/alloc.rs"] +pub mod alloc; +pub mod args; +#[path = "../unix/cmath.rs"] +pub mod cmath; +pub mod env; +pub mod fd; +pub mod fs; +pub mod io; +#[path = "../unsupported/locks/mod.rs"] +pub mod locks; +pub mod net; +pub mod os; +#[path = "../unix/os_str.rs"] +pub mod os_str; +#[path = "../unix/path.rs"] +pub mod path; +#[path = "../unsupported/pipe.rs"] +pub mod pipe; +#[path = "../unsupported/process.rs"] +pub mod process; +pub mod stdio; +pub mod thread; +#[path = "../unsupported/thread_local_dtor.rs"] +pub mod thread_local_dtor; +#[path = "../unsupported/thread_local_key.rs"] +pub mod thread_local_key; +pub mod time; + +#[path = "../unsupported/common.rs"] +#[deny(unsafe_op_in_unsafe_fn)] +#[allow(unused)] +mod common; +pub use common::*; + +pub fn decode_error_kind(errno: i32) -> std_io::ErrorKind { + use std_io::ErrorKind::*; + if errno > u16::MAX as i32 || errno < 0 { + return Uncategorized; + } + + match errno { + e if e == wasi::ERRNO_CONNREFUSED.raw().into() => ConnectionRefused, + e if e == wasi::ERRNO_CONNRESET.raw().into() => ConnectionReset, + e if e == wasi::ERRNO_PERM.raw().into() || e == wasi::ERRNO_ACCES.raw().into() => { + PermissionDenied + } + e if e == wasi::ERRNO_PIPE.raw().into() => BrokenPipe, + e if e == wasi::ERRNO_NOTCONN.raw().into() => NotConnected, + e if e == wasi::ERRNO_CONNABORTED.raw().into() => ConnectionAborted, + e if e == wasi::ERRNO_ADDRNOTAVAIL.raw().into() => AddrNotAvailable, + e if e == wasi::ERRNO_ADDRINUSE.raw().into() => AddrInUse, + e if e == wasi::ERRNO_NOENT.raw().into() => NotFound, + e if e == wasi::ERRNO_INTR.raw().into() => Interrupted, + e if e == wasi::ERRNO_INVAL.raw().into() => InvalidInput, + e if e == wasi::ERRNO_TIMEDOUT.raw().into() => TimedOut, + e if e == wasi::ERRNO_EXIST.raw().into() => AlreadyExists, + e if e == wasi::ERRNO_AGAIN.raw().into() => WouldBlock, + e if e == wasi::ERRNO_NOSYS.raw().into() => Unsupported, + e if e == wasi::ERRNO_NOMEM.raw().into() => OutOfMemory, + _ => Uncategorized, + } +} + +pub fn abort_internal() -> ! { + unsafe { libc::abort() } +} + +pub fn hashmap_random_keys() -> (u64, u64) { + let mut ret = (0u64, 0u64); + unsafe { + let base = &mut ret as *mut (u64, u64) as *mut u8; + let len = mem::size_of_val(&ret); + wasi::random_get(base, len).expect("random_get failure"); + } + return ret; +} + +fn err2io(err: wasi::Errno) -> std_io::Error { + std_io::Error::from_raw_os_error(err.raw().into()) +} diff --git a/library/std/src/sys/wasi/net.rs b/library/std/src/sys/wasi/net.rs new file mode 100644 index 000000000..590d268c3 --- /dev/null +++ b/library/std/src/sys/wasi/net.rs @@ -0,0 +1,527 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use super::err2io; +use super::fd::WasiFd; +use crate::fmt; +use crate::io::{self, IoSlice, IoSliceMut}; +use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr}; +use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; +use crate::sys::unsupported; +use crate::sys_common::{AsInner, FromInner, IntoInner}; +use crate::time::Duration; + +pub struct Socket(WasiFd); + +pub struct TcpStream { + inner: Socket, +} + +impl AsInner<WasiFd> for Socket { + fn as_inner(&self) -> &WasiFd { + &self.0 + } +} + +impl IntoInner<WasiFd> for Socket { + fn into_inner(self) -> WasiFd { + self.0 + } +} + +impl FromInner<WasiFd> for Socket { + fn from_inner(inner: WasiFd) -> Socket { + Socket(inner) + } +} + +impl AsFd for Socket { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} + +impl AsRawFd for Socket { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} + +impl IntoRawFd for Socket { + fn into_raw_fd(self) -> RawFd { + self.0.into_raw_fd() + } +} + +impl FromRawFd for Socket { + unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { + unsafe { Self(FromRawFd::from_raw_fd(raw_fd)) } + } +} + +impl TcpStream { + pub fn connect(_: io::Result<&SocketAddr>) -> io::Result<TcpStream> { + unsupported() + } + + pub fn connect_timeout(_: &SocketAddr, _: Duration) -> io::Result<TcpStream> { + unsupported() + } + + pub fn set_read_timeout(&self, _: Option<Duration>) -> io::Result<()> { + unsupported() + } + + pub fn set_write_timeout(&self, _: Option<Duration>) -> io::Result<()> { + unsupported() + } + + pub fn read_timeout(&self) -> io::Result<Option<Duration>> { + unsupported() + } + + pub fn write_timeout(&self) -> io::Result<Option<Duration>> { + unsupported() + } + + pub fn peek(&self, _: &mut [u8]) -> io::Result<usize> { + unsupported() + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> { + self.read_vectored(&mut [IoSliceMut::new(buf)]) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> { + self.socket().as_inner().read(bufs) + } + + pub fn is_read_vectored(&self) -> bool { + true + } + + pub fn write(&self, buf: &[u8]) -> io::Result<usize> { + self.write_vectored(&[IoSlice::new(buf)]) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> { + self.socket().as_inner().write(bufs) + } + + pub fn is_write_vectored(&self) -> bool { + true + } + + pub fn peer_addr(&self) -> io::Result<SocketAddr> { + unsupported() + } + + pub fn socket_addr(&self) -> io::Result<SocketAddr> { + unsupported() + } + + pub fn shutdown(&self, _: Shutdown) -> io::Result<()> { + unsupported() + } + + pub fn duplicate(&self) -> io::Result<TcpStream> { + unsupported() + } + + pub fn set_linger(&self, _: Option<Duration>) -> io::Result<()> { + unsupported() + } + + pub fn linger(&self) -> io::Result<Option<Duration>> { + unsupported() + } + + pub fn set_nodelay(&self, _: bool) -> io::Result<()> { + unsupported() + } + + pub fn nodelay(&self) -> io::Result<bool> { + unsupported() + } + + pub fn set_ttl(&self, _: u32) -> io::Result<()> { + unsupported() + } + + pub fn ttl(&self) -> io::Result<u32> { + unsupported() + } + + pub fn take_error(&self) -> io::Result<Option<io::Error>> { + unsupported() + } + + pub fn set_nonblocking(&self, state: bool) -> io::Result<()> { + let fdstat = unsafe { + wasi::fd_fdstat_get(self.socket().as_inner().as_raw_fd() as wasi::Fd).map_err(err2io)? + }; + + let mut flags = fdstat.fs_flags; + + if state { + flags |= wasi::FDFLAGS_NONBLOCK; + } else { + flags &= !wasi::FDFLAGS_NONBLOCK; + } + + unsafe { + wasi::fd_fdstat_set_flags(self.socket().as_inner().as_raw_fd() as wasi::Fd, flags) + .map_err(err2io) + } + } + + pub fn socket(&self) -> &Socket { + &self.inner + } + + pub fn into_socket(self) -> Socket { + self.inner + } +} + +impl FromInner<Socket> for TcpStream { + fn from_inner(socket: Socket) -> TcpStream { + TcpStream { inner: socket } + } +} + +impl fmt::Debug for TcpStream { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TcpStream").field("fd", &self.inner.as_raw_fd()).finish() + } +} + +pub struct TcpListener { + inner: Socket, +} + +impl TcpListener { + pub fn bind(_: io::Result<&SocketAddr>) -> io::Result<TcpListener> { + unsupported() + } + + pub fn socket_addr(&self) -> io::Result<SocketAddr> { + unsupported() + } + + pub fn accept(&self) -> io::Result<(TcpStream, SocketAddr)> { + let fd = unsafe { + wasi::sock_accept(self.as_inner().as_inner().as_raw_fd() as _, 0).map_err(err2io)? + }; + + Ok(( + TcpStream::from_inner(unsafe { Socket::from_raw_fd(fd as _) }), + // WASI has no concept of SocketAddr yet + // return an unspecified IPv4Addr + SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0), + )) + } + + pub fn duplicate(&self) -> io::Result<TcpListener> { + unsupported() + } + + pub fn set_ttl(&self, _: u32) -> io::Result<()> { + unsupported() + } + + pub fn ttl(&self) -> io::Result<u32> { + unsupported() + } + + pub fn set_only_v6(&self, _: bool) -> io::Result<()> { + unsupported() + } + + pub fn only_v6(&self) -> io::Result<bool> { + unsupported() + } + + pub fn take_error(&self) -> io::Result<Option<io::Error>> { + unsupported() + } + + pub fn set_nonblocking(&self, state: bool) -> io::Result<()> { + let fdstat = unsafe { + wasi::fd_fdstat_get(self.socket().as_inner().as_raw_fd() as wasi::Fd).map_err(err2io)? + }; + + let mut flags = fdstat.fs_flags; + + if state { + flags |= wasi::FDFLAGS_NONBLOCK; + } else { + flags &= !wasi::FDFLAGS_NONBLOCK; + } + + unsafe { + wasi::fd_fdstat_set_flags(self.socket().as_inner().as_raw_fd() as wasi::Fd, flags) + .map_err(err2io) + } + } + + pub fn socket(&self) -> &Socket { + &self.inner + } + + pub fn into_socket(self) -> Socket { + self.inner + } +} + +impl AsInner<Socket> for TcpListener { + fn as_inner(&self) -> &Socket { + &self.inner + } +} + +impl IntoInner<Socket> for TcpListener { + fn into_inner(self) -> Socket { + self.inner + } +} + +impl FromInner<Socket> for TcpListener { + fn from_inner(inner: Socket) -> TcpListener { + TcpListener { inner } + } +} + +impl fmt::Debug for TcpListener { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TcpListener").field("fd", &self.inner.as_raw_fd()).finish() + } +} + +pub struct UdpSocket { + inner: Socket, +} + +impl UdpSocket { + pub fn bind(_: io::Result<&SocketAddr>) -> io::Result<UdpSocket> { + unsupported() + } + + pub fn peer_addr(&self) -> io::Result<SocketAddr> { + unsupported() + } + + pub fn socket_addr(&self) -> io::Result<SocketAddr> { + unsupported() + } + + pub fn recv_from(&self, _: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + unsupported() + } + + pub fn peek_from(&self, _: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + unsupported() + } + + pub fn send_to(&self, _: &[u8], _: &SocketAddr) -> io::Result<usize> { + unsupported() + } + + pub fn duplicate(&self) -> io::Result<UdpSocket> { + unsupported() + } + + pub fn set_read_timeout(&self, _: Option<Duration>) -> io::Result<()> { + unsupported() + } + + pub fn set_write_timeout(&self, _: Option<Duration>) -> io::Result<()> { + unsupported() + } + + pub fn read_timeout(&self) -> io::Result<Option<Duration>> { + unsupported() + } + + pub fn write_timeout(&self) -> io::Result<Option<Duration>> { + unsupported() + } + + pub fn set_broadcast(&self, _: bool) -> io::Result<()> { + unsupported() + } + + pub fn broadcast(&self) -> io::Result<bool> { + unsupported() + } + + pub fn set_multicast_loop_v4(&self, _: bool) -> io::Result<()> { + unsupported() + } + + pub fn multicast_loop_v4(&self) -> io::Result<bool> { + unsupported() + } + + pub fn set_multicast_ttl_v4(&self, _: u32) -> io::Result<()> { + unsupported() + } + + pub fn multicast_ttl_v4(&self) -> io::Result<u32> { + unsupported() + } + + pub fn set_multicast_loop_v6(&self, _: bool) -> io::Result<()> { + unsupported() + } + + pub fn multicast_loop_v6(&self) -> io::Result<bool> { + unsupported() + } + + pub fn join_multicast_v4(&self, _: &Ipv4Addr, _: &Ipv4Addr) -> io::Result<()> { + unsupported() + } + + pub fn join_multicast_v6(&self, _: &Ipv6Addr, _: u32) -> io::Result<()> { + unsupported() + } + + pub fn leave_multicast_v4(&self, _: &Ipv4Addr, _: &Ipv4Addr) -> io::Result<()> { + unsupported() + } + + pub fn leave_multicast_v6(&self, _: &Ipv6Addr, _: u32) -> io::Result<()> { + unsupported() + } + + pub fn set_ttl(&self, _: u32) -> io::Result<()> { + unsupported() + } + + pub fn ttl(&self) -> io::Result<u32> { + unsupported() + } + + pub fn take_error(&self) -> io::Result<Option<io::Error>> { + unsupported() + } + + pub fn set_nonblocking(&self, _: bool) -> io::Result<()> { + unsupported() + } + + pub fn recv(&self, _: &mut [u8]) -> io::Result<usize> { + unsupported() + } + + pub fn peek(&self, _: &mut [u8]) -> io::Result<usize> { + unsupported() + } + + pub fn send(&self, _: &[u8]) -> io::Result<usize> { + unsupported() + } + + pub fn connect(&self, _: io::Result<&SocketAddr>) -> io::Result<()> { + unsupported() + } + + pub fn socket(&self) -> &Socket { + &self.inner + } + + pub fn into_socket(self) -> Socket { + self.inner + } +} + +impl AsInner<Socket> for UdpSocket { + fn as_inner(&self) -> &Socket { + &self.inner + } +} + +impl IntoInner<Socket> for UdpSocket { + fn into_inner(self) -> Socket { + self.inner + } +} + +impl FromInner<Socket> for UdpSocket { + fn from_inner(inner: Socket) -> UdpSocket { + UdpSocket { inner } + } +} + +impl fmt::Debug for UdpSocket { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("UdpSocket").field("fd", &self.inner.as_raw_fd()).finish() + } +} + +pub struct LookupHost(!); + +impl LookupHost { + pub fn port(&self) -> u16 { + self.0 + } +} + +impl Iterator for LookupHost { + type Item = SocketAddr; + fn next(&mut self) -> Option<SocketAddr> { + self.0 + } +} + +impl<'a> TryFrom<&'a str> for LookupHost { + type Error = io::Error; + + fn try_from(_v: &'a str) -> io::Result<LookupHost> { + unsupported() + } +} + +impl<'a> TryFrom<(&'a str, u16)> for LookupHost { + type Error = io::Error; + + fn try_from(_v: (&'a str, u16)) -> io::Result<LookupHost> { + unsupported() + } +} + +#[allow(nonstandard_style)] +pub mod netc { + pub const AF_INET: u8 = 0; + pub const AF_INET6: u8 = 1; + pub type sa_family_t = u8; + + #[derive(Copy, Clone)] + pub struct in_addr { + pub s_addr: u32, + } + + #[derive(Copy, Clone)] + pub struct sockaddr_in { + pub sin_family: sa_family_t, + pub sin_port: u16, + pub sin_addr: in_addr, + } + + #[derive(Copy, Clone)] + pub struct in6_addr { + pub s6_addr: [u8; 16], + } + + #[derive(Copy, Clone)] + pub struct sockaddr_in6 { + pub sin6_family: sa_family_t, + pub sin6_port: u16, + pub sin6_addr: in6_addr, + pub sin6_flowinfo: u32, + pub sin6_scope_id: u32, + } + + #[derive(Copy, Clone)] + pub struct sockaddr {} +} diff --git a/library/std/src/sys/wasi/os.rs b/library/std/src/sys/wasi/os.rs new file mode 100644 index 000000000..c5229a188 --- /dev/null +++ b/library/std/src/sys/wasi/os.rs @@ -0,0 +1,243 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use crate::any::Any; +use crate::error::Error as StdError; +use crate::ffi::{CStr, CString, OsStr, OsString}; +use crate::fmt; +use crate::io; +use crate::marker::PhantomData; +use crate::os::wasi::prelude::*; +use crate::path::{self, PathBuf}; +use crate::str; +use crate::sys::memchr; +use crate::sys::unsupported; +use crate::vec; + +// Add a few symbols not in upstream `libc` just yet. +mod libc { + pub use libc::*; + + extern "C" { + pub fn getcwd(buf: *mut c_char, size: size_t) -> *mut c_char; + pub fn chdir(dir: *const c_char) -> c_int; + } +} + +#[cfg(not(target_feature = "atomics"))] +pub unsafe fn env_lock() -> impl Any { + // No need for a lock if we're single-threaded, but this function will need + // to get implemented for multi-threaded scenarios +} + +pub fn errno() -> i32 { + extern "C" { + #[thread_local] + static errno: libc::c_int; + } + + unsafe { errno as i32 } +} + +pub fn error_string(errno: i32) -> String { + let mut buf = [0 as libc::c_char; 1024]; + + let p = buf.as_mut_ptr(); + unsafe { + if libc::strerror_r(errno as libc::c_int, p, buf.len()) < 0 { + panic!("strerror_r failure"); + } + str::from_utf8(CStr::from_ptr(p).to_bytes()).unwrap().to_owned() + } +} + +pub fn getcwd() -> io::Result<PathBuf> { + let mut buf = Vec::with_capacity(512); + loop { + unsafe { + let ptr = buf.as_mut_ptr() as *mut libc::c_char; + if !libc::getcwd(ptr, buf.capacity()).is_null() { + let len = CStr::from_ptr(buf.as_ptr() as *const libc::c_char).to_bytes().len(); + buf.set_len(len); + buf.shrink_to_fit(); + return Ok(PathBuf::from(OsString::from_vec(buf))); + } else { + let error = io::Error::last_os_error(); + if error.raw_os_error() != Some(libc::ERANGE) { + return Err(error); + } + } + + // Trigger the internal buffer resizing logic of `Vec` by requiring + // more space than the current capacity. + let cap = buf.capacity(); + buf.set_len(cap); + buf.reserve(1); + } + } +} + +pub fn chdir(p: &path::Path) -> io::Result<()> { + let p: &OsStr = p.as_ref(); + let p = CString::new(p.as_bytes())?; + unsafe { + match libc::chdir(p.as_ptr()) == (0 as libc::c_int) { + true => Ok(()), + false => Err(io::Error::last_os_error()), + } + } +} + +pub struct SplitPaths<'a>(!, PhantomData<&'a ()>); + +pub fn split_paths(_unparsed: &OsStr) -> SplitPaths<'_> { + panic!("unsupported") +} + +impl<'a> Iterator for SplitPaths<'a> { + type Item = PathBuf; + fn next(&mut self) -> Option<PathBuf> { + self.0 + } +} + +#[derive(Debug)] +pub struct JoinPathsError; + +pub fn join_paths<I, T>(_paths: I) -> Result<OsString, JoinPathsError> +where + I: Iterator<Item = T>, + T: AsRef<OsStr>, +{ + Err(JoinPathsError) +} + +impl fmt::Display for JoinPathsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + "not supported on wasm yet".fmt(f) + } +} + +impl StdError for JoinPathsError { + #[allow(deprecated)] + fn description(&self) -> &str { + "not supported on wasm yet" + } +} + +pub fn current_exe() -> io::Result<PathBuf> { + unsupported() +} +pub struct Env { + iter: vec::IntoIter<(OsString, OsString)>, +} + +impl !Send for Env {} +impl !Sync for Env {} + +impl Iterator for Env { + type Item = (OsString, OsString); + fn next(&mut self) -> Option<(OsString, OsString)> { + self.iter.next() + } + fn size_hint(&self) -> (usize, Option<usize>) { + self.iter.size_hint() + } +} + +pub fn env() -> Env { + unsafe { + let _guard = env_lock(); + let mut environ = libc::environ; + let mut result = Vec::new(); + if !environ.is_null() { + while !(*environ).is_null() { + if let Some(key_value) = parse(CStr::from_ptr(*environ).to_bytes()) { + result.push(key_value); + } + environ = environ.add(1); + } + } + return Env { iter: result.into_iter() }; + } + + // See src/libstd/sys/unix/os.rs, same as that + fn parse(input: &[u8]) -> Option<(OsString, OsString)> { + if input.is_empty() { + return None; + } + let pos = memchr::memchr(b'=', &input[1..]).map(|p| p + 1); + pos.map(|p| { + ( + OsStringExt::from_vec(input[..p].to_vec()), + OsStringExt::from_vec(input[p + 1..].to_vec()), + ) + }) + } +} + +pub fn getenv(k: &OsStr) -> Option<OsString> { + let k = CString::new(k.as_bytes()).ok()?; + unsafe { + let _guard = env_lock(); + let s = libc::getenv(k.as_ptr()) as *const libc::c_char; + if s.is_null() { + None + } else { + Some(OsStringExt::from_vec(CStr::from_ptr(s).to_bytes().to_vec())) + } + } +} + +pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { + let k = CString::new(k.as_bytes())?; + let v = CString::new(v.as_bytes())?; + + unsafe { + let _guard = env_lock(); + cvt(libc::setenv(k.as_ptr(), v.as_ptr(), 1)).map(drop) + } +} + +pub fn unsetenv(n: &OsStr) -> io::Result<()> { + let nbuf = CString::new(n.as_bytes())?; + + unsafe { + let _guard = env_lock(); + cvt(libc::unsetenv(nbuf.as_ptr())).map(drop) + } +} + +pub fn temp_dir() -> PathBuf { + panic!("no filesystem on wasm") +} + +pub fn home_dir() -> Option<PathBuf> { + None +} + +pub fn exit(code: i32) -> ! { + unsafe { libc::exit(code) } +} + +pub fn getpid() -> u32 { + panic!("unsupported"); +} + +#[doc(hidden)] +pub trait IsMinusOne { + fn is_minus_one(&self) -> bool; +} + +macro_rules! impl_is_minus_one { + ($($t:ident)*) => ($(impl IsMinusOne for $t { + fn is_minus_one(&self) -> bool { + *self == -1 + } + })*) +} + +impl_is_minus_one! { i8 i16 i32 i64 isize } + +fn cvt<T: IsMinusOne>(t: T) -> io::Result<T> { + if t.is_minus_one() { Err(io::Error::last_os_error()) } else { Ok(t) } +} diff --git a/library/std/src/sys/wasi/stdio.rs b/library/std/src/sys/wasi/stdio.rs new file mode 100644 index 000000000..4cc0e4ed5 --- /dev/null +++ b/library/std/src/sys/wasi/stdio.rs @@ -0,0 +1,112 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use super::fd::WasiFd; +use crate::io::{self, IoSlice, IoSliceMut}; +use crate::mem::ManuallyDrop; +use crate::os::raw; +use crate::os::wasi::io::{AsRawFd, FromRawFd}; + +pub struct Stdin; +pub struct Stdout; +pub struct Stderr; + +impl Stdin { + pub const fn new() -> Stdin { + Stdin + } +} + +impl AsRawFd for Stdin { + #[inline] + fn as_raw_fd(&self) -> raw::c_int { + 0 + } +} + +impl io::Read for Stdin { + fn read(&mut self, data: &mut [u8]) -> io::Result<usize> { + self.read_vectored(&mut [IoSliceMut::new(data)]) + } + + fn read_vectored(&mut self, data: &mut [IoSliceMut<'_>]) -> io::Result<usize> { + ManuallyDrop::new(unsafe { WasiFd::from_raw_fd(self.as_raw_fd()) }).read(data) + } + + #[inline] + fn is_read_vectored(&self) -> bool { + true + } +} + +impl Stdout { + pub const fn new() -> Stdout { + Stdout + } +} + +impl AsRawFd for Stdout { + #[inline] + fn as_raw_fd(&self) -> raw::c_int { + 1 + } +} + +impl io::Write for Stdout { + fn write(&mut self, data: &[u8]) -> io::Result<usize> { + self.write_vectored(&[IoSlice::new(data)]) + } + + fn write_vectored(&mut self, data: &[IoSlice<'_>]) -> io::Result<usize> { + ManuallyDrop::new(unsafe { WasiFd::from_raw_fd(self.as_raw_fd()) }).write(data) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Stderr { + pub const fn new() -> Stderr { + Stderr + } +} + +impl AsRawFd for Stderr { + #[inline] + fn as_raw_fd(&self) -> raw::c_int { + 2 + } +} + +impl io::Write for Stderr { + fn write(&mut self, data: &[u8]) -> io::Result<usize> { + self.write_vectored(&[IoSlice::new(data)]) + } + + fn write_vectored(&mut self, data: &[IoSlice<'_>]) -> io::Result<usize> { + ManuallyDrop::new(unsafe { WasiFd::from_raw_fd(self.as_raw_fd()) }).write(data) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub const STDIN_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE; + +pub fn is_ebadf(err: &io::Error) -> bool { + err.raw_os_error() == Some(wasi::ERRNO_BADF.raw().into()) +} + +pub fn panic_output() -> Option<impl io::Write> { + Some(Stderr::new()) +} diff --git a/library/std/src/sys/wasi/thread.rs b/library/std/src/sys/wasi/thread.rs new file mode 100644 index 000000000..e7a6ab4be --- /dev/null +++ b/library/std/src/sys/wasi/thread.rs @@ -0,0 +1,81 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use crate::ffi::CStr; +use crate::io; +use crate::mem; +use crate::num::NonZeroUsize; +use crate::sys::unsupported; +use crate::time::Duration; + +pub struct Thread(!); + +pub const DEFAULT_MIN_STACK_SIZE: usize = 4096; + +impl Thread { + // unsafe: see thread::Builder::spawn_unchecked for safety requirements + pub unsafe fn new(_stack: usize, _p: Box<dyn FnOnce()>) -> io::Result<Thread> { + unsupported() + } + + pub fn yield_now() { + let ret = unsafe { wasi::sched_yield() }; + debug_assert_eq!(ret, Ok(())); + } + + pub fn set_name(_name: &CStr) { + // nope + } + + pub fn sleep(dur: Duration) { + let nanos = dur.as_nanos(); + assert!(nanos <= u64::MAX as u128); + + const USERDATA: wasi::Userdata = 0x0123_45678; + + let clock = wasi::SubscriptionClock { + id: wasi::CLOCKID_MONOTONIC, + timeout: nanos as u64, + precision: 0, + flags: 0, + }; + + let in_ = wasi::Subscription { + userdata: USERDATA, + u: wasi::SubscriptionU { tag: 0, u: wasi::SubscriptionUU { clock } }, + }; + unsafe { + let mut event: wasi::Event = mem::zeroed(); + let res = wasi::poll_oneoff(&in_, &mut event, 1); + match (res, event) { + ( + Ok(1), + wasi::Event { + userdata: USERDATA, + error: wasi::ERRNO_SUCCESS, + type_: wasi::EVENTTYPE_CLOCK, + .. + }, + ) => {} + _ => panic!("thread::sleep(): unexpected result of poll_oneoff"), + } + } + } + + pub fn join(self) { + self.0 + } +} + +pub fn available_parallelism() -> io::Result<NonZeroUsize> { + unsupported() +} + +pub mod guard { + pub type Guard = !; + pub unsafe fn current() -> Option<Guard> { + None + } + pub unsafe fn init() -> Option<Guard> { + None + } +} diff --git a/library/std/src/sys/wasi/time.rs b/library/std/src/sys/wasi/time.rs new file mode 100644 index 000000000..3d326e491 --- /dev/null +++ b/library/std/src/sys/wasi/time.rs @@ -0,0 +1,65 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use crate::time::Duration; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct Instant(Duration); + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct SystemTime(Duration); + +pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::from_secs(0)); + +fn current_time(clock: wasi::Clockid) -> Duration { + let ts = unsafe { + wasi::clock_time_get( + clock, 1, // precision... seems ignored though? + ) + .unwrap() + }; + Duration::new((ts / 1_000_000_000) as u64, (ts % 1_000_000_000) as u32) +} + +impl Instant { + pub fn now() -> Instant { + Instant(current_time(wasi::CLOCKID_MONOTONIC)) + } + + pub fn checked_sub_instant(&self, other: &Instant) -> Option<Duration> { + self.0.checked_sub(other.0) + } + + pub fn checked_add_duration(&self, other: &Duration) -> Option<Instant> { + Some(Instant(self.0.checked_add(*other)?)) + } + + pub fn checked_sub_duration(&self, other: &Duration) -> Option<Instant> { + Some(Instant(self.0.checked_sub(*other)?)) + } +} + +impl SystemTime { + pub fn now() -> SystemTime { + SystemTime(current_time(wasi::CLOCKID_REALTIME)) + } + + pub fn from_wasi_timestamp(ts: wasi::Timestamp) -> SystemTime { + SystemTime(Duration::from_nanos(ts)) + } + + pub fn to_wasi_timestamp_or_panic(&self) -> wasi::Timestamp { + self.0.as_nanos().try_into().expect("time does not fit in WASI timestamp") + } + + pub fn sub_time(&self, other: &SystemTime) -> Result<Duration, Duration> { + self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0) + } + + pub fn checked_add_duration(&self, other: &Duration) -> Option<SystemTime> { + Some(SystemTime(self.0.checked_add(*other)?)) + } + + pub fn checked_sub_duration(&self, other: &Duration) -> Option<SystemTime> { + Some(SystemTime(self.0.checked_sub(*other)?)) + } +} |