* Introduce strongly-typed system primitives This commit does a lot of reshuffling and even some more. It introduces strongly-typed system primitives which are: `OsFile`, `OsDir`, `Stdio`, and `OsOther`. Those primitives are separate structs now, each implementing a subset of `Handle` methods, rather than all being an enumeration of some supertype such as `OsHandle`. To summarise the structs: * `OsFile` represents a regular file, and implements fd-ops of `Handle` trait * `OsDir` represents a directory, and primarily implements path-ops, plus `readdir` and some common fd-ops such as `fdstat`, etc. * `Stdio` represents a stdio handle, and implements a subset of fd-ops such as `fdstat` _and_ `read_` and `write_vectored` calls * `OsOther` currently represents anything else and implements a set similar to that implemented by `Stdio` This commit is effectively an experiment and an excercise into better understanding what's going on for each OS resource/type under-the-hood. It's meant to give us some intuition in order to move on with the idea of having strongly-typed handles in WASI both in the syscall impl as well as at the libc level. Some more minor changes include making `OsHandle` represent an OS-specific wrapper for a raw OS handle (Unix fd or Windows handle). Also, since `OsDir` is tricky across OSes, we also have a supertype of `OsHandle` called `OsDirHandle` which may store a `DIR*` stream pointer (mainly BSD). Last but not least, the `Filetype` and `Rights` are now computed when the resource is created, rather than every time we call `Handle::get_file_type` and `Handle::get_rights`. Finally, in order to facilitate the latter, I've converted `EntryRights` into `HandleRights` and pushed them into each `Handle` implementor. * Do not adjust rights on Stdio * Clean up testing for TTY and escaping writes * Implement AsFile for dyn Handle This cleans up a lot of repeating boilerplate code todo with dynamic dispatch. * Delegate definition of OsDir to OS-specific modules Delegates defining `OsDir` struct to OS-specific modules (BSD, Linux, Emscripten, Windows). This way, `OsDir` can safely re-use `OsHandle` for raw OS handle storage, and can store some aux data such as an initialized stream ptr in case of BSD. As a result, we can safely get rid of `OsDirHandle` which IMHO was causing unnecessary noise and overcomplicating the design. On the other hand, delegating definition of `OsDir` to OS-specific modules isn't super clean in and of itself either. Perhaps there's a better way of handling this? * Check if filetype of OS handle matches WASI filetype when creating It seems prudent to check if the passed in `File` instance is of type matching that of the requested WASI filetype. In other words, we'd like to avoid situations where `OsFile` is created from a pipe. * Make AsFile fallible Return `EBADF` in `AsFile` in case a `Handle` cannot be made into a `std::fs::File`. * Remove unnecessary as_file conversion * Remove unnecessary check for TTY for Stdio handle type * Fix incorrect stdio ctors on Unix * Split Stdio into three separate types: Stdin, Stdout, Stderr * Rename PendingEntry::File to PendingEntry::OsHandle to avoid confusion * Rename OsHandle to RawOsHandle Also, since `RawOsHandle` on *nix doesn't need interior mutability wrt the inner raw file descriptor, we can safely swap the `RawFd` for `File` instance. * Add docs explaining what OsOther is * Allow for stdio to be non-character-device (e.g., piped) * Return error on bad preopen rather than panic
119 lines
4.5 KiB
Rust
119 lines
4.5 KiB
Rust
use crate::sys::osdir::OsDir;
|
|
use crate::wasi::{Errno, Result};
|
|
use std::os::unix::prelude::AsRawFd;
|
|
|
|
pub(crate) fn unlink_file(dirfd: &OsDir, path: &str) -> Result<()> {
|
|
use yanix::file::{unlinkat, AtFlag};
|
|
match unsafe { unlinkat(dirfd.as_raw_fd(), 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(dirfd.as_raw_fd(), 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, new_dirfd: &OsDir, new_path: &str) -> Result<()> {
|
|
use yanix::file::{fstatat, symlinkat, AtFlag};
|
|
|
|
log::debug!("path_symlink old_path = {:?}", old_path);
|
|
log::debug!(
|
|
"path_symlink (new_dirfd, new_path) = ({:?}, {:?})",
|
|
new_dirfd,
|
|
new_path
|
|
);
|
|
|
|
match unsafe { symlinkat(old_path, new_dirfd.as_raw_fd(), new_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 = new_path.trim_end_matches('/');
|
|
match unsafe { fstatat(new_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(
|
|
old_dirfd: &OsDir,
|
|
old_path: &str,
|
|
new_dirfd: &OsDir,
|
|
new_path: &str,
|
|
) -> Result<()> {
|
|
use yanix::file::{fstatat, renameat, AtFlag};
|
|
match unsafe {
|
|
renameat(
|
|
old_dirfd.as_raw_fd(),
|
|
old_path,
|
|
new_dirfd.as_raw_fd(),
|
|
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(old_dirfd.as_raw_fd(), old_path, AtFlag::SYMLINK_NOFOLLOW) }
|
|
{
|
|
Ok(_) => {
|
|
// check if destination contains a trailing slash
|
|
if 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(()),
|
|
}
|
|
}
|