Files
wasmtime/crates/wasi-common/src/sys/unix/bsd/hostcalls_impl.rs
Jakub Konka 51f880f625 Add yanix crate and replace nix with yanix in wasi-common (#649)
* Add yanix crate

This commit adds `yanix` crate as a Unix dependency for `wasi-common`.
`yanix` stands for Yet Another Nix crate and is exactly what the name
suggests: a crate in the spirit of the `nix` crate, but which takes a different
approach, using lower-level interfaces with less abstraction, so that it fits
better with its main use case, implementation of WASI syscalls.

* Replace nix with yanix crate

Having introduced `yanix` crate as an in-house replacement for the
`nix` crate, this commit makes the necessary changes to `wasi-common`
to depend _only_ on `yanix` crate.

* Address review comments

* make `fd_dup` unsafe
* rename `get_fd` to `get_fd_flags`, etc.
* reuse `io::Error::last_os_error()` to get the last errno value

* Address more comments

* make all `fcntl` fns unsafe
* adjust `wasi-common` impl appropriately

* Make all fns operating on RawFd unsafe

* Fix linux build

* Address more comments
2019-12-08 16:40:05 -08:00

171 lines
6.3 KiB
Rust

use crate::hostcalls_impl::PathGet;
use crate::sys::host_impl;
use crate::{Error, Result};
use std::os::unix::prelude::AsRawFd;
pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> {
use yanix::{
file::{unlinkat, AtFlag},
Errno, YanixError,
};
unsafe {
unlinkat(
resolved.dirfd().as_raw_fd(),
resolved.path(),
AtFlag::empty(),
)
}
.map_err(|err| {
if let YanixError::Errno(mut errno) = err {
// 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, SFlag};
if errno == Errno::EPERM {
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::IFDIR) {
errno = Errno::EISDIR;
}
} else {
errno = Errno::last();
}
}
errno.into()
} else {
err
}
})
.map_err(Into::into)
}
pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> Result<()> {
use yanix::{
file::{fstatat, symlinkat, AtFlag},
Errno, YanixError,
};
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()) }.or_else(|err| {
if let YanixError::Errno(errno) = err {
match errno {
Errno::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('/');
if let Ok(_) = unsafe {
fstatat(
resolved.dirfd().as_raw_fd(),
new_path,
AtFlag::SYMLINK_NOFOLLOW,
)
} {
Err(Error::EEXIST)
} else {
Err(Error::ENOTDIR)
}
}
x => Err(host_impl::errno_from_nix(x)),
}
} else {
Err(err.into())
}
})
}
pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
use yanix::{
file::{fstatat, renameat, AtFlag},
Errno, YanixError,
};
unsafe {
renameat(
resolved_old.dirfd().as_raw_fd(),
resolved_old.path(),
resolved_new.dirfd().as_raw_fd(),
resolved_new.path(),
)
}
.or_else(|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 let YanixError::Errno(errno) = err {
match errno {
Errno::ENOENT => {
// check if the source path exists
if let Ok(_) = unsafe {
fstatat(
resolved_old.dirfd().as_raw_fd(),
resolved_old.path(),
AtFlag::SYMLINK_NOFOLLOW,
)
} {
// check if destination contains a trailing slash
if resolved_new.path().contains('/') {
Err(Error::ENOTDIR)
} else {
Err(Error::ENOENT)
}
} else {
Err(Error::ENOENT)
}
}
x => Err(host_impl::errno_from_nix(x)),
}
} else {
Err(err.into())
}
})
}
pub(crate) mod fd_readdir_impl {
use crate::sys::fdentry_impl::OsHandle;
use crate::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())
}
}