It's wiggle time! (#1202)

* Use wiggle in place of wig in wasi-common

This is a rather massive commit that introduces `wiggle` into the
picture. We still use `wig`'s macro in `old` snapshot and to generate
`wasmtime-wasi` glue, but everything else is now autogenerated by `wiggle`.
In summary, thanks to `wiggle`, we no longer need to worry about
serialising and deserialising to and from the guest memory, and
all guest (WASI) types are now proper idiomatic Rust types.

While we're here, in preparation for the ephemeral snapshot, I went
ahead and reorganised the internal structure of the crate. Instead of
modules like `hostcalls_impl` or `hostcalls_impl::fs`, the structure
now resembles that in ephemeral with modules like `path`, `fd`, etc.
Now, I'm not requiring we leave it like this, but I reckon it looks
cleaner this way after all.

* Fix wig to use new first-class access to caller's mem

* Ignore warning in proc_exit for the moment

* Group unsafes together in args and environ calls

* Simplify pwrite; more unsafe blocks

* Simplify fd_read

* Bundle up unsafes in fd_readdir

* Simplify fd_write

* Add comment to path_readlink re zero-len buffers

* Simplify unsafes in random_get

* Hide GuestPtr<str> to &str in path::get

* Rewrite pread and pwrite using SeekFrom and read/write_vectored

I've left the implementation of VirtualFs pretty much untouched
as I don't feel that comfortable in changing the API too much.
Having said that, I reckon `pread` and `pwrite` could be refactored
out, and `preadv` and `pwritev` could be entirely rewritten using
`seek` and `read_vectored` and `write_vectored`.

* Add comment about VirtFs unsafety

* Fix all mentions of FdEntry to Entry

* Fix warnings on Win

* Add aux struct EntryTable responsible for Fds and Entries

This commit adds aux struct `EntryTable` which is private to `WasiCtx`
and is basically responsible for `Fd` alloc/dealloc as well as storing
matching `Entry`s. This struct is entirely private to `WasiCtx` and
as such as should remain transparent to `WasiCtx` users.

* Remove redundant check for empty buffer in path_readlink

* Preserve and rewind file cursor in pread/pwrite

* Use GuestPtr<[u8]>::copy_from_slice wherever copying bytes directly

* Use GuestPtr<[u8]>::copy_from_slice in fd_readdir

* Clean up unsafes around WasiCtx accessors

* Fix bugs in args_get and environ_get

* Fix conflicts after rebase
This commit is contained in:
Jakub Konka
2020-03-20 21:54:44 +01:00
committed by GitHub
parent f700efeb03
commit 32595faba5
62 changed files with 4293 additions and 5027 deletions

View File

@@ -0,0 +1,26 @@
use crate::sys::entry::OsHandle;
use crate::wasi::Result;
use std::sync::{Mutex, MutexGuard};
use yanix::dir::Dir;
pub(crate) fn get_dir_from_os_handle<'a>(
os_handle: &'a mut OsHandle,
) -> Result<MutexGuard<'a, Dir>> {
let dir = match os_handle.dir {
Some(ref mut dir) => dir,
None => {
// We need to duplicate the fd, because `opendir(3)`:
// Upon successful return from fdopendir(), the file descriptor is under
// control of the system, and if any attempt is made to close the file
// descriptor, or to modify the state of the associated description other
// than by means of closedir(), readdir(), readdir_r(), or rewinddir(),
// the behaviour is undefined.
let fd = (*os_handle).try_clone()?;
let dir = Dir::from(fd)?;
os_handle.dir.get_or_insert(Mutex::new(dir))
}
};
// Note that from this point on, until the end of the parent scope (i.e., enclosing this
// function), we're locking the `Dir` member of this `OsHandle`.
Ok(dir.lock().unwrap())
}

View File

@@ -1,12 +0,0 @@
use crate::wasi::{self, WasiResult};
use std::convert::TryFrom;
pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::SYNC;
pub(crate) fn stdev_from_nix(dev: libc::dev_t) -> WasiResult<wasi::__wasi_device_t> {
wasi::__wasi_device_t::try_from(dev).map_err(Into::into)
}
pub(crate) fn stino_from_nix(ino: libc::ino_t) -> WasiResult<wasi::__wasi_inode_t> {
wasi::__wasi_device_t::try_from(ino).map_err(Into::into)
}

View File

@@ -1,3 +1,5 @@
pub(crate) mod host_impl;
pub(crate) mod hostcalls_impl;
pub(crate) mod fd;
pub(crate) mod oshandle;
pub(crate) mod path;
pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::SYNC;

View File

@@ -1,8 +1,8 @@
use crate::hostcalls_impl::PathGet;
use crate::wasi::{WasiError, WasiResult};
use crate::path::PathGet;
use crate::wasi::{Errno, Result};
use std::os::unix::prelude::AsRawFd;
pub(crate) fn path_unlink_file(resolved: PathGet) -> WasiResult<()> {
pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> {
use yanix::file::{unlinkat, AtFlag};
match unsafe {
unlinkat(
@@ -32,7 +32,7 @@ pub(crate) fn path_unlink_file(resolved: PathGet) -> WasiResult<()> {
} {
Ok(stat) => {
if FileType::from_stat_st_mode(stat.st_mode) == FileType::Directory {
return Err(WasiError::EISDIR);
return Err(Errno::Isdir);
}
}
Err(err) => {
@@ -47,7 +47,7 @@ pub(crate) fn path_unlink_file(resolved: PathGet) -> WasiResult<()> {
}
}
pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> WasiResult<()> {
pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> {
use yanix::file::{fstatat, symlinkat, AtFlag};
log::debug!("path_symlink old_path = {:?}", old_path);
@@ -69,7 +69,7 @@ pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> WasiResult<()>
AtFlag::SYMLINK_NOFOLLOW,
)
} {
Ok(_) => return Err(WasiError::EEXIST),
Ok(_) => return Err(Errno::Exist),
Err(err) => {
log::debug!("path_symlink fstatat error: {:?}", err);
}
@@ -81,7 +81,7 @@ pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> WasiResult<()>
}
}
pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> WasiResult<()> {
pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
use yanix::file::{fstatat, renameat, AtFlag};
match unsafe {
renameat(
@@ -113,9 +113,9 @@ pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> WasiR
Ok(_) => {
// check if destination contains a trailing slash
if resolved_new.path().contains('/') {
return Err(WasiError::ENOTDIR);
return Err(Errno::Notdir);
} else {
return Err(WasiError::ENOENT);
return Err(Errno::Noent);
}
}
Err(err) => {
@@ -129,32 +129,3 @@ pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> WasiR
Ok(()) => Ok(()),
}
}
pub(crate) mod fd_readdir_impl {
use crate::sys::entry_impl::OsHandle;
use crate::wasi::WasiResult;
use std::sync::{Mutex, MutexGuard};
use yanix::dir::Dir;
pub(crate) fn get_dir_from_os_handle<'a>(
os_handle: &'a mut OsHandle,
) -> WasiResult<MutexGuard<'a, Dir>> {
let dir = match os_handle.dir {
Some(ref mut dir) => dir,
None => {
// We need to duplicate the fd, because `opendir(3)`:
// Upon successful return from fdopendir(), the file descriptor is under
// control of the system, and if any attempt is made to close the file
// descriptor, or to modify the state of the associated description other
// than by means of closedir(), readdir(), readdir_r(), or rewinddir(),
// the behaviour is undefined.
let fd = (*os_handle).try_clone()?;
let dir = Dir::from(fd)?;
os_handle.dir.get_or_insert(Mutex::new(dir))
}
};
// Note that from this point on, until the end of the parent scope (i.e., enclosing this
// function), we're locking the `Dir` member of this `OsHandle`.
Ok(dir.lock().unwrap())
}
}

View File

@@ -0,0 +1,35 @@
use crate::wasi::{types, Errno, Result};
use yanix::clock::{clock_getres, clock_gettime, ClockId};
pub(crate) fn res_get(clock_id: types::Clockid) -> Result<types::Timestamp> {
let clock_id: ClockId = clock_id.into();
let timespec = clock_getres(clock_id)?;
// convert to nanoseconds, returning EOVERFLOW in case of overflow;
// this is freelancing a bit from the spec but seems like it'll
// be an unusual situation to hit
(timespec.tv_sec as types::Timestamp)
.checked_mul(1_000_000_000)
.and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as types::Timestamp))
.map_or(Err(Errno::Overflow), |resolution| {
// a supported clock can never return zero; this case will probably never get hit, but
// make sure we follow the spec
if resolution == 0 {
Err(Errno::Inval)
} else {
Ok(resolution)
}
})
}
pub(crate) fn time_get(clock_id: types::Clockid) -> Result<types::Timestamp> {
let clock_id: ClockId = clock_id.into();
let timespec = clock_gettime(clock_id)?;
// convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit
// from the spec but seems like it'll be an unusual situation to hit
(timespec.tv_sec as types::Timestamp)
.checked_mul(1_000_000_000)
.and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as types::Timestamp))
.map_or(Err(Errno::Overflow), Ok)
}

View File

@@ -1,6 +1,8 @@
#[path = "../linux/host_impl.rs"]
pub(crate) mod host_impl;
#[path = "../linux/hostcalls_impl.rs"]
pub(crate) mod hostcalls_impl;
#[path = "../linux/fd.rs"]
pub(crate) mod fd;
#[path = "../linux/oshandle.rs"]
pub(crate) mod oshandle;
#[path = "../linux/path.rs"]
pub(crate) mod path;
pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::RSYNC;

View File

@@ -1,11 +1,11 @@
use crate::entry::{Descriptor, OsHandleRef};
use crate::{sys::unix::sys_impl, wasi};
use crate::wasi::{types, RightsExt};
use std::fs::File;
use std::io;
use std::mem::ManuallyDrop;
use std::os::unix::prelude::{AsRawFd, FileTypeExt, FromRawFd, RawFd};
pub(crate) use sys_impl::oshandle::*;
pub(crate) use super::sys_impl::oshandle::*;
impl AsRawFd for Descriptor {
fn as_raw_fd(&self) -> RawFd {
@@ -33,20 +33,16 @@ pub(crate) fn descriptor_as_oshandle<'lifetime>(
/// This function is unsafe because it operates on a raw file descriptor.
pub(crate) unsafe fn determine_type_and_access_rights<Fd: AsRawFd>(
fd: &Fd,
) -> io::Result<(
wasi::__wasi_filetype_t,
wasi::__wasi_rights_t,
wasi::__wasi_rights_t,
)> {
) -> io::Result<(types::Filetype, types::Rights, types::Rights)> {
let (file_type, mut rights_base, rights_inheriting) = determine_type_rights(fd)?;
use yanix::{fcntl, file::OFlag};
let flags = fcntl::get_status_flags(fd.as_raw_fd())?;
let accmode = flags & OFlag::ACCMODE;
if accmode == OFlag::RDONLY {
rights_base &= !wasi::__WASI_RIGHTS_FD_WRITE;
rights_base &= !types::Rights::FD_WRITE;
} else if accmode == OFlag::WRONLY {
rights_base &= !wasi::__WASI_RIGHTS_FD_READ;
rights_base &= !types::Rights::FD_READ;
}
Ok((file_type, rights_base, rights_inheriting))
@@ -57,11 +53,7 @@ pub(crate) unsafe fn determine_type_and_access_rights<Fd: AsRawFd>(
/// This function is unsafe because it operates on a raw file descriptor.
pub(crate) unsafe fn determine_type_rights<Fd: AsRawFd>(
fd: &Fd,
) -> io::Result<(
wasi::__wasi_filetype_t,
wasi::__wasi_rights_t,
wasi::__wasi_rights_t,
)> {
) -> io::Result<(types::Filetype, types::Rights, types::Rights)> {
let (file_type, rights_base, rights_inheriting) = {
// we just make a `File` here for convenience; we don't want it to close when it drops
let file = std::mem::ManuallyDrop::new(std::fs::File::from_raw_fd(fd.as_raw_fd()));
@@ -69,62 +61,62 @@ pub(crate) unsafe fn determine_type_rights<Fd: AsRawFd>(
if ft.is_block_device() {
log::debug!("Host fd {:?} is a block device", fd.as_raw_fd());
(
wasi::__WASI_FILETYPE_BLOCK_DEVICE,
wasi::RIGHTS_BLOCK_DEVICE_BASE,
wasi::RIGHTS_BLOCK_DEVICE_INHERITING,
types::Filetype::BlockDevice,
types::Rights::block_device_base(),
types::Rights::block_device_inheriting(),
)
} else if ft.is_char_device() {
log::debug!("Host fd {:?} is a char device", fd.as_raw_fd());
use yanix::file::isatty;
if isatty(fd.as_raw_fd())? {
(
wasi::__WASI_FILETYPE_CHARACTER_DEVICE,
wasi::RIGHTS_TTY_BASE,
wasi::RIGHTS_TTY_BASE,
types::Filetype::CharacterDevice,
types::Rights::tty_base(),
types::Rights::tty_base(),
)
} else {
(
wasi::__WASI_FILETYPE_CHARACTER_DEVICE,
wasi::RIGHTS_CHARACTER_DEVICE_BASE,
wasi::RIGHTS_CHARACTER_DEVICE_INHERITING,
types::Filetype::CharacterDevice,
types::Rights::character_device_base(),
types::Rights::character_device_inheriting(),
)
}
} else if ft.is_dir() {
log::debug!("Host fd {:?} is a directory", fd.as_raw_fd());
(
wasi::__WASI_FILETYPE_DIRECTORY,
wasi::RIGHTS_DIRECTORY_BASE,
wasi::RIGHTS_DIRECTORY_INHERITING,
types::Filetype::Directory,
types::Rights::directory_base(),
types::Rights::directory_inheriting(),
)
} else if ft.is_file() {
log::debug!("Host fd {:?} is a file", fd.as_raw_fd());
(
wasi::__WASI_FILETYPE_REGULAR_FILE,
wasi::RIGHTS_REGULAR_FILE_BASE,
wasi::RIGHTS_REGULAR_FILE_INHERITING,
types::Filetype::RegularFile,
types::Rights::regular_file_base(),
types::Rights::regular_file_inheriting(),
)
} else if ft.is_socket() {
log::debug!("Host fd {:?} is a socket", fd.as_raw_fd());
use yanix::socket::{get_socket_type, SockType};
match get_socket_type(fd.as_raw_fd())? {
SockType::Datagram => (
wasi::__WASI_FILETYPE_SOCKET_DGRAM,
wasi::RIGHTS_SOCKET_BASE,
wasi::RIGHTS_SOCKET_INHERITING,
types::Filetype::SocketDgram,
types::Rights::socket_base(),
types::Rights::socket_inheriting(),
),
SockType::Stream => (
wasi::__WASI_FILETYPE_SOCKET_STREAM,
wasi::RIGHTS_SOCKET_BASE,
wasi::RIGHTS_SOCKET_INHERITING,
types::Filetype::SocketStream,
types::Rights::socket_base(),
types::Rights::socket_inheriting(),
),
_ => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
}
} else if ft.is_fifo() {
log::debug!("Host fd {:?} is a fifo", fd.as_raw_fd());
(
wasi::__WASI_FILETYPE_UNKNOWN,
wasi::RIGHTS_REGULAR_FILE_BASE,
wasi::RIGHTS_REGULAR_FILE_INHERITING,
types::Filetype::Unknown,
types::Rights::regular_file_base(),
types::Rights::regular_file_inheriting(),
)
} else {
log::debug!("Host fd {:?} is unknown", fd.as_raw_fd());

View File

@@ -0,0 +1,78 @@
use super::sys_impl::fd::get_dir_from_os_handle;
use crate::sys::entry::OsHandle;
use crate::wasi::{self, types, Result};
use std::convert::TryInto;
use std::fs::File;
use std::os::unix::prelude::AsRawFd;
pub(crate) fn fdstat_get(fd: &File) -> Result<types::Fdflags> {
let fdflags = unsafe { yanix::fcntl::get_status_flags(fd.as_raw_fd())? };
Ok(fdflags.into())
}
pub(crate) fn fdstat_set_flags(fd: &File, fdflags: types::Fdflags) -> Result<Option<OsHandle>> {
unsafe { yanix::fcntl::set_status_flags(fd.as_raw_fd(), fdflags.into())? };
// TODO why are we returning Ok(None) here?
Ok(None)
}
pub(crate) fn advise(
file: &File,
advice: types::Advice,
offset: types::Filesize,
len: types::Filesize,
) -> Result<()> {
use yanix::fadvise::{posix_fadvise, PosixFadviseAdvice};
let offset = offset.try_into()?;
let len = len.try_into()?;
let host_advice = match advice {
types::Advice::Dontneed => PosixFadviseAdvice::DontNeed,
types::Advice::Sequential => PosixFadviseAdvice::Sequential,
types::Advice::Willneed => PosixFadviseAdvice::WillNeed,
types::Advice::Noreuse => PosixFadviseAdvice::NoReuse,
types::Advice::Random => PosixFadviseAdvice::Random,
types::Advice::Normal => PosixFadviseAdvice::Normal,
};
unsafe { posix_fadvise(file.as_raw_fd(), offset, len, host_advice)? };
Ok(())
}
pub(crate) fn filestat_get(file: &std::fs::File) -> Result<types::Filestat> {
use yanix::file::fstat;
let stat = unsafe { fstat(file.as_raw_fd())? };
Ok(stat.try_into()?)
}
pub(crate) fn readdir<'a>(
os_handle: &'a mut OsHandle,
cookie: types::Dircookie,
) -> Result<impl Iterator<Item = Result<(types::Dirent, String)>> + 'a> {
use yanix::dir::{DirIter, Entry, EntryExt, SeekLoc};
// Get an instance of `Dir`; this is host-specific due to intricasies
// of managing a dir stream between Linux and BSD *nixes
let mut dir = get_dir_from_os_handle(os_handle)?;
// Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START,
// new items may not be returned to the caller.
if cookie == wasi::DIRCOOKIE_START {
log::trace!(" | fd_readdir: doing rewinddir");
dir.rewind();
} else {
log::trace!(" | fd_readdir: doing seekdir to {}", cookie);
let loc = unsafe { SeekLoc::from_raw(cookie as i64)? };
dir.seek(loc);
}
Ok(DirIter::new(dir).map(|entry| {
let entry: Entry = entry?;
let name = entry.file_name().to_str()?.to_owned();
let dirent = types::Dirent {
d_next: entry.seek_loc()?.to_raw().try_into()?,
d_ino: entry.ino(),
d_namlen: name.len().try_into()?,
d_type: entry.file_type().into(),
};
Ok((dirent, name))
}))
}

View File

@@ -1,227 +0,0 @@
//! WASI host types specific to *nix host.
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]
use crate::host::FileType;
use crate::wasi::{self, WasiError, WasiResult};
use crate::{helpers, sys::unix::sys_impl};
use std::ffi::OsStr;
use std::io;
use std::os::unix::prelude::OsStrExt;
use yanix::file::OFlag;
pub(crate) use sys_impl::host_impl::*;
impl From<io::Error> for WasiError {
fn from(err: io::Error) -> Self {
match err.raw_os_error() {
Some(code) => match code {
libc::EPERM => Self::EPERM,
libc::ENOENT => Self::ENOENT,
libc::ESRCH => Self::ESRCH,
libc::EINTR => Self::EINTR,
libc::EIO => Self::EIO,
libc::ENXIO => Self::ENXIO,
libc::E2BIG => Self::E2BIG,
libc::ENOEXEC => Self::ENOEXEC,
libc::EBADF => Self::EBADF,
libc::ECHILD => Self::ECHILD,
libc::EAGAIN => Self::EAGAIN,
libc::ENOMEM => Self::ENOMEM,
libc::EACCES => Self::EACCES,
libc::EFAULT => Self::EFAULT,
libc::EBUSY => Self::EBUSY,
libc::EEXIST => Self::EEXIST,
libc::EXDEV => Self::EXDEV,
libc::ENODEV => Self::ENODEV,
libc::ENOTDIR => Self::ENOTDIR,
libc::EISDIR => Self::EISDIR,
libc::EINVAL => Self::EINVAL,
libc::ENFILE => Self::ENFILE,
libc::EMFILE => Self::EMFILE,
libc::ENOTTY => Self::ENOTTY,
libc::ETXTBSY => Self::ETXTBSY,
libc::EFBIG => Self::EFBIG,
libc::ENOSPC => Self::ENOSPC,
libc::ESPIPE => Self::ESPIPE,
libc::EROFS => Self::EROFS,
libc::EMLINK => Self::EMLINK,
libc::EPIPE => Self::EPIPE,
libc::EDOM => Self::EDOM,
libc::ERANGE => Self::ERANGE,
libc::EDEADLK => Self::EDEADLK,
libc::ENAMETOOLONG => Self::ENAMETOOLONG,
libc::ENOLCK => Self::ENOLCK,
libc::ENOSYS => Self::ENOSYS,
libc::ENOTEMPTY => Self::ENOTEMPTY,
libc::ELOOP => Self::ELOOP,
libc::ENOMSG => Self::ENOMSG,
libc::EIDRM => Self::EIDRM,
libc::ENOLINK => Self::ENOLINK,
libc::EPROTO => Self::EPROTO,
libc::EMULTIHOP => Self::EMULTIHOP,
libc::EBADMSG => Self::EBADMSG,
libc::EOVERFLOW => Self::EOVERFLOW,
libc::EILSEQ => Self::EILSEQ,
libc::ENOTSOCK => Self::ENOTSOCK,
libc::EDESTADDRREQ => Self::EDESTADDRREQ,
libc::EMSGSIZE => Self::EMSGSIZE,
libc::EPROTOTYPE => Self::EPROTOTYPE,
libc::ENOPROTOOPT => Self::ENOPROTOOPT,
libc::EPROTONOSUPPORT => Self::EPROTONOSUPPORT,
libc::EAFNOSUPPORT => Self::EAFNOSUPPORT,
libc::EADDRINUSE => Self::EADDRINUSE,
libc::EADDRNOTAVAIL => Self::EADDRNOTAVAIL,
libc::ENETDOWN => Self::ENETDOWN,
libc::ENETUNREACH => Self::ENETUNREACH,
libc::ENETRESET => Self::ENETRESET,
libc::ECONNABORTED => Self::ECONNABORTED,
libc::ECONNRESET => Self::ECONNRESET,
libc::ENOBUFS => Self::ENOBUFS,
libc::EISCONN => Self::EISCONN,
libc::ENOTCONN => Self::ENOTCONN,
libc::ETIMEDOUT => Self::ETIMEDOUT,
libc::ECONNREFUSED => Self::ECONNREFUSED,
libc::EHOSTUNREACH => Self::EHOSTUNREACH,
libc::EALREADY => Self::EALREADY,
libc::EINPROGRESS => Self::EINPROGRESS,
libc::ESTALE => Self::ESTALE,
libc::EDQUOT => Self::EDQUOT,
libc::ECANCELED => Self::ECANCELED,
libc::EOWNERDEAD => Self::EOWNERDEAD,
libc::ENOTRECOVERABLE => Self::ENOTRECOVERABLE,
libc::ENOTSUP => Self::ENOTSUP,
x => {
log::debug!("Unknown errno value: {}", x);
Self::EIO
}
},
None => {
log::debug!("Other I/O error: {}", err);
Self::EIO
}
}
}
}
pub(crate) fn nix_from_fdflags(fdflags: wasi::__wasi_fdflags_t) -> OFlag {
let mut nix_flags = OFlag::empty();
if fdflags & wasi::__WASI_FDFLAGS_APPEND != 0 {
nix_flags.insert(OFlag::APPEND);
}
if fdflags & wasi::__WASI_FDFLAGS_DSYNC != 0 {
nix_flags.insert(OFlag::DSYNC);
}
if fdflags & wasi::__WASI_FDFLAGS_NONBLOCK != 0 {
nix_flags.insert(OFlag::NONBLOCK);
}
if fdflags & wasi::__WASI_FDFLAGS_RSYNC != 0 {
nix_flags.insert(O_RSYNC);
}
if fdflags & wasi::__WASI_FDFLAGS_SYNC != 0 {
nix_flags.insert(OFlag::SYNC);
}
nix_flags
}
pub(crate) fn fdflags_from_nix(oflags: OFlag) -> wasi::__wasi_fdflags_t {
let mut fdflags = 0;
if oflags.contains(OFlag::APPEND) {
fdflags |= wasi::__WASI_FDFLAGS_APPEND;
}
if oflags.contains(OFlag::DSYNC) {
fdflags |= wasi::__WASI_FDFLAGS_DSYNC;
}
if oflags.contains(OFlag::NONBLOCK) {
fdflags |= wasi::__WASI_FDFLAGS_NONBLOCK;
}
if oflags.contains(O_RSYNC) {
fdflags |= wasi::__WASI_FDFLAGS_RSYNC;
}
if oflags.contains(OFlag::SYNC) {
fdflags |= wasi::__WASI_FDFLAGS_SYNC;
}
fdflags
}
pub(crate) fn nix_from_oflags(oflags: wasi::__wasi_oflags_t) -> OFlag {
let mut nix_flags = OFlag::empty();
if oflags & wasi::__WASI_OFLAGS_CREAT != 0 {
nix_flags.insert(OFlag::CREAT);
}
if oflags & wasi::__WASI_OFLAGS_DIRECTORY != 0 {
nix_flags.insert(OFlag::DIRECTORY);
}
if oflags & wasi::__WASI_OFLAGS_EXCL != 0 {
nix_flags.insert(OFlag::EXCL);
}
if oflags & wasi::__WASI_OFLAGS_TRUNC != 0 {
nix_flags.insert(OFlag::TRUNC);
}
nix_flags
}
pub(crate) fn filestat_from_nix(filestat: libc::stat) -> WasiResult<wasi::__wasi_filestat_t> {
use std::convert::TryInto;
fn filestat_to_timestamp(secs: u64, nsecs: u64) -> WasiResult<wasi::__wasi_timestamp_t> {
secs.checked_mul(1_000_000_000)
.and_then(|sec_nsec| sec_nsec.checked_add(nsecs))
.ok_or(WasiError::EOVERFLOW)
}
let filetype = yanix::file::FileType::from_stat_st_mode(filestat.st_mode);
let dev = stdev_from_nix(filestat.st_dev)?;
let ino = stino_from_nix(filestat.st_ino)?;
let atim = filestat_to_timestamp(
filestat.st_atime.try_into()?,
filestat.st_atime_nsec.try_into()?,
)?;
let ctim = filestat_to_timestamp(
filestat.st_ctime.try_into()?,
filestat.st_ctime_nsec.try_into()?,
)?;
let mtim = filestat_to_timestamp(
filestat.st_mtime.try_into()?,
filestat.st_mtime_nsec.try_into()?,
)?;
Ok(wasi::__wasi_filestat_t {
dev,
ino,
nlink: wasi::__wasi_linkcount_t::from(filestat.st_nlink),
size: filestat.st_size as wasi::__wasi_filesize_t,
atim,
ctim,
mtim,
filetype: FileType::from(filetype).to_wasi(),
})
}
/// Creates owned WASI path from OS string.
///
/// NB WASI spec requires OS string to be valid UTF-8. Otherwise,
/// `__WASI_ERRNO_ILSEQ` error is returned.
pub(crate) fn path_from_host<S: AsRef<OsStr>>(s: S) -> WasiResult<String> {
helpers::path_from_slice(s.as_ref().as_bytes()).map(String::from)
}
impl From<yanix::file::FileType> for FileType {
fn from(ft: yanix::file::FileType) -> Self {
use yanix::file::FileType::*;
match ft {
RegularFile => Self::RegularFile,
Symlink => Self::Symlink,
Directory => Self::Directory,
BlockDevice => Self::BlockDevice,
CharacterDevice => Self::CharacterDevice,
/* Unknown | Socket | Fifo */
_ => Self::Unknown,
// TODO how to discriminate between STREAM and DGRAM?
// Perhaps, we should create a more general WASI filetype
// such as __WASI_FILETYPE_SOCKET, and then it would be
// up to the client to check whether it's actually
// STREAM or DGRAM?
}
}
}

View File

@@ -1,329 +0,0 @@
#![allow(non_camel_case_types)]
#![allow(unused_unsafe)]
use crate::entry::Descriptor;
use crate::host::Dirent;
use crate::hostcalls_impl::PathGet;
use crate::sys::entry_impl::OsHandle;
use crate::sys::{host_impl, unix::sys_impl};
use crate::wasi::{self, WasiError, WasiResult};
use std::convert::TryInto;
use std::fs::File;
use std::os::unix::fs::FileExt;
use std::os::unix::prelude::{AsRawFd, FromRawFd};
pub(crate) use sys_impl::hostcalls_impl::*;
pub(crate) fn fd_pread(
file: &File,
buf: &mut [u8],
offset: wasi::__wasi_filesize_t,
) -> WasiResult<usize> {
file.read_at(buf, offset).map_err(Into::into)
}
pub(crate) fn fd_pwrite(
file: &File,
buf: &[u8],
offset: wasi::__wasi_filesize_t,
) -> WasiResult<usize> {
file.write_at(buf, offset).map_err(Into::into)
}
pub(crate) fn fd_fdstat_get(fd: &File) -> WasiResult<wasi::__wasi_fdflags_t> {
unsafe { yanix::fcntl::get_status_flags(fd.as_raw_fd()) }
.map(host_impl::fdflags_from_nix)
.map_err(Into::into)
}
pub(crate) fn fd_fdstat_set_flags(
fd: &File,
fdflags: wasi::__wasi_fdflags_t,
) -> WasiResult<Option<OsHandle>> {
let nix_flags = host_impl::nix_from_fdflags(fdflags);
unsafe { yanix::fcntl::set_status_flags(fd.as_raw_fd(), nix_flags) }
.map(|_| None)
.map_err(Into::into)
}
pub(crate) fn fd_advise(
file: &File,
advice: wasi::__wasi_advice_t,
offset: wasi::__wasi_filesize_t,
len: wasi::__wasi_filesize_t,
) -> WasiResult<()> {
use yanix::fadvise::{posix_fadvise, PosixFadviseAdvice};
let offset = offset.try_into()?;
let len = len.try_into()?;
let host_advice = match advice {
wasi::__WASI_ADVICE_DONTNEED => PosixFadviseAdvice::DontNeed,
wasi::__WASI_ADVICE_SEQUENTIAL => PosixFadviseAdvice::Sequential,
wasi::__WASI_ADVICE_WILLNEED => PosixFadviseAdvice::WillNeed,
wasi::__WASI_ADVICE_NOREUSE => PosixFadviseAdvice::NoReuse,
wasi::__WASI_ADVICE_RANDOM => PosixFadviseAdvice::Random,
wasi::__WASI_ADVICE_NORMAL => PosixFadviseAdvice::Normal,
_ => return Err(WasiError::EINVAL),
};
unsafe { posix_fadvise(file.as_raw_fd(), offset, len, host_advice) }.map_err(Into::into)
}
pub(crate) fn path_create_directory(base: &File, path: &str) -> WasiResult<()> {
use yanix::file::{mkdirat, Mode};
unsafe { mkdirat(base.as_raw_fd(), path, Mode::from_bits_truncate(0o777)) }.map_err(Into::into)
}
pub(crate) fn path_link(
resolved_old: PathGet,
resolved_new: PathGet,
follow_symlinks: bool,
) -> WasiResult<()> {
use yanix::file::{linkat, AtFlag};
let flags = if follow_symlinks {
AtFlag::SYMLINK_FOLLOW
} else {
AtFlag::empty()
};
unsafe {
linkat(
resolved_old.dirfd().as_raw_fd(),
resolved_old.path(),
resolved_new.dirfd().as_raw_fd(),
resolved_new.path(),
flags,
)
}
.map_err(Into::into)
}
pub(crate) fn path_open(
resolved: PathGet,
read: bool,
write: bool,
oflags: wasi::__wasi_oflags_t,
fs_flags: wasi::__wasi_fdflags_t,
) -> WasiResult<Descriptor> {
use yanix::file::{fstatat, openat, AtFlag, FileType, Mode, OFlag};
let mut nix_all_oflags = if read && write {
OFlag::RDWR
} else if write {
OFlag::WRONLY
} else {
OFlag::RDONLY
};
// on non-Capsicum systems, we always want nofollow
nix_all_oflags.insert(OFlag::NOFOLLOW);
// convert open flags
nix_all_oflags.insert(host_impl::nix_from_oflags(oflags));
// convert file descriptor flags
nix_all_oflags.insert(host_impl::nix_from_fdflags(fs_flags));
// Call openat. Use mode 0o666 so that we follow whatever the user's
// umask is, but don't set the executable flag, because it isn't yet
// meaningful for WASI programs to create executable files.
log::debug!("path_open resolved = {:?}", resolved);
log::debug!("path_open oflags = {:?}", nix_all_oflags);
let fd_no = unsafe {
openat(
resolved.dirfd().as_raw_fd(),
resolved.path(),
nix_all_oflags,
Mode::from_bits_truncate(0o666),
)
};
let new_fd = match fd_no {
Ok(fd) => fd,
Err(e) => {
match e.raw_os_error().unwrap() {
// Linux returns ENXIO instead of EOPNOTSUPP when opening a socket
libc::ENXIO => {
match unsafe {
fstatat(
resolved.dirfd().as_raw_fd(),
resolved.path(),
AtFlag::SYMLINK_NOFOLLOW,
)
} {
Ok(stat) => {
if FileType::from_stat_st_mode(stat.st_mode) == FileType::Socket {
return Err(WasiError::ENOTSUP);
}
}
Err(err) => {
log::debug!("path_open fstatat error: {:?}", err);
}
}
}
// Linux returns ENOTDIR instead of ELOOP when using O_NOFOLLOW|O_DIRECTORY
// on a symlink.
libc::ENOTDIR
if !(nix_all_oflags & (OFlag::NOFOLLOW | OFlag::DIRECTORY)).is_empty() =>
{
match unsafe {
fstatat(
resolved.dirfd().as_raw_fd(),
resolved.path(),
AtFlag::SYMLINK_NOFOLLOW,
)
} {
Ok(stat) => {
if FileType::from_stat_st_mode(stat.st_mode) == FileType::Symlink {
return Err(WasiError::ELOOP);
}
}
Err(err) => {
log::debug!("path_open fstatat error: {:?}", err);
}
}
}
// FreeBSD returns EMLINK instead of ELOOP when using O_NOFOLLOW on
// a symlink.
libc::EMLINK if !(nix_all_oflags & OFlag::NOFOLLOW).is_empty() => {
return Err(WasiError::ELOOP);
}
_ => {}
}
return Err(e.into());
}
};
log::debug!("path_open (host) new_fd = {:?}", new_fd);
// Determine the type of the new file descriptor and which rights contradict with this type
Ok(OsHandle::from(unsafe { File::from_raw_fd(new_fd) }).into())
}
pub(crate) fn path_readlink(resolved: PathGet, buf: &mut [u8]) -> WasiResult<usize> {
use std::cmp::min;
use yanix::file::readlinkat;
let read_link = unsafe { readlinkat(resolved.dirfd().as_raw_fd(), resolved.path()) }
.map_err(Into::into)
.and_then(host_impl::path_from_host)?;
let copy_len = min(read_link.len(), buf.len());
if copy_len > 0 {
buf[..copy_len].copy_from_slice(&read_link.as_bytes()[..copy_len]);
}
Ok(copy_len)
}
pub(crate) fn fd_filestat_get(file: &std::fs::File) -> WasiResult<wasi::__wasi_filestat_t> {
use yanix::file::fstat;
unsafe { fstat(file.as_raw_fd()) }
.map_err(Into::into)
.and_then(host_impl::filestat_from_nix)
}
pub(crate) fn path_filestat_get(
resolved: PathGet,
dirflags: wasi::__wasi_lookupflags_t,
) -> WasiResult<wasi::__wasi_filestat_t> {
use yanix::file::{fstatat, AtFlag};
let atflags = match dirflags {
0 => AtFlag::empty(),
_ => AtFlag::SYMLINK_NOFOLLOW,
};
unsafe { fstatat(resolved.dirfd().as_raw_fd(), resolved.path(), atflags) }
.map_err(Into::into)
.and_then(host_impl::filestat_from_nix)
}
pub(crate) fn path_filestat_set_times(
resolved: PathGet,
dirflags: wasi::__wasi_lookupflags_t,
st_atim: wasi::__wasi_timestamp_t,
st_mtim: wasi::__wasi_timestamp_t,
fst_flags: wasi::__wasi_fstflags_t,
) -> WasiResult<()> {
use std::time::{Duration, UNIX_EPOCH};
use yanix::filetime::*;
let set_atim = fst_flags & wasi::__WASI_FSTFLAGS_ATIM != 0;
let set_atim_now = fst_flags & wasi::__WASI_FSTFLAGS_ATIM_NOW != 0;
let set_mtim = fst_flags & wasi::__WASI_FSTFLAGS_MTIM != 0;
let set_mtim_now = fst_flags & wasi::__WASI_FSTFLAGS_MTIM_NOW != 0;
if (set_atim && set_atim_now) || (set_mtim && set_mtim_now) {
return Err(WasiError::EINVAL);
}
let symlink_nofollow = wasi::__WASI_LOOKUPFLAGS_SYMLINK_FOLLOW != dirflags;
let atim = if set_atim {
let time = UNIX_EPOCH + Duration::from_nanos(st_atim);
FileTime::FileTime(filetime::FileTime::from_system_time(time))
} else if set_atim_now {
FileTime::Now
} else {
FileTime::Omit
};
let mtim = if set_mtim {
let time = UNIX_EPOCH + Duration::from_nanos(st_mtim);
FileTime::FileTime(filetime::FileTime::from_system_time(time))
} else if set_mtim_now {
FileTime::Now
} else {
FileTime::Omit
};
utimensat(
&resolved.dirfd().as_os_handle(),
resolved.path(),
atim,
mtim,
symlink_nofollow,
)
.map_err(Into::into)
}
pub(crate) fn path_remove_directory(resolved: PathGet) -> WasiResult<()> {
use yanix::file::{unlinkat, AtFlag};
unsafe {
unlinkat(
resolved.dirfd().as_raw_fd(),
resolved.path(),
AtFlag::REMOVEDIR,
)
}
.map_err(Into::into)
}
pub(crate) fn fd_readdir<'a>(
os_handle: &'a mut OsHandle,
cookie: wasi::__wasi_dircookie_t,
) -> WasiResult<impl Iterator<Item = WasiResult<Dirent>> + 'a> {
use yanix::dir::{DirIter, Entry, EntryExt, SeekLoc};
// Get an instance of `Dir`; this is host-specific due to intricasies
// of managing a dir stream between Linux and BSD *nixes
let mut dir = fd_readdir_impl::get_dir_from_os_handle(os_handle)?;
// Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START,
// new items may not be returned to the caller.
if cookie == wasi::__WASI_DIRCOOKIE_START {
log::trace!(" | fd_readdir: doing rewinddir");
dir.rewind();
} else {
log::trace!(" | fd_readdir: doing seekdir to {}", cookie);
let loc = unsafe { SeekLoc::from_raw(cookie as i64)? };
dir.seek(loc);
}
Ok(DirIter::new(dir).map(|entry| {
let entry: Entry = entry?;
Ok(Dirent {
name: entry
// TODO can we reuse path_from_host for CStr?
.file_name()
.to_str()?
.to_owned(),
ino: entry.ino(),
ftype: entry.file_type().into(),
cookie: entry.seek_loc()?.to_raw().try_into()?,
})
}))
}

View File

@@ -1,66 +0,0 @@
#![allow(non_camel_case_types)]
#![allow(unused_unsafe)]
use crate::sys::host_impl;
use crate::wasi::{self, WasiResult};
use std::fs::File;
use yanix::file::OFlag;
pub(crate) fn path_open_rights(
rights_base: wasi::__wasi_rights_t,
rights_inheriting: wasi::__wasi_rights_t,
oflags: wasi::__wasi_oflags_t,
fs_flags: wasi::__wasi_fdflags_t,
) -> (wasi::__wasi_rights_t, wasi::__wasi_rights_t) {
// which rights are needed on the dirfd?
let mut needed_base = wasi::__WASI_RIGHTS_PATH_OPEN;
let mut needed_inheriting = rights_base | rights_inheriting;
// convert open flags
let oflags = host_impl::nix_from_oflags(oflags);
if oflags.contains(OFlag::CREAT) {
needed_base |= wasi::__WASI_RIGHTS_PATH_CREATE_FILE;
}
if oflags.contains(OFlag::TRUNC) {
needed_base |= wasi::__WASI_RIGHTS_PATH_FILESTAT_SET_SIZE;
}
// convert file descriptor flags
let fdflags = host_impl::nix_from_fdflags(fs_flags);
if fdflags.contains(OFlag::DSYNC) {
needed_inheriting |= wasi::__WASI_RIGHTS_FD_DATASYNC;
}
if fdflags.intersects(host_impl::O_RSYNC | OFlag::SYNC) {
needed_inheriting |= wasi::__WASI_RIGHTS_FD_SYNC;
}
(needed_base, needed_inheriting)
}
pub(crate) fn openat(dirfd: &File, path: &str) -> WasiResult<File> {
use std::os::unix::prelude::{AsRawFd, FromRawFd};
use yanix::file::{openat, Mode};
log::debug!("path_get openat path = {:?}", path);
unsafe {
openat(
dirfd.as_raw_fd(),
path,
OFlag::RDONLY | OFlag::DIRECTORY | OFlag::NOFOLLOW,
Mode::empty(),
)
}
.map(|new_fd| unsafe { File::from_raw_fd(new_fd) })
.map_err(Into::into)
}
pub(crate) fn readlinkat(dirfd: &File, path: &str) -> WasiResult<String> {
use std::os::unix::prelude::AsRawFd;
use yanix::file::readlinkat;
log::debug!("path_get readlinkat path = {:?}", path);
unsafe { readlinkat(dirfd.as_raw_fd(), path) }
.map_err(Into::into)
.and_then(host_impl::path_from_host)
}

View File

@@ -1,213 +0,0 @@
#![allow(non_camel_case_types)]
#![allow(unused_unsafe)]
use crate::hostcalls_impl::{ClockEventData, FdEventData};
use crate::wasi::{self, WasiError, WasiResult};
use std::io;
use yanix::clock::{clock_getres, clock_gettime, ClockId};
fn wasi_clock_id_to_unix(clock_id: wasi::__wasi_clockid_t) -> WasiResult<ClockId> {
// convert the supported clocks to libc types, or return EINVAL
match clock_id {
wasi::__WASI_CLOCKID_REALTIME => Ok(ClockId::Realtime),
wasi::__WASI_CLOCKID_MONOTONIC => Ok(ClockId::Monotonic),
wasi::__WASI_CLOCKID_PROCESS_CPUTIME_ID => Ok(ClockId::ProcessCPUTime),
wasi::__WASI_CLOCKID_THREAD_CPUTIME_ID => Ok(ClockId::ThreadCPUTime),
_ => Err(WasiError::EINVAL),
}
}
pub(crate) fn clock_res_get(
clock_id: wasi::__wasi_clockid_t,
) -> WasiResult<wasi::__wasi_timestamp_t> {
let clock_id = wasi_clock_id_to_unix(clock_id)?;
let timespec = clock_getres(clock_id)?;
// convert to nanoseconds, returning EOVERFLOW in case of overflow;
// this is freelancing a bit from the spec but seems like it'll
// be an unusual situation to hit
(timespec.tv_sec as wasi::__wasi_timestamp_t)
.checked_mul(1_000_000_000)
.and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as wasi::__wasi_timestamp_t))
.map_or(Err(WasiError::EOVERFLOW), |resolution| {
// a supported clock can never return zero; this case will probably never get hit, but
// make sure we follow the spec
if resolution == 0 {
Err(WasiError::EINVAL)
} else {
Ok(resolution)
}
})
}
pub(crate) fn clock_time_get(
clock_id: wasi::__wasi_clockid_t,
) -> WasiResult<wasi::__wasi_timestamp_t> {
let clock_id = wasi_clock_id_to_unix(clock_id)?;
let timespec = clock_gettime(clock_id)?;
// convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit
// from the spec but seems like it'll be an unusual situation to hit
(timespec.tv_sec as wasi::__wasi_timestamp_t)
.checked_mul(1_000_000_000)
.and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as wasi::__wasi_timestamp_t))
.map_or(Err(WasiError::EOVERFLOW), Ok)
}
pub(crate) fn poll_oneoff(
timeout: Option<ClockEventData>,
fd_events: Vec<FdEventData>,
events: &mut Vec<wasi::__wasi_event_t>,
) -> WasiResult<()> {
use std::{convert::TryInto, os::unix::prelude::AsRawFd};
use yanix::poll::{poll, PollFd, PollFlags};
if fd_events.is_empty() && timeout.is_none() {
return Ok(());
}
let mut poll_fds: Vec<_> = fd_events
.iter()
.map(|event| {
let mut flags = PollFlags::empty();
match event.r#type {
wasi::__WASI_EVENTTYPE_FD_READ => flags.insert(PollFlags::POLLIN),
wasi::__WASI_EVENTTYPE_FD_WRITE => flags.insert(PollFlags::POLLOUT),
// An event on a file descriptor can currently only be of type FD_READ or FD_WRITE
// Nothing else has been defined in the specification, and these are also the only two
// events we filtered before. If we get something else here, the code has a serious bug.
_ => unreachable!(),
};
unsafe { PollFd::new(event.descriptor.as_raw_fd(), flags) }
})
.collect();
let poll_timeout = timeout.map_or(-1, |timeout| {
let delay = timeout.delay / 1_000_000; // poll syscall requires delay to expressed in milliseconds
delay.try_into().unwrap_or(libc::c_int::max_value())
});
log::debug!("poll_oneoff poll_timeout = {:?}", poll_timeout);
let ready = loop {
match poll(&mut poll_fds, poll_timeout) {
Err(_) => {
let last_err = io::Error::last_os_error();
if last_err.raw_os_error().unwrap() == libc::EINTR {
continue;
}
return Err(last_err.into());
}
Ok(ready) => break ready,
}
};
Ok(if ready == 0 {
poll_oneoff_handle_timeout_event(timeout.expect("timeout should not be None"), events)
} else {
let ready_events = fd_events.into_iter().zip(poll_fds.into_iter()).take(ready);
poll_oneoff_handle_fd_event(ready_events, events)?
})
}
fn poll_oneoff_handle_timeout_event(
timeout: ClockEventData,
events: &mut Vec<wasi::__wasi_event_t>,
) {
events.push(wasi::__wasi_event_t {
userdata: timeout.userdata,
error: wasi::__WASI_ERRNO_SUCCESS,
r#type: wasi::__WASI_EVENTTYPE_CLOCK,
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
flags: 0,
nbytes: 0,
},
});
}
fn poll_oneoff_handle_fd_event<'a>(
ready_events: impl Iterator<Item = (FdEventData<'a>, yanix::poll::PollFd)>,
events: &mut Vec<wasi::__wasi_event_t>,
) -> WasiResult<()> {
use crate::entry::Descriptor;
use std::{convert::TryInto, os::unix::prelude::AsRawFd};
use yanix::{file::fionread, poll::PollFlags};
fn query_nbytes(fd: &Descriptor) -> WasiResult<u64> {
// fionread may overflow for large files, so use another way for regular files.
if let Descriptor::OsHandle(os_handle) = fd {
let meta = os_handle.metadata()?;
if meta.file_type().is_file() {
use yanix::file::tell;
let len = meta.len();
let host_offset = unsafe { tell(os_handle.as_raw_fd())? };
return Ok(len - host_offset);
}
}
unsafe { Ok(fionread(fd.as_raw_fd())?.into()) }
}
for (fd_event, poll_fd) in ready_events {
log::debug!("poll_oneoff_handle_fd_event fd_event = {:?}", fd_event);
log::debug!("poll_oneoff_handle_fd_event poll_fd = {:?}", poll_fd);
let revents = match poll_fd.revents() {
Some(revents) => revents,
None => continue,
};
log::debug!("poll_oneoff_handle_fd_event revents = {:?}", revents);
let nbytes = if fd_event.r#type == wasi::__WASI_EVENTTYPE_FD_READ {
query_nbytes(fd_event.descriptor)?
} else {
0
};
let output_event = if revents.contains(PollFlags::POLLNVAL) {
wasi::__wasi_event_t {
userdata: fd_event.userdata,
error: wasi::__WASI_ERRNO_BADF,
r#type: fd_event.r#type,
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
nbytes: 0,
flags: wasi::__WASI_EVENTRWFLAGS_FD_READWRITE_HANGUP,
},
}
} else if revents.contains(PollFlags::POLLERR) {
wasi::__wasi_event_t {
userdata: fd_event.userdata,
error: wasi::__WASI_ERRNO_IO,
r#type: fd_event.r#type,
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
nbytes: 0,
flags: wasi::__WASI_EVENTRWFLAGS_FD_READWRITE_HANGUP,
},
}
} else if revents.contains(PollFlags::POLLHUP) {
wasi::__wasi_event_t {
userdata: fd_event.userdata,
error: wasi::__WASI_ERRNO_SUCCESS,
r#type: fd_event.r#type,
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
nbytes: 0,
flags: wasi::__WASI_EVENTRWFLAGS_FD_READWRITE_HANGUP,
},
}
} else if revents.contains(PollFlags::POLLIN) | revents.contains(PollFlags::POLLOUT) {
wasi::__wasi_event_t {
userdata: fd_event.userdata,
error: wasi::__WASI_ERRNO_SUCCESS,
r#type: fd_event.r#type,
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
nbytes: nbytes.try_into()?,
flags: 0,
},
}
} else {
continue;
};
events.push(output_event);
}
Ok(())
}

View File

@@ -1,8 +0,0 @@
//! Unix-specific hostcalls that implement
//! [WASI](https://github.com/WebAssembly/WASI).
mod fs;
pub(crate) mod fs_helpers;
mod misc;
pub(crate) use self::fs::*;
pub(crate) use self::misc::*;

View File

@@ -0,0 +1,19 @@
use crate::sys::entry::OsHandle;
use crate::wasi::Result;
use yanix::dir::Dir;
pub(crate) fn get_dir_from_os_handle(os_handle: &mut OsHandle) -> Result<Box<Dir>> {
// We need to duplicate the fd, because `opendir(3)`:
// After a successful call to fdopendir(), fd is used internally by the implementation,
// and should not otherwise be used by the application.
// `opendir(3p)` also says that it's undefined behavior to
// modify the state of the fd in a different way than by accessing DIR*.
//
// Still, rewinddir will be needed because the two file descriptors
// share progress. But we can safely execute closedir now.
let fd = os_handle.try_clone()?;
// TODO This doesn't look very clean. Can we do something about it?
// Boxing is needed here in order to satisfy `yanix`'s trait requirement for the `DirIter`
// where `T: Deref<Target = Dir>`.
Ok(Box::new(Dir::from(fd)?))
}

View File

@@ -1,11 +0,0 @@
use crate::wasi::{self, WasiResult};
pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::RSYNC;
pub(crate) fn stdev_from_nix(dev: libc::dev_t) -> WasiResult<wasi::__wasi_device_t> {
Ok(wasi::__wasi_device_t::from(dev))
}
pub(crate) fn stino_from_nix(ino: libc::ino_t) -> WasiResult<wasi::__wasi_inode_t> {
Ok(wasi::__wasi_device_t::from(ino))
}

View File

@@ -1,68 +0,0 @@
use crate::entry::Descriptor;
use crate::hostcalls_impl::PathGet;
use crate::wasi::WasiResult;
use std::os::unix::prelude::AsRawFd;
pub(crate) fn path_unlink_file(resolved: PathGet) -> WasiResult<()> {
use yanix::file::{unlinkat, AtFlag};
unsafe {
unlinkat(
resolved.dirfd().as_raw_fd(),
resolved.path(),
AtFlag::empty(),
)
}
.map_err(Into::into)
}
pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> WasiResult<()> {
use yanix::file::symlinkat;
log::debug!("path_symlink old_path = {:?}", old_path);
log::debug!("path_symlink resolved = {:?}", resolved);
unsafe { symlinkat(old_path, resolved.dirfd().as_raw_fd(), resolved.path()) }
.map_err(Into::into)
}
pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> WasiResult<()> {
use yanix::file::renameat;
match (resolved_old.dirfd(), resolved_new.dirfd()) {
(Descriptor::OsHandle(resolved_old_file), Descriptor::OsHandle(resolved_new_file)) => {
unsafe {
renameat(
resolved_old_file.as_raw_fd(),
resolved_old.path(),
resolved_new_file.as_raw_fd(),
resolved_new.path(),
)
}
.map_err(Into::into)
}
_ => {
unimplemented!("path_link with one or more virtual files");
}
}
}
pub(crate) mod fd_readdir_impl {
use crate::sys::entry_impl::OsHandle;
use crate::wasi::WasiResult;
use yanix::dir::Dir;
pub(crate) fn get_dir_from_os_handle(os_handle: &mut OsHandle) -> WasiResult<Box<Dir>> {
// We need to duplicate the fd, because `opendir(3)`:
// After a successful call to fdopendir(), fd is used internally by the implementation,
// and should not otherwise be used by the application.
// `opendir(3p)` also says that it's undefined behavior to
// modify the state of the fd in a different way than by accessing DIR*.
//
// Still, rewinddir will be needed because the two file descriptors
// share progress. But we can safely execute closedir now.
let fd = os_handle.try_clone()?;
// TODO This doesn't look very clean. Can we do something about it?
// Boxing is needed here in order to satisfy `yanix`'s trait requirement for the `DirIter`
// where `T: Deref<Target = Dir>`.
Ok(Box::new(Dir::from(fd)?))
}
}

View File

@@ -1,3 +1,5 @@
pub(crate) mod host_impl;
pub(crate) mod hostcalls_impl;
pub(crate) mod fd;
pub(crate) mod oshandle;
pub(crate) mod path;
pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::RSYNC;

View File

@@ -0,0 +1,46 @@
use crate::entry::Descriptor;
use crate::path::PathGet;
use crate::wasi::Result;
use std::os::unix::prelude::AsRawFd;
pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> {
use yanix::file::{unlinkat, AtFlag};
unsafe {
unlinkat(
resolved.dirfd().as_raw_fd(),
resolved.path(),
AtFlag::empty(),
)?
};
Ok(())
}
pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> {
use yanix::file::symlinkat;
log::debug!("path_symlink old_path = {:?}", old_path);
log::debug!("path_symlink resolved = {:?}", resolved);
unsafe { symlinkat(old_path, resolved.dirfd().as_raw_fd(), resolved.path())? };
Ok(())
}
pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
use yanix::file::renameat;
match (resolved_old.dirfd(), resolved_new.dirfd()) {
(Descriptor::OsHandle(resolved_old_file), Descriptor::OsHandle(resolved_new_file)) => {
unsafe {
renameat(
resolved_old_file.as_raw_fd(),
resolved_old.path(),
resolved_new_file.as_raw_fd(),
resolved_new.path(),
)?
};
Ok(())
}
_ => {
unimplemented!("path_link with one or more virtual files");
}
}
}

View File

@@ -1,14 +1,16 @@
pub(crate) mod entry_impl;
pub(crate) mod host_impl;
pub(crate) mod hostcalls_impl;
pub(crate) mod clock;
pub(crate) mod entry;
pub(crate) mod fd;
pub(crate) mod path;
pub(crate) mod poll;
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
mod linux;
use self::linux as sys_impl;
use linux as sys_impl;
} else if #[cfg(target_os = "emscripten")] {
mod emscripten;
use self::emscripten as sys_impl;
use emscripten as sys_impl;
} else if #[cfg(any(target_os = "macos",
target_os = "netbsd",
target_os = "freebsd",
@@ -16,18 +18,258 @@ cfg_if::cfg_if! {
target_os = "ios",
target_os = "dragonfly"))] {
mod bsd;
use self::bsd as sys_impl;
use bsd as sys_impl;
}
}
use crate::wasi::{types, Errno, Result};
use std::convert::{TryFrom, TryInto};
use std::fs::{File, OpenOptions};
use std::io::Result;
use std::io;
use std::path::Path;
use sys_impl::O_RSYNC;
use yanix::clock::ClockId;
use yanix::file::{AtFlag, OFlag};
pub(crate) fn dev_null() -> Result<File> {
pub(crate) fn dev_null() -> io::Result<File> {
OpenOptions::new().read(true).write(true).open("/dev/null")
}
pub fn preopen_dir<P: AsRef<Path>>(path: P) -> Result<File> {
pub fn preopen_dir<P: AsRef<Path>>(path: P) -> io::Result<File> {
File::open(path)
}
impl From<types::Clockid> for ClockId {
fn from(clock_id: types::Clockid) -> Self {
use types::Clockid::*;
match clock_id {
Realtime => Self::Realtime,
Monotonic => Self::Monotonic,
ProcessCputimeId => Self::ProcessCPUTime,
ThreadCputimeId => Self::ThreadCPUTime,
}
}
}
impl From<io::Error> for Errno {
fn from(err: io::Error) -> Self {
match err.raw_os_error() {
Some(code) => match code {
libc::EPERM => Self::Perm,
libc::ENOENT => Self::Noent,
libc::ESRCH => Self::Srch,
libc::EINTR => Self::Intr,
libc::EIO => Self::Io,
libc::ENXIO => Self::Nxio,
libc::E2BIG => Self::TooBig,
libc::ENOEXEC => Self::Noexec,
libc::EBADF => Self::Badf,
libc::ECHILD => Self::Child,
libc::EAGAIN => Self::Again,
libc::ENOMEM => Self::Nomem,
libc::EACCES => Self::Acces,
libc::EFAULT => Self::Fault,
libc::EBUSY => Self::Busy,
libc::EEXIST => Self::Exist,
libc::EXDEV => Self::Xdev,
libc::ENODEV => Self::Nodev,
libc::ENOTDIR => Self::Notdir,
libc::EISDIR => Self::Isdir,
libc::EINVAL => Self::Inval,
libc::ENFILE => Self::Nfile,
libc::EMFILE => Self::Mfile,
libc::ENOTTY => Self::Notty,
libc::ETXTBSY => Self::Txtbsy,
libc::EFBIG => Self::Fbig,
libc::ENOSPC => Self::Nospc,
libc::ESPIPE => Self::Spipe,
libc::EROFS => Self::Rofs,
libc::EMLINK => Self::Mlink,
libc::EPIPE => Self::Pipe,
libc::EDOM => Self::Dom,
libc::ERANGE => Self::Range,
libc::EDEADLK => Self::Deadlk,
libc::ENAMETOOLONG => Self::Nametoolong,
libc::ENOLCK => Self::Nolck,
libc::ENOSYS => Self::Nosys,
libc::ENOTEMPTY => Self::Notempty,
libc::ELOOP => Self::Loop,
libc::ENOMSG => Self::Nomsg,
libc::EIDRM => Self::Idrm,
libc::ENOLINK => Self::Nolink,
libc::EPROTO => Self::Proto,
libc::EMULTIHOP => Self::Multihop,
libc::EBADMSG => Self::Badmsg,
libc::EOVERFLOW => Self::Overflow,
libc::EILSEQ => Self::Ilseq,
libc::ENOTSOCK => Self::Notsock,
libc::EDESTADDRREQ => Self::Destaddrreq,
libc::EMSGSIZE => Self::Msgsize,
libc::EPROTOTYPE => Self::Prototype,
libc::ENOPROTOOPT => Self::Noprotoopt,
libc::EPROTONOSUPPORT => Self::Protonosupport,
libc::EAFNOSUPPORT => Self::Afnosupport,
libc::EADDRINUSE => Self::Addrinuse,
libc::EADDRNOTAVAIL => Self::Addrnotavail,
libc::ENETDOWN => Self::Netdown,
libc::ENETUNREACH => Self::Netunreach,
libc::ENETRESET => Self::Netreset,
libc::ECONNABORTED => Self::Connaborted,
libc::ECONNRESET => Self::Connreset,
libc::ENOBUFS => Self::Nobufs,
libc::EISCONN => Self::Isconn,
libc::ENOTCONN => Self::Notconn,
libc::ETIMEDOUT => Self::Timedout,
libc::ECONNREFUSED => Self::Connrefused,
libc::EHOSTUNREACH => Self::Hostunreach,
libc::EALREADY => Self::Already,
libc::EINPROGRESS => Self::Inprogress,
libc::ESTALE => Self::Stale,
libc::EDQUOT => Self::Dquot,
libc::ECANCELED => Self::Canceled,
libc::EOWNERDEAD => Self::Ownerdead,
libc::ENOTRECOVERABLE => Self::Notrecoverable,
libc::ENOTSUP => Self::Notsup,
x => {
log::debug!("Unknown errno value: {}", x);
Self::Io
}
},
None => {
log::debug!("Other I/O error: {}", err);
Self::Io
}
}
}
}
impl From<types::Fdflags> for OFlag {
fn from(fdflags: types::Fdflags) -> Self {
let mut nix_flags = Self::empty();
if fdflags.contains(&types::Fdflags::APPEND) {
nix_flags.insert(Self::APPEND);
}
if fdflags.contains(&types::Fdflags::DSYNC) {
nix_flags.insert(Self::DSYNC);
}
if fdflags.contains(&types::Fdflags::NONBLOCK) {
nix_flags.insert(Self::NONBLOCK);
}
if fdflags.contains(&types::Fdflags::RSYNC) {
nix_flags.insert(O_RSYNC);
}
if fdflags.contains(&types::Fdflags::SYNC) {
nix_flags.insert(Self::SYNC);
}
nix_flags
}
}
impl From<OFlag> for types::Fdflags {
fn from(oflags: OFlag) -> Self {
let mut fdflags = Self::empty();
if oflags.contains(OFlag::APPEND) {
fdflags |= Self::APPEND;
}
if oflags.contains(OFlag::DSYNC) {
fdflags |= Self::DSYNC;
}
if oflags.contains(OFlag::NONBLOCK) {
fdflags |= Self::NONBLOCK;
}
if oflags.contains(O_RSYNC) {
fdflags |= Self::RSYNC;
}
if oflags.contains(OFlag::SYNC) {
fdflags |= Self::SYNC;
}
fdflags
}
}
impl From<types::Oflags> for OFlag {
fn from(oflags: types::Oflags) -> Self {
let mut nix_flags = Self::empty();
if oflags.contains(&types::Oflags::CREAT) {
nix_flags.insert(Self::CREAT);
}
if oflags.contains(&types::Oflags::DIRECTORY) {
nix_flags.insert(Self::DIRECTORY);
}
if oflags.contains(&types::Oflags::EXCL) {
nix_flags.insert(Self::EXCL);
}
if oflags.contains(&types::Oflags::TRUNC) {
nix_flags.insert(Self::TRUNC);
}
nix_flags
}
}
impl TryFrom<libc::stat> for types::Filestat {
type Error = Errno;
fn try_from(filestat: libc::stat) -> Result<Self> {
fn filestat_to_timestamp(secs: u64, nsecs: u64) -> Result<types::Timestamp> {
secs.checked_mul(1_000_000_000)
.and_then(|sec_nsec| sec_nsec.checked_add(nsecs))
.ok_or(Errno::Overflow)
}
let filetype = yanix::file::FileType::from_stat_st_mode(filestat.st_mode);
let dev = filestat.st_dev.try_into()?;
let ino = filestat.st_ino.try_into()?;
let atim = filestat_to_timestamp(
filestat.st_atime.try_into()?,
filestat.st_atime_nsec.try_into()?,
)?;
let ctim = filestat_to_timestamp(
filestat.st_ctime.try_into()?,
filestat.st_ctime_nsec.try_into()?,
)?;
let mtim = filestat_to_timestamp(
filestat.st_mtime.try_into()?,
filestat.st_mtime_nsec.try_into()?,
)?;
Ok(Self {
dev,
ino,
nlink: filestat.st_nlink.into(),
size: filestat.st_size as types::Filesize,
atim,
ctim,
mtim,
filetype: filetype.into(),
})
}
}
impl From<yanix::file::FileType> for types::Filetype {
fn from(ft: yanix::file::FileType) -> Self {
use yanix::file::FileType::*;
match ft {
RegularFile => Self::RegularFile,
Symlink => Self::SymbolicLink,
Directory => Self::Directory,
BlockDevice => Self::BlockDevice,
CharacterDevice => Self::CharacterDevice,
/* Unknown | Socket | Fifo */
_ => Self::Unknown,
// TODO how to discriminate between STREAM and DGRAM?
// Perhaps, we should create a more general WASI filetype
// such as __WASI_FILETYPE_SOCKET, and then it would be
// up to the client to check whether it's actually
// STREAM or DGRAM?
}
}
}
impl From<types::Lookupflags> for AtFlag {
fn from(flags: types::Lookupflags) -> Self {
match flags {
types::Lookupflags::SYMLINK_FOLLOW => Self::empty(),
_ => Self::SYMLINK_NOFOLLOW,
}
}
}

View File

@@ -0,0 +1,298 @@
use crate::entry::Descriptor;
use crate::path::PathGet;
use crate::sys::entry::OsHandle;
use crate::sys::unix::sys_impl;
use crate::wasi::{types, Errno, Result};
use std::convert::TryInto;
use std::ffi::OsStr;
use std::fs::File;
use std::os::unix::prelude::{AsRawFd, FromRawFd, OsStrExt};
use std::str;
use yanix::file::OFlag;
pub(crate) use sys_impl::path::*;
/// Creates owned WASI path from OS string.
///
/// NB WASI spec requires OS string to be valid UTF-8. Otherwise,
/// `__WASI_ERRNO_ILSEQ` error is returned.
pub(crate) fn from_host<S: AsRef<OsStr>>(s: S) -> Result<String> {
let s = str::from_utf8(s.as_ref().as_bytes())?;
Ok(s.to_owned())
}
pub(crate) fn open_rights(
rights_base: types::Rights,
rights_inheriting: types::Rights,
oflags: types::Oflags,
fs_flags: types::Fdflags,
) -> (types::Rights, types::Rights) {
// which rights are needed on the dirfd?
let mut needed_base = types::Rights::PATH_OPEN;
let mut needed_inheriting = rights_base | rights_inheriting;
// convert open flags
let oflags: OFlag = oflags.into();
if oflags.contains(OFlag::CREAT) {
needed_base |= types::Rights::PATH_CREATE_FILE;
}
if oflags.contains(OFlag::TRUNC) {
needed_base |= types::Rights::PATH_FILESTAT_SET_SIZE;
}
// convert file descriptor flags
let fdflags: OFlag = fs_flags.into();
if fdflags.contains(OFlag::DSYNC) {
needed_inheriting |= types::Rights::FD_DATASYNC;
}
if fdflags.intersects(sys_impl::O_RSYNC | OFlag::SYNC) {
needed_inheriting |= types::Rights::FD_SYNC;
}
(needed_base, needed_inheriting)
}
pub(crate) fn openat(dirfd: &File, path: &str) -> Result<File> {
use std::os::unix::prelude::{AsRawFd, FromRawFd};
use yanix::file::{openat, Mode};
log::debug!("path_get openat path = {:?}", path);
let raw_fd = unsafe {
openat(
dirfd.as_raw_fd(),
path,
OFlag::RDONLY | OFlag::DIRECTORY | OFlag::NOFOLLOW,
Mode::empty(),
)?
};
let file = unsafe { File::from_raw_fd(raw_fd) };
Ok(file)
}
pub(crate) fn readlinkat(dirfd: &File, path: &str) -> Result<String> {
use std::os::unix::prelude::AsRawFd;
use yanix::file::readlinkat;
log::debug!("path_get readlinkat path = {:?}", path);
let path = unsafe { readlinkat(dirfd.as_raw_fd(), path)? };
let path = from_host(path)?;
Ok(path)
}
pub(crate) fn create_directory(base: &File, path: &str) -> Result<()> {
use yanix::file::{mkdirat, Mode};
unsafe { mkdirat(base.as_raw_fd(), path, Mode::from_bits_truncate(0o777))? };
Ok(())
}
pub(crate) fn link(
resolved_old: PathGet,
resolved_new: PathGet,
follow_symlinks: bool,
) -> Result<()> {
use yanix::file::{linkat, AtFlag};
let flags = if follow_symlinks {
AtFlag::SYMLINK_FOLLOW
} else {
AtFlag::empty()
};
unsafe {
linkat(
resolved_old.dirfd().as_raw_fd(),
resolved_old.path(),
resolved_new.dirfd().as_raw_fd(),
resolved_new.path(),
flags,
)?
};
Ok(())
}
pub(crate) fn open(
resolved: PathGet,
read: bool,
write: bool,
oflags: types::Oflags,
fs_flags: types::Fdflags,
) -> Result<Descriptor> {
use yanix::file::{fstatat, openat, AtFlag, FileType, Mode, OFlag};
let mut nix_all_oflags = if read && write {
OFlag::RDWR
} else if write {
OFlag::WRONLY
} else {
OFlag::RDONLY
};
// on non-Capsicum systems, we always want nofollow
nix_all_oflags.insert(OFlag::NOFOLLOW);
// convert open flags
nix_all_oflags.insert(oflags.into());
// convert file descriptor flags
nix_all_oflags.insert(fs_flags.into());
// Call openat. Use mode 0o666 so that we follow whatever the user's
// umask is, but don't set the executable flag, because it isn't yet
// meaningful for WASI programs to create executable files.
log::debug!("path_open resolved = {:?}", resolved);
log::debug!("path_open oflags = {:?}", nix_all_oflags);
let fd_no = unsafe {
openat(
resolved.dirfd().as_raw_fd(),
resolved.path(),
nix_all_oflags,
Mode::from_bits_truncate(0o666),
)
};
let new_fd = match fd_no {
Ok(fd) => fd,
Err(e) => {
match e.raw_os_error().unwrap() {
// Linux returns ENXIO instead of EOPNOTSUPP when opening a socket
libc::ENXIO => {
match unsafe {
fstatat(
resolved.dirfd().as_raw_fd(),
resolved.path(),
AtFlag::SYMLINK_NOFOLLOW,
)
} {
Ok(stat) => {
if FileType::from_stat_st_mode(stat.st_mode) == FileType::Socket {
return Err(Errno::Notsup);
}
}
Err(err) => {
log::debug!("path_open fstatat error: {:?}", err);
}
}
}
// Linux returns ENOTDIR instead of ELOOP when using O_NOFOLLOW|O_DIRECTORY
// on a symlink.
libc::ENOTDIR
if !(nix_all_oflags & (OFlag::NOFOLLOW | OFlag::DIRECTORY)).is_empty() =>
{
match unsafe {
fstatat(
resolved.dirfd().as_raw_fd(),
resolved.path(),
AtFlag::SYMLINK_NOFOLLOW,
)
} {
Ok(stat) => {
if FileType::from_stat_st_mode(stat.st_mode) == FileType::Symlink {
return Err(Errno::Loop);
}
}
Err(err) => {
log::debug!("path_open fstatat error: {:?}", err);
}
}
}
// FreeBSD returns EMLINK instead of ELOOP when using O_NOFOLLOW on
// a symlink.
libc::EMLINK if !(nix_all_oflags & OFlag::NOFOLLOW).is_empty() => {
return Err(Errno::Loop);
}
_ => {}
}
return Err(e.into());
}
};
log::debug!("path_open (host) new_fd = {:?}", new_fd);
// Determine the type of the new file descriptor and which rights contradict with this type
Ok(OsHandle::from(unsafe { File::from_raw_fd(new_fd) }).into())
}
pub(crate) fn readlink(resolved: PathGet, buf: &mut [u8]) -> Result<usize> {
use std::cmp::min;
use yanix::file::readlinkat;
let read_link = unsafe { readlinkat(resolved.dirfd().as_raw_fd(), resolved.path())? };
let read_link = from_host(read_link)?;
let copy_len = min(read_link.len(), buf.len());
if copy_len > 0 {
buf[..copy_len].copy_from_slice(&read_link.as_bytes()[..copy_len]);
}
Ok(copy_len)
}
pub(crate) fn filestat_get(
resolved: PathGet,
dirflags: types::Lookupflags,
) -> Result<types::Filestat> {
use yanix::file::fstatat;
let atflags = dirflags.into();
let filestat = unsafe { fstatat(resolved.dirfd().as_raw_fd(), resolved.path(), atflags)? };
let filestat = filestat.try_into()?;
Ok(filestat)
}
pub(crate) fn filestat_set_times(
resolved: PathGet,
dirflags: types::Lookupflags,
st_atim: types::Timestamp,
st_mtim: types::Timestamp,
fst_flags: types::Fstflags,
) -> Result<()> {
use std::time::{Duration, UNIX_EPOCH};
use yanix::filetime::*;
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);
if (set_atim && set_atim_now) || (set_mtim && set_mtim_now) {
return Err(Errno::Inval);
}
let symlink_nofollow = types::Lookupflags::SYMLINK_FOLLOW != dirflags;
let atim = if set_atim {
let time = UNIX_EPOCH + Duration::from_nanos(st_atim);
FileTime::FileTime(filetime::FileTime::from_system_time(time))
} else if set_atim_now {
FileTime::Now
} else {
FileTime::Omit
};
let mtim = if set_mtim {
let time = UNIX_EPOCH + Duration::from_nanos(st_mtim);
FileTime::FileTime(filetime::FileTime::from_system_time(time))
} else if set_mtim_now {
FileTime::Now
} else {
FileTime::Omit
};
utimensat(
&resolved.dirfd().as_os_handle(),
resolved.path(),
atim,
mtim,
symlink_nofollow,
)?;
Ok(())
}
pub(crate) fn remove_directory(resolved: PathGet) -> Result<()> {
use yanix::file::{unlinkat, AtFlag};
unsafe {
unlinkat(
resolved.dirfd().as_raw_fd(),
resolved.path(),
AtFlag::REMOVEDIR,
)?
};
Ok(())
}

View File

@@ -0,0 +1,159 @@
use crate::poll::{ClockEventData, FdEventData};
use crate::wasi::{types, Errno, Result};
use std::io;
pub(crate) fn oneoff(
timeout: Option<ClockEventData>,
fd_events: Vec<FdEventData>,
events: &mut Vec<types::Event>,
) -> Result<()> {
use std::{convert::TryInto, os::unix::prelude::AsRawFd};
use yanix::poll::{poll, PollFd, PollFlags};
if fd_events.is_empty() && timeout.is_none() {
return Ok(());
}
let mut poll_fds: Vec<_> = fd_events
.iter()
.map(|event| {
let mut flags = PollFlags::empty();
match event.r#type {
types::Eventtype::FdRead => flags.insert(PollFlags::POLLIN),
types::Eventtype::FdWrite => flags.insert(PollFlags::POLLOUT),
// An event on a file descriptor can currently only be of type FD_READ or FD_WRITE
// Nothing else has been defined in the specification, and these are also the only two
// events we filtered before. If we get something else here, the code has a serious bug.
_ => unreachable!(),
};
unsafe { PollFd::new(event.descriptor.as_raw_fd(), flags) }
})
.collect();
let poll_timeout = timeout.map_or(-1, |timeout| {
let delay = timeout.delay / 1_000_000; // poll syscall requires delay to expressed in milliseconds
delay.try_into().unwrap_or(libc::c_int::max_value())
});
log::debug!("poll_oneoff poll_timeout = {:?}", poll_timeout);
let ready = loop {
match poll(&mut poll_fds, poll_timeout) {
Err(_) => {
let last_err = io::Error::last_os_error();
if last_err.raw_os_error().unwrap() == libc::EINTR {
continue;
}
return Err(last_err.into());
}
Ok(ready) => break ready,
}
};
Ok(if ready == 0 {
handle_timeout_event(timeout.expect("timeout should not be None"), events)
} else {
let ready_events = fd_events.into_iter().zip(poll_fds.into_iter()).take(ready);
handle_fd_event(ready_events, events)?
})
}
fn handle_timeout_event(timeout: ClockEventData, events: &mut Vec<types::Event>) {
events.push(types::Event {
userdata: timeout.userdata,
error: Errno::Success,
type_: types::Eventtype::Clock,
fd_readwrite: types::EventFdReadwrite {
flags: types::Eventrwflags::empty(),
nbytes: 0,
},
});
}
fn handle_fd_event<'a>(
ready_events: impl Iterator<Item = (FdEventData<'a>, yanix::poll::PollFd)>,
events: &mut Vec<types::Event>,
) -> Result<()> {
use crate::entry::Descriptor;
use std::{convert::TryInto, os::unix::prelude::AsRawFd};
use yanix::{file::fionread, poll::PollFlags};
fn query_nbytes(fd: &Descriptor) -> Result<u64> {
// fionread may overflow for large files, so use another way for regular files.
if let Descriptor::OsHandle(os_handle) = fd {
let meta = os_handle.metadata()?;
if meta.file_type().is_file() {
use yanix::file::tell;
let len = meta.len();
let host_offset = unsafe { tell(os_handle.as_raw_fd())? };
return Ok(len - host_offset);
}
}
unsafe { Ok(fionread(fd.as_raw_fd())?.into()) }
}
for (fd_event, poll_fd) in ready_events {
log::debug!("poll_oneoff_handle_fd_event fd_event = {:?}", fd_event);
log::debug!("poll_oneoff_handle_fd_event poll_fd = {:?}", poll_fd);
let revents = match poll_fd.revents() {
Some(revents) => revents,
None => continue,
};
log::debug!("poll_oneoff_handle_fd_event revents = {:?}", revents);
let nbytes = if fd_event.r#type == types::Eventtype::FdRead {
query_nbytes(&fd_event.descriptor)?
} else {
0
};
let output_event = if revents.contains(PollFlags::POLLNVAL) {
types::Event {
userdata: fd_event.userdata,
error: Errno::Badf,
type_: fd_event.r#type,
fd_readwrite: types::EventFdReadwrite {
nbytes: 0,
flags: types::Eventrwflags::FD_READWRITE_HANGUP,
},
}
} else if revents.contains(PollFlags::POLLERR) {
types::Event {
userdata: fd_event.userdata,
error: Errno::Io,
type_: fd_event.r#type,
fd_readwrite: types::EventFdReadwrite {
nbytes: 0,
flags: types::Eventrwflags::FD_READWRITE_HANGUP,
},
}
} else if revents.contains(PollFlags::POLLHUP) {
types::Event {
userdata: fd_event.userdata,
error: Errno::Success,
type_: fd_event.r#type,
fd_readwrite: types::EventFdReadwrite {
nbytes: 0,
flags: types::Eventrwflags::FD_READWRITE_HANGUP,
},
}
} else if revents.contains(PollFlags::POLLIN) | revents.contains(PollFlags::POLLOUT) {
types::Event {
userdata: fd_event.userdata,
error: Errno::Success,
type_: fd_event.r#type,
fd_readwrite: types::EventFdReadwrite {
nbytes: nbytes.try_into()?,
flags: types::Eventrwflags::empty(),
},
}
} else {
continue;
};
events.push(output_event);
}
Ok(())
}