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:
26
crates/wasi-common/src/sys/unix/bsd/fd.rs
Normal file
26
crates/wasi-common/src/sys/unix/bsd/fd.rs
Normal 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())
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
35
crates/wasi-common/src/sys/unix/clock.rs
Normal file
35
crates/wasi-common/src/sys/unix/clock.rs
Normal 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)
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
78
crates/wasi-common/src/sys/unix/fd.rs
Normal file
78
crates/wasi-common/src/sys/unix/fd.rs
Normal 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))
|
||||
}))
|
||||
}
|
||||
@@ -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?
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()?,
|
||||
})
|
||||
}))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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::*;
|
||||
19
crates/wasi-common/src/sys/unix/linux/fd.rs
Normal file
19
crates/wasi-common/src/sys/unix/linux/fd.rs
Normal 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)?))
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)?))
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
46
crates/wasi-common/src/sys/unix/linux/path.rs
Normal file
46
crates/wasi-common/src/sys/unix/linux/path.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
298
crates/wasi-common/src/sys/unix/path.rs
Normal file
298
crates/wasi-common/src/sys/unix/path.rs
Normal 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(())
|
||||
}
|
||||
159
crates/wasi-common/src/sys/unix/poll.rs
Normal file
159
crates/wasi-common/src/sys/unix/poll.rs
Normal 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user