* Log str repr of WASI errno at trace level
This commit refactors `Error` enum, and adds logging of the WASI
errno string representation at the trace level. Now, when tracing
WASI syscalls, we will be greeted with a nicely formatted errno
value after each syscall:
```
path_open(...)
| *fd=5
| errno=ESUCCESS
```
This commit gets rid of `errno_from_nix`, `errno_from_win` and
`errno_from_host` helper fns in favour of direct `From` implementations
for the relevant types such as `yanix::Errno` and `winx::winerror::WinError`.
`errno_from_host` is replaced by a trait `FromRawOsError`.
* Back port changes to snapshot0
* Fix indentation in logs
320 lines
10 KiB
Rust
320 lines
10 KiB
Rust
#![allow(non_camel_case_types)]
|
|
#![allow(unused_unsafe)]
|
|
use crate::host::Dirent;
|
|
use crate::hostcalls_impl::PathGet;
|
|
use crate::sys::{fdentry_impl::OsHandle, host_impl, unix::sys_impl};
|
|
use crate::{wasi, Error, Result};
|
|
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,
|
|
) -> Result<usize> {
|
|
file.read_at(buf, offset).map_err(Into::into)
|
|
}
|
|
|
|
pub(crate) fn fd_pwrite(file: &File, buf: &[u8], offset: wasi::__wasi_filesize_t) -> Result<usize> {
|
|
file.write_at(buf, offset).map_err(Into::into)
|
|
}
|
|
|
|
pub(crate) fn fd_fdstat_get(fd: &File) -> Result<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,
|
|
) -> Result<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,
|
|
) -> Result<()> {
|
|
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(Error::EINVAL),
|
|
};
|
|
unsafe { posix_fadvise(file.as_raw_fd(), offset, len, host_advice) }.map_err(Into::into)
|
|
}
|
|
|
|
pub(crate) fn path_create_directory(resolved: PathGet) -> Result<()> {
|
|
use yanix::file::{mkdirat, Mode};
|
|
unsafe {
|
|
mkdirat(
|
|
resolved.dirfd().as_raw_fd(),
|
|
resolved.path(),
|
|
Mode::from_bits_truncate(0o777),
|
|
)
|
|
}
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub(crate) fn path_link(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
|
|
use yanix::file::{linkat, AtFlag};
|
|
unsafe {
|
|
linkat(
|
|
resolved_old.dirfd().as_raw_fd(),
|
|
resolved_old.path(),
|
|
resolved_new.dirfd().as_raw_fd(),
|
|
resolved_new.path(),
|
|
AtFlag::SYMLINK_FOLLOW,
|
|
)
|
|
}
|
|
.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,
|
|
) -> Result<File> {
|
|
use yanix::{
|
|
file::{fstatat, openat, AtFlag, Mode, OFlag, SFlag},
|
|
Errno,
|
|
};
|
|
|
|
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 new_fd = match unsafe {
|
|
openat(
|
|
resolved.dirfd().as_raw_fd(),
|
|
resolved.path(),
|
|
nix_all_oflags,
|
|
Mode::from_bits_truncate(0o666),
|
|
)
|
|
} {
|
|
Ok(fd) => fd,
|
|
Err(e) => {
|
|
if let yanix::YanixError::Errno(errno) = e {
|
|
match errno {
|
|
// Linux returns ENXIO instead of EOPNOTSUPP when opening a socket
|
|
Errno::ENXIO => {
|
|
if let Ok(stat) = unsafe {
|
|
fstatat(
|
|
resolved.dirfd().as_raw_fd(),
|
|
resolved.path(),
|
|
AtFlag::SYMLINK_NOFOLLOW,
|
|
)
|
|
} {
|
|
if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::IFSOCK) {
|
|
return Err(Error::ENOTSUP);
|
|
} else {
|
|
return Err(Error::ENXIO);
|
|
}
|
|
} else {
|
|
return Err(Error::ENXIO);
|
|
}
|
|
}
|
|
// Linux returns ENOTDIR instead of ELOOP when using O_NOFOLLOW|O_DIRECTORY
|
|
// on a symlink.
|
|
Errno::ENOTDIR
|
|
if !(nix_all_oflags & (OFlag::NOFOLLOW | OFlag::DIRECTORY)).is_empty() =>
|
|
{
|
|
if let Ok(stat) = unsafe {
|
|
fstatat(
|
|
resolved.dirfd().as_raw_fd(),
|
|
resolved.path(),
|
|
AtFlag::SYMLINK_NOFOLLOW,
|
|
)
|
|
} {
|
|
if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::IFLNK) {
|
|
return Err(Error::ELOOP);
|
|
}
|
|
}
|
|
return Err(Error::ENOTDIR);
|
|
}
|
|
// FreeBSD returns EMLINK instead of ELOOP when using O_NOFOLLOW on
|
|
// a symlink.
|
|
Errno::EMLINK if !(nix_all_oflags & OFlag::NOFOLLOW).is_empty() => {
|
|
return Err(Error::ELOOP);
|
|
}
|
|
errno => return Err(errno.into()),
|
|
}
|
|
} else {
|
|
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(unsafe { File::from_raw_fd(new_fd) })
|
|
}
|
|
|
|
pub(crate) fn path_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()) }
|
|
.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) -> Result<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,
|
|
) -> Result<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,
|
|
) -> Result<()> {
|
|
use super::super::filetime::*;
|
|
use std::time::{Duration, UNIX_EPOCH};
|
|
|
|
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(Error::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(),
|
|
resolved.path(),
|
|
atim,
|
|
mtim,
|
|
symlink_nofollow,
|
|
)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub(crate) fn path_remove_directory(resolved: PathGet) -> Result<()> {
|
|
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,
|
|
) -> Result<impl Iterator<Item = Result<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()?,
|
|
})
|
|
}))
|
|
}
|