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,131 @@
use crate::path::PathGet;
use crate::wasi::{Errno, Result};
use std::os::unix::prelude::AsRawFd;
pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> {
use yanix::file::{unlinkat, AtFlag};
match unsafe {
unlinkat(
resolved.dirfd().as_raw_fd(),
resolved.path(),
AtFlag::empty(),
)
} {
Err(err) => {
let raw_errno = err.raw_os_error().unwrap();
// Non-Linux implementations may return EPERM when attempting to remove a
// directory without REMOVEDIR. While that's what POSIX specifies, it's
// less useful. Adjust this to EISDIR. It doesn't matter that this is not
// atomic with the unlinkat, because if the file is removed and a directory
// is created before fstatat sees it, we're racing with that change anyway
// and unlinkat could have legitimately seen the directory if the race had
// turned out differently.
use yanix::file::{fstatat, FileType};
if raw_errno == libc::EPERM {
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::Directory {
return Err(Errno::Isdir);
}
}
Err(err) => {
log::debug!("path_unlink_file fstatat error: {:?}", err);
}
}
}
Err(err.into())
}
Ok(()) => Ok(()),
}
}
pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> {
use yanix::file::{fstatat, symlinkat, AtFlag};
log::debug!("path_symlink old_path = {:?}", old_path);
log::debug!("path_symlink resolved = {:?}", resolved);
match unsafe { symlinkat(old_path, resolved.dirfd().as_raw_fd(), resolved.path()) } {
Err(err) => {
if err.raw_os_error().unwrap() == libc::ENOTDIR {
// On BSD, symlinkat returns ENOTDIR when it should in fact
// return a EEXIST. It seems that it gets confused with by
// the trailing slash in the target path. Thus, we strip
// the trailing slash and check if the path exists, and
// adjust the error code appropriately.
let new_path = resolved.path().trim_end_matches('/');
match unsafe {
fstatat(
resolved.dirfd().as_raw_fd(),
new_path,
AtFlag::SYMLINK_NOFOLLOW,
)
} {
Ok(_) => return Err(Errno::Exist),
Err(err) => {
log::debug!("path_symlink fstatat error: {:?}", err);
}
}
}
Err(err.into())
}
Ok(()) => Ok(()),
}
}
pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
use yanix::file::{fstatat, renameat, AtFlag};
match unsafe {
renameat(
resolved_old.dirfd().as_raw_fd(),
resolved_old.path(),
resolved_new.dirfd().as_raw_fd(),
resolved_new.path(),
)
} {
Err(err) => {
// Currently, this is verified to be correct on macOS, where
// ENOENT can be returned in case when we try to rename a file
// into a name with a trailing slash. On macOS, if the latter does
// not exist, an ENOENT is thrown, whereas on Linux we observe the
// correct behaviour of throwing an ENOTDIR since the destination is
// indeed not a directory.
//
// TODO
// Verify on other BSD-based OSes.
if err.raw_os_error().unwrap() == libc::ENOENT {
// check if the source path exists
match unsafe {
fstatat(
resolved_old.dirfd().as_raw_fd(),
resolved_old.path(),
AtFlag::SYMLINK_NOFOLLOW,
)
} {
Ok(_) => {
// check if destination contains a trailing slash
if resolved_new.path().contains('/') {
return Err(Errno::Notdir);
} else {
return Err(Errno::Noent);
}
}
Err(err) => {
log::debug!("path_rename fstatat error: {:?}", err);
}
}
}
Err(err.into())
}
Ok(()) => Ok(()),
}
}