* 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
171 lines
6.3 KiB
Rust
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())
|
|
}
|
|
}
|