Make Handle a trait required for any WASI-compatible handle (#1443)

* Make Handle a trait required for any WASI-compatible handle

OK, so this PR is a bit of an experiment that came about somewhat itself
when I was looking at refactoring use of `Rc<RefCell<Descriptor>>` inside
`Entry` struct. I've noticed that since we've placed `VirtualFile` on the
same level as `OsHandle` and `Stdin` etc., we've ended up necessiitating
checks for different combinations such as "is a real OS resource being mixed
up with a virtual resource?", and if that was the case, we'd panic since
this was clearly not allowed (e.g., symlinking, or worse renaming).
Therefore, it seemed natural for virtual file to be on the same level
as _any_ OS handle (regardless of whether it's an actual file, socket,
or stdio handle). In other words, we should ideally envision the following
hierarchy:

```
\-- OsHandle \-- OsFile
              -- Stdio
\-- Virtual
```

This way, we can deal with the mix up at a level above which cleans up
our logic significantly.

On the other hand, when looking through the `virtfs`, the trait approach
to some type that's a valid `Handle` grew on me, and I think this
is the way to go. And this is what this PR is proposing, a trait
`Handle` which features enough functionality to make both virtual and
OS ops to work. Now, inside `Entry` we can safely store something like
`Rc<dyn Handle>` where `Handle` can downcast to either `VirtualFile` or
`VirtualDir`, or `OsHandle` if its an actual OS resource. Note that
I've left `Handle` as one massive trait, but I reckon we could split
it up into several smaller traits, each dealing with some bit of WASI
functionality. I'm hoping this would perhaps make it easier to figure
out polyfilling between snapshots and the new upcoming ephemeral
snapshot since a lot of boilerplate functionality is now done as part
of the `Handle` trait implementation.

Next, I've redone the original `OsHandle` to be an `OsFile` which
now stores a raw descriptor/handle (`RawFd`/`RawHandle`) inside a
`Cell` so that we can handle interior mutability in an easy (read,
non-panicky) way. In order not to lose the perks of derefercing to
`std::fs::File`, I've added a convenience trait `AsFile` which
will take `OsFile` by reference (or the stdio handles) and create
a non-owned `ManuallyDrop<File>` resource which can be passed around
and acted upon the way we'd normally do on `&File`. This change of
course implies that we now have to worry about properly closing all
OS resources stored as part of `OsFile`, thus this type now implements
`Drop` trait which essentially speaking moves the raw descriptor/handle
into a `File` and drops it.

Finally, I've redone setting time info on relative paths on *nix using
the same approach as advocated in the virtual fs. Namely, we do an
`openat` followed by `filestat_set_times` on the obtained descriptor.
This effectively removes the need for custom `filetime` module in
`yanix`. However, this does probably incur additional cost of at least
one additional syscall, and I haven't checked whether this approach
performs as expected on platforms such as NixOS which as far as I remember
had some weirdness todo with linking `utimensat` symbols, etc. Still,
this change is worth considering given that the implementation of
`path_filestat_set_times` cleans up a lot, albeit with some additional
cost.

* Fix tests on Windows

* Address comments plus minor consistency cleanup

* Address comments

* Fix formatting
This commit is contained in:
Jakub Konka
2020-04-09 22:18:19 +02:00
committed by GitHub
parent 6fd0451bc3
commit de919382b3
34 changed files with 1807 additions and 1964 deletions

View File

@@ -1,133 +0,0 @@
use crate::entry::{Descriptor, EntryRights, OsHandleRef};
use crate::wasi::{types, RightsExt};
use std::fs::File;
use std::io;
use std::mem::ManuallyDrop;
use std::ops::Deref;
use std::os::windows::prelude::{AsRawHandle, FromRawHandle, RawHandle};
#[derive(Debug)]
pub(crate) struct OsHandle(File);
impl From<File> for OsHandle {
fn from(file: File) -> Self {
Self(file)
}
}
impl AsRawHandle for OsHandle {
fn as_raw_handle(&self) -> RawHandle {
self.0.as_raw_handle()
}
}
impl Deref for OsHandle {
type Target = File;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRawHandle for Descriptor {
fn as_raw_handle(&self) -> RawHandle {
match self {
Self::OsHandle(file) => file.as_raw_handle(),
Self::VirtualFile(_file) => {
unimplemented!("virtual as_raw_handle");
}
Self::Stdin => io::stdin().as_raw_handle(),
Self::Stdout => io::stdout().as_raw_handle(),
Self::Stderr => io::stderr().as_raw_handle(),
}
}
}
pub(crate) fn descriptor_as_oshandle<'lifetime>(
desc: &'lifetime Descriptor,
) -> OsHandleRef<'lifetime> {
OsHandleRef::new(ManuallyDrop::new(OsHandle::from(unsafe {
File::from_raw_handle(desc.as_raw_handle())
})))
}
/// Returns the set of all possible rights that are both relevant for the file
/// type and consistent with the open mode.
///
/// This function is unsafe because it operates on a raw file descriptor.
pub(crate) unsafe fn determine_type_and_access_rights<Handle: AsRawHandle>(
handle: &Handle,
) -> io::Result<(types::Filetype, EntryRights)> {
use winx::file::{query_access_information, AccessMode};
let (file_type, mut rights) = determine_type_rights(handle)?;
match file_type {
types::Filetype::Directory | types::Filetype::RegularFile => {
let mode = query_access_information(handle.as_raw_handle())?;
if mode.contains(AccessMode::FILE_GENERIC_READ) {
rights.base |= types::Rights::FD_READ;
}
if mode.contains(AccessMode::FILE_GENERIC_WRITE) {
rights.base |= types::Rights::FD_WRITE;
}
}
_ => {
// TODO: is there a way around this? On windows, it seems
// we cannot check access rights for anything but dirs and regular files
}
}
Ok((file_type, rights))
}
/// Returns the set of all possible rights that are relevant for file type.
///
/// This function is unsafe because it operates on a raw file descriptor.
pub(crate) unsafe fn determine_type_rights<Handle: AsRawHandle>(
handle: &Handle,
) -> io::Result<(types::Filetype, EntryRights)> {
let (file_type, rights) = {
let file_type = winx::file::get_file_type(handle.as_raw_handle())?;
let (file_type, base, inheriting) = if file_type.is_char() {
// character file: LPT device or console
// TODO: rule out LPT device
(
types::Filetype::CharacterDevice,
types::Rights::tty_base(),
types::Rights::tty_base(),
)
} else if file_type.is_disk() {
// disk file: file, dir or disk device
let file = std::mem::ManuallyDrop::new(File::from_raw_handle(handle.as_raw_handle()));
let meta = file.metadata()?;
if meta.is_dir() {
(
types::Filetype::Directory,
types::Rights::directory_base(),
types::Rights::directory_inheriting(),
)
} else if meta.is_file() {
(
types::Filetype::RegularFile,
types::Rights::regular_file_base(),
types::Rights::regular_file_inheriting(),
)
} else {
return Err(io::Error::from_raw_os_error(libc::EINVAL));
}
} else if file_type.is_pipe() {
// pipe object: socket, named pipe or anonymous pipe
// TODO: what about pipes, etc?
(
types::Filetype::SocketStream,
types::Rights::socket_base(),
types::Rights::socket_inheriting(),
)
} else {
return Err(io::Error::from_raw_os_error(libc::EINVAL));
};
(file_type, EntryRights::new(base, inheriting))
};
Ok((file_type, rights))
}

View File

@@ -1,6 +1,7 @@
use super::file_serial_no;
use super::oshandle::OsFile;
use crate::path;
use crate::sys::entry::OsHandle;
use crate::sys::oshandle::AsFile;
use crate::wasi::{types, Result};
use log::trace;
use std::convert::TryInto;
@@ -10,9 +11,9 @@ use std::os::windows::prelude::{AsRawHandle, FromRawHandle};
use std::path::Path;
use winx::file::{AccessMode, FileModeInformation, Flags};
pub(crate) fn fdstat_get(fd: &File) -> Result<types::Fdflags> {
pub(crate) fn fdstat_get(file: &File) -> Result<types::Fdflags> {
let mut fdflags = types::Fdflags::empty();
let handle = fd.as_raw_handle();
let handle = file.as_raw_handle();
let access_mode = winx::file::query_access_information(handle)?;
let mode = winx::file::query_mode_information(handle)?;
@@ -34,27 +35,30 @@ pub(crate) fn fdstat_get(fd: &File) -> Result<types::Fdflags> {
Ok(fdflags)
}
pub(crate) fn fdstat_set_flags(fd: &File, fdflags: types::Fdflags) -> Result<Option<OsHandle>> {
let handle = fd.as_raw_handle();
// TODO Investigate further for Stdio handles. `ReOpenFile` requires the file
// handle came from `CreateFile`, but the Rust's libstd will use `GetStdHandle`
// rather than `CreateFile`. Relevant discussion can be found in:
// https://github.com/rust-lang/rust/issues/40490
pub(crate) fn fdstat_set_flags(file: &File, fdflags: types::Fdflags) -> Result<Option<OsFile>> {
let handle = file.as_raw_handle();
let access_mode = winx::file::query_access_information(handle)?;
let new_access_mode = file_access_mode_from_fdflags(
fdflags,
access_mode.contains(AccessMode::FILE_READ_DATA),
access_mode.contains(AccessMode::FILE_WRITE_DATA)
| access_mode.contains(AccessMode::FILE_APPEND_DATA),
);
unsafe {
Ok(Some(OsHandle::from(File::from_raw_handle(
winx::file::reopen_file(handle, new_access_mode, fdflags.into())?,
))))
Ok(Some(OsFile::from_raw_handle(winx::file::reopen_file(
handle,
new_access_mode,
fdflags.into(),
)?)))
}
}
pub(crate) fn advise(
_file: &File,
_file: &OsFile,
_advice: types::Advice,
_offset: types::Filesize,
_len: types::Filesize,
@@ -116,13 +120,13 @@ fn file_access_mode_from_fdflags(fdflags: types::Fdflags, read: bool, write: boo
// .. gets cookie = 2
// other entries, in order they were returned by FindNextFileW get subsequent integers as their cookies
pub(crate) fn readdir(
fd: &File,
file: &OsFile,
cookie: types::Dircookie,
) -> Result<impl Iterator<Item = Result<(types::Dirent, String)>>> {
) -> Result<Box<dyn Iterator<Item = Result<(types::Dirent, String)>>>> {
use winx::file::get_file_path;
let cookie = cookie.try_into()?;
let path = get_file_path(fd)?;
let path = get_file_path(&file.as_file())?;
// std::fs::ReadDir doesn't return . and .., so we need to emulate it
let path = Path::new(&path);
// The directory /.. is the same as / on Unix (at least on ext4), so emulate this behavior too
@@ -155,7 +159,7 @@ pub(crate) fn readdir(
// small host_buf, but this is difficult to implement efficiently.
//
// See https://github.com/WebAssembly/WASI/issues/61 for more details.
Ok(iter.skip(cookie))
Ok(Box::new(iter.skip(cookie)))
}
fn dirent_from_path<P: AsRef<Path>>(
@@ -182,7 +186,7 @@ fn dirent_from_path<P: AsRef<Path>>(
Ok((dirent, name))
}
pub(crate) fn filestat_get(file: &std::fs::File) -> Result<types::Filestat> {
pub(crate) fn filestat_get(file: &File) -> Result<types::Filestat> {
let filestat = file.try_into()?;
Ok(filestat)
}

View File

@@ -1,22 +1,18 @@
pub(crate) mod clock;
pub(crate) mod entry;
pub(crate) mod fd;
pub(crate) mod oshandle;
pub(crate) mod path;
pub(crate) mod poll;
use crate::wasi::{types, Errno, Result};
use std::convert::{TryFrom, TryInto};
use std::fs::{File, OpenOptions};
use std::fs::File;
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use std::{io, string};
use winapi::shared::winerror;
use winx::file::{CreationDisposition, Flags};
pub(crate) fn dev_null() -> io::Result<File> {
OpenOptions::new().read(true).write(true).open("NUL")
}
pub fn preopen_dir<P: AsRef<Path>>(path: P) -> io::Result<File> {
use std::fs::OpenOptions;
use std::os::windows::fs::OpenOptionsExt;

View File

@@ -0,0 +1,169 @@
use crate::entry::EntryRights;
use crate::sys::oshandle::{AsFile, OsHandle, OsHandleExt};
use crate::wasi::{types, RightsExt};
use std::cell::Cell;
use std::fs::{File, OpenOptions};
use std::io;
use std::mem::ManuallyDrop;
use std::os::windows::prelude::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle};
#[derive(Debug)]
pub(crate) struct OsFile(Cell<RawHandle>);
impl OsFile {
/// Consumes `other` taking the ownership of the underlying
/// `RawHandle` file handle.
pub(crate) fn update_from(&self, other: Self) {
let new_handle = other.into_raw_handle();
let old_handle = self.0.get();
self.0.set(new_handle);
// We need to remember to close the old_handle.
unsafe {
File::from_raw_handle(old_handle);
}
}
/// Clones `self`.
pub(crate) fn try_clone(&self) -> io::Result<Self> {
let handle = self.as_file().try_clone()?;
Ok(Self(Cell::new(handle.into_raw_handle())))
}
}
impl Drop for OsFile {
fn drop(&mut self) {
unsafe {
File::from_raw_handle(self.as_raw_handle());
}
}
}
impl AsRawHandle for OsFile {
fn as_raw_handle(&self) -> RawHandle {
self.0.get()
}
}
impl FromRawHandle for OsFile {
unsafe fn from_raw_handle(handle: RawHandle) -> Self {
Self(Cell::new(handle))
}
}
impl IntoRawHandle for OsFile {
fn into_raw_handle(self) -> RawHandle {
// We need to prevent dropping of the OsFile
let wrapped = ManuallyDrop::new(self);
wrapped.0.get()
}
}
impl AsFile for OsFile {
fn as_file(&self) -> ManuallyDrop<File> {
let file = unsafe { File::from_raw_handle(self.0.get()) };
ManuallyDrop::new(file)
}
}
impl AsRawHandle for OsHandle {
fn as_raw_handle(&self) -> RawHandle {
match self {
Self::OsFile(file) => file.as_raw_handle(),
Self::Stdin => io::stdin().as_raw_handle(),
Self::Stdout => io::stdout().as_raw_handle(),
Self::Stderr => io::stderr().as_raw_handle(),
}
}
}
impl AsFile for OsHandle {
fn as_file(&self) -> ManuallyDrop<File> {
let file = unsafe { File::from_raw_handle(self.as_raw_handle()) };
ManuallyDrop::new(file)
}
}
impl From<File> for OsHandle {
fn from(file: File) -> Self {
Self::from(unsafe { OsFile::from_raw_handle(file.into_raw_handle()) })
}
}
impl OsHandleExt for OsHandle {
fn get_file_type(&self) -> io::Result<types::Filetype> {
let file_type = unsafe { winx::file::get_file_type(self.as_raw_handle())? };
let file_type = if file_type.is_char() {
// character file: LPT device or console
// TODO: rule out LPT device
types::Filetype::CharacterDevice
} else if file_type.is_disk() {
// disk file: file, dir or disk device
let file = self.as_file();
let meta = file.metadata()?;
if meta.is_dir() {
types::Filetype::Directory
} else if meta.is_file() {
types::Filetype::RegularFile
} else {
return Err(io::Error::from_raw_os_error(libc::EINVAL));
}
} else if file_type.is_pipe() {
// pipe object: socket, named pipe or anonymous pipe
// TODO: what about pipes, etc?
types::Filetype::SocketStream
} else {
return Err(io::Error::from_raw_os_error(libc::EINVAL));
};
Ok(file_type)
}
fn get_rights(&self, file_type: types::Filetype) -> io::Result<EntryRights> {
use winx::file::{query_access_information, AccessMode};
let (base, inheriting) = match file_type {
types::Filetype::BlockDevice => (
types::Rights::block_device_base(),
types::Rights::block_device_inheriting(),
),
types::Filetype::CharacterDevice => {
(types::Rights::tty_base(), types::Rights::tty_base())
}
types::Filetype::Directory => (
types::Rights::directory_base(),
types::Rights::directory_inheriting(),
),
types::Filetype::RegularFile => (
types::Rights::regular_file_base(),
types::Rights::regular_file_inheriting(),
),
types::Filetype::SocketDgram | types::Filetype::SocketStream => (
types::Rights::socket_base(),
types::Rights::socket_inheriting(),
),
types::Filetype::SymbolicLink | types::Filetype::Unknown => (
types::Rights::regular_file_base(),
types::Rights::regular_file_inheriting(),
),
};
let mut rights = EntryRights::new(base, inheriting);
match file_type {
types::Filetype::Directory | types::Filetype::RegularFile => {
let mode = query_access_information(self.as_raw_handle())?;
if mode.contains(AccessMode::FILE_GENERIC_READ) {
rights.base |= types::Rights::FD_READ;
}
if mode.contains(AccessMode::FILE_GENERIC_WRITE) {
rights.base |= types::Rights::FD_WRITE;
}
}
_ => {
// TODO: is there a way around this? On windows, it seems
// we cannot check access rights for anything but dirs and regular files
}
}
Ok(rights)
}
fn from_null() -> io::Result<Self> {
let file = OpenOptions::new().read(true).write(true).open("NUL")?;
Ok(Self::from(file))
}
}

View File

@@ -1,18 +1,83 @@
use crate::entry::{Descriptor, EntryRights};
use crate::fd;
use crate::path::PathGet;
use crate::sys::entry::OsHandle;
use super::oshandle::OsFile;
use crate::entry::EntryRights;
use crate::sys::oshandle::{AsFile, OsHandle};
use crate::wasi::{types, Errno, Result};
use log::debug;
use std::convert::TryInto;
use std::ffi::{OsStr, OsString};
use std::fs::{File, Metadata, OpenOptions};
use std::fs::{self, Metadata, OpenOptions};
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::os::windows::fs::OpenOptionsExt;
use std::path::{Path, PathBuf};
use winapi::shared::winerror;
use winx::file::AccessMode;
fn strip_trailing_slashes_and_concatenate(dirfd: &OsFile, path: &str) -> Result<Option<PathBuf>> {
if path.ends_with('/') {
let suffix = path.trim_end_matches('/');
concatenate(dirfd, Path::new(suffix)).map(Some)
} else {
Ok(None)
}
}
fn strip_extended_prefix<P: AsRef<OsStr>>(path: P) -> OsString {
let path: Vec<u16> = path.as_ref().encode_wide().collect();
if &[92, 92, 63, 92] == &path[0..4] {
OsString::from_wide(&path[4..])
} else {
OsString::from_wide(&path)
}
}
fn concatenate<P: AsRef<Path>>(file: &OsFile, path: P) -> Result<PathBuf> {
use winx::file::get_file_path;
// WASI is not able to deal with absolute paths
// so error out if absolute
if path.as_ref().is_absolute() {
return Err(Errno::Notcapable);
}
let dir_path = get_file_path(&file.as_file())?;
// concatenate paths
let mut out_path = PathBuf::from(dir_path);
out_path.push(path.as_ref());
// strip extended prefix; otherwise we will error out on any relative
// components with `out_path`
let out_path = PathBuf::from(strip_extended_prefix(out_path));
log::debug!("out_path={:?}", out_path);
Ok(out_path)
}
fn file_access_mode_from_fdflags(fdflags: types::Fdflags, read: bool, write: bool) -> AccessMode {
let mut access_mode = AccessMode::READ_CONTROL;
// We always need `FILE_WRITE_ATTRIBUTES` so that we can set attributes such as filetimes, etc.
access_mode.insert(AccessMode::FILE_WRITE_ATTRIBUTES);
// Note that `GENERIC_READ` and `GENERIC_WRITE` cannot be used to properly support append-only mode
// The file-specific flags `FILE_GENERIC_READ` and `FILE_GENERIC_WRITE` are used here instead
// These flags have the same semantic meaning for file objects, but allow removal of specific permissions (see below)
if read {
access_mode.insert(AccessMode::FILE_GENERIC_READ);
}
if write {
access_mode.insert(AccessMode::FILE_GENERIC_WRITE);
}
// For append, grant the handle FILE_APPEND_DATA access but *not* FILE_WRITE_DATA.
// This makes the handle "append only".
// Changes to the file pointer will be ignored (like POSIX's O_APPEND behavior).
if fdflags.contains(&types::Fdflags::APPEND) {
access_mode.insert(AccessMode::FILE_APPEND_DATA);
access_mode.remove(AccessMode::FILE_WRITE_DATA);
}
access_mode
}
/// Creates owned WASI path from OS string.
///
/// NB WASI spec requires OS string to be valid UTF-8. Otherwise,
@@ -23,24 +88,6 @@ pub(crate) fn from_host<S: AsRef<OsStr>>(s: S) -> Result<String> {
Ok(s)
}
pub(crate) trait PathGetExt {
fn concatenate(&self) -> Result<PathBuf>;
}
impl PathGetExt for PathGet {
fn concatenate(&self) -> Result<PathBuf> {
match self.dirfd() {
Descriptor::OsHandle(file) => concatenate(file, Path::new(self.path())),
Descriptor::VirtualFile(_virt) => {
panic!("concatenate on a virtual base");
}
Descriptor::Stdin | Descriptor::Stdout | Descriptor::Stderr => {
unreachable!("streams do not have paths and should not be accessible via PathGet");
}
}
}
}
pub(crate) fn open_rights(
input_rights: &EntryRights,
oflags: types::Oflags,
@@ -69,30 +116,7 @@ pub(crate) fn open_rights(
EntryRights::new(needed_base, needed_inheriting)
}
pub(crate) fn openat(dirfd: &File, path: &str) -> Result<File> {
use std::fs::OpenOptions;
use std::os::windows::fs::OpenOptionsExt;
use winx::file::Flags;
let path = concatenate(dirfd, Path::new(path))?;
let err = match OpenOptions::new()
.read(true)
.custom_flags(Flags::FILE_FLAG_BACKUP_SEMANTICS.bits())
.open(&path)
{
Ok(file) => return Ok(file),
Err(e) => e,
};
if let Some(code) = err.raw_os_error() {
log::debug!("openat error={:?}", code);
if code as u32 == winerror::ERROR_INVALID_NAME {
return Err(Errno::Notdir);
}
}
Err(err.into())
}
pub(crate) fn readlinkat(dirfd: &File, s_path: &str) -> Result<String> {
pub(crate) fn readlinkat(dirfd: &OsFile, s_path: &str) -> Result<String> {
use winx::file::get_file_path;
let path = concatenate(dirfd, Path::new(s_path))?;
@@ -102,7 +126,7 @@ pub(crate) fn readlinkat(dirfd: &File, s_path: &str) -> Result<String> {
// we need to strip the prefix from the absolute path
// as otherwise we will error out since WASI is not capable
// of dealing with absolute paths
let dir_path = get_file_path(dirfd)?;
let dir_path = get_file_path(&dirfd.as_file())?;
let dir_path = PathBuf::from(strip_extended_prefix(dir_path));
let target_path = target_path
.strip_prefix(dir_path)
@@ -127,54 +151,25 @@ pub(crate) fn readlinkat(dirfd: &File, s_path: &str) -> Result<String> {
Err(err.into())
}
fn strip_extended_prefix<P: AsRef<OsStr>>(path: P) -> OsString {
let path: Vec<u16> = path.as_ref().encode_wide().collect();
if &[92, 92, 63, 92] == &path[0..4] {
OsString::from_wide(&path[4..])
} else {
OsString::from_wide(&path)
}
}
fn concatenate<P: AsRef<Path>>(file: &File, path: P) -> Result<PathBuf> {
use winx::file::get_file_path;
// WASI is not able to deal with absolute paths
// so error out if absolute
if path.as_ref().is_absolute() {
return Err(Errno::Notcapable);
}
let dir_path = get_file_path(file)?;
// concatenate paths
let mut out_path = PathBuf::from(dir_path);
out_path.push(path.as_ref());
// strip extended prefix; otherwise we will error out on any relative
// components with `out_path`
let out_path = PathBuf::from(strip_extended_prefix(out_path));
log::debug!("out_path={:?}", out_path);
Ok(out_path)
}
pub(crate) fn create_directory(file: &File, path: &str) -> Result<()> {
pub(crate) fn create_directory(file: &OsFile, path: &str) -> Result<()> {
let path = concatenate(file, path)?;
std::fs::create_dir(&path)?;
Ok(())
}
pub(crate) fn link(
resolved_old: PathGet,
resolved_new: PathGet,
old_dirfd: &OsFile,
old_path: &str,
new_dirfd: &OsFile,
new_path: &str,
follow_symlinks: bool,
) -> Result<()> {
use std::fs;
let mut old_path = resolved_old.concatenate()?;
let new_path = resolved_new.concatenate()?;
let mut old_path = concatenate(old_dirfd, old_path)?;
let new_path = concatenate(new_dirfd, new_path)?;
if follow_symlinks {
// in particular, this will return an error if the target path doesn't exist
debug!("Following symlinks for path: {:?}", old_path);
log::debug!("Following symlinks for path: {:?}", old_path);
old_path = fs::canonicalize(&old_path).map_err(|e| match e.raw_os_error() {
// fs::canonicalize under Windows will return:
// * ERROR_FILE_NOT_FOUND, if it encounters a dangling symlink
@@ -183,39 +178,32 @@ pub(crate) fn link(
_ => e.into(),
})?;
}
fs::hard_link(&old_path, &new_path).or_else(|err| {
match err.raw_os_error() {
Some(code) => {
debug!("path_link at fs::hard_link error code={:?}", code);
match code as u32 {
winerror::ERROR_ACCESS_DENIED => {
// If an attempt is made to create a hard link to a directory, POSIX-compliant
// implementations of link return `EPERM`, but `ERROR_ACCESS_DENIED` is converted
// to `EACCES`. We detect and correct this case here.
if fs::metadata(&old_path).map(|m| m.is_dir()).unwrap_or(false) {
return Err(Errno::Perm);
}
}
_ => {}
}
Err(err.into())
}
None => {
log::debug!("Inconvertible OS error: {}", err);
Err(Errno::Io)
let err = match fs::hard_link(&old_path, &new_path) {
Ok(()) => return Ok(()),
Err(e) => e,
};
if let Some(code) = err.raw_os_error() {
log::debug!("path_link at fs::hard_link error code={:?}", code);
if code as u32 == winerror::ERROR_ACCESS_DENIED {
// If an attempt is made to create a hard link to a directory, POSIX-compliant
// implementations of link return `EPERM`, but `ERROR_ACCESS_DENIED` is converted
// to `EACCES`. We detect and correct this case here.
if fs::metadata(&old_path).map(|m| m.is_dir()).unwrap_or(false) {
return Err(Errno::Perm);
}
}
})
}
Err(err.into())
}
pub(crate) fn open(
resolved: PathGet,
dirfd: &OsFile,
path: &str,
read: bool,
write: bool,
oflags: types::Oflags,
fdflags: types::Fdflags,
) -> Result<Descriptor> {
) -> Result<OsHandle> {
use winx::file::{AccessMode, CreationDisposition, Flags};
let is_trunc = oflags.contains(&types::Oflags::TRUNC);
@@ -245,9 +233,7 @@ pub(crate) fn open(
}
_ => {}
}
let path = resolved.concatenate()?;
let path = concatenate(dirfd, path)?;
match path.symlink_metadata().map(|metadata| metadata.file_type()) {
Ok(file_type) => {
// check if we are trying to open a symlink
@@ -262,12 +248,18 @@ pub(crate) fn open(
Err(err) => match err.raw_os_error() {
Some(code) => {
log::debug!("path_open at symlink_metadata error code={:?}", code);
if code as u32 != winerror::ERROR_FILE_NOT_FOUND {
return Err(err.into());
}
// file not found, let it proceed to actually
// trying to open it
match code as u32 {
winerror::ERROR_FILE_NOT_FOUND => {
// file not found, let it proceed to actually
// trying to open it
}
winerror::ERROR_INVALID_NAME => {
// TODO rethink this. For now, migrate how we handled
// it in `path::openat` on Windows.
return Err(Errno::Notdir);
}
_ => return Err(err.into()),
};
}
None => {
log::debug!("Inconvertible OS error: {}", err);
@@ -284,49 +276,25 @@ pub(crate) fn open(
}
let flags: Flags = fdflags.into();
opts.access_mode(access_mode.bits())
let file = opts
.access_mode(access_mode.bits())
.custom_flags(flags.bits())
.open(&path)
.map(|f| OsHandle::from(f).into())
.map_err(Into::into)
.open(&path)?;
let handle = OsHandle::from(file);
Ok(handle)
}
fn file_access_mode_from_fdflags(fdflags: types::Fdflags, read: bool, write: bool) -> AccessMode {
let mut access_mode = AccessMode::READ_CONTROL;
// Note that `GENERIC_READ` and `GENERIC_WRITE` cannot be used to properly support append-only mode
// The file-specific flags `FILE_GENERIC_READ` and `FILE_GENERIC_WRITE` are used here instead
// These flags have the same semantic meaning for file objects, but allow removal of specific permissions (see below)
if read {
access_mode.insert(AccessMode::FILE_GENERIC_READ);
}
if write {
access_mode.insert(AccessMode::FILE_GENERIC_WRITE);
}
// For append, grant the handle FILE_APPEND_DATA access but *not* FILE_WRITE_DATA.
// This makes the handle "append only".
// Changes to the file pointer will be ignored (like POSIX's O_APPEND behavior).
if fdflags.contains(&types::Fdflags::APPEND) {
access_mode.insert(AccessMode::FILE_APPEND_DATA);
access_mode.remove(AccessMode::FILE_WRITE_DATA);
}
access_mode
}
pub(crate) fn readlink(resolved: PathGet, buf: &mut [u8]) -> Result<usize> {
pub(crate) fn readlink(dirfd: &OsFile, path: &str, buf: &mut [u8]) -> Result<usize> {
use winx::file::get_file_path;
let path = resolved.concatenate()?;
let path = concatenate(dirfd, path)?;
let target_path = path.read_link()?;
// since on Windows we are effectively emulating 'at' syscalls
// we need to strip the prefix from the absolute path
// as otherwise we will error out since WASI is not capable
// of dealing with absolute paths
let dir_path = get_file_path(&resolved.dirfd().as_os_handle())?;
let dir_path = get_file_path(&dirfd.as_file())?;
let dir_path = PathBuf::from(strip_extended_prefix(dir_path));
let target_path = target_path
.strip_prefix(dir_path)
@@ -353,20 +321,16 @@ pub(crate) fn readlink(resolved: PathGet, buf: &mut [u8]) -> Result<usize> {
}
}
fn strip_trailing_slashes_and_concatenate(resolved: &PathGet) -> Result<Option<PathBuf>> {
if resolved.path().ends_with('/') {
let suffix = resolved.path().trim_end_matches('/');
concatenate(&resolved.dirfd().as_os_handle(), Path::new(suffix)).map(Some)
} else {
Ok(None)
}
}
pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
pub(crate) fn rename(
old_dirfd: &OsFile,
old_path_: &str,
new_dirfd: &OsFile,
new_path_: &str,
) -> Result<()> {
use std::fs;
let old_path = resolved_old.concatenate()?;
let new_path = resolved_new.concatenate()?;
let old_path = concatenate(old_dirfd, old_path_)?;
let new_path = concatenate(new_dirfd, new_path_)?;
// First sanity check: check we're not trying to rename dir to file or vice versa.
// NB on Windows, the former is actually permitted [std::fs::rename].
@@ -377,7 +341,7 @@ pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()>
}
// Second sanity check: check we're not trying to rename a file into a path
// ending in a trailing slash.
if old_path.is_file() && resolved_new.path().ends_with('/') {
if old_path.is_file() && new_path_.ends_with('/') {
return Err(Errno::Notdir);
}
@@ -406,7 +370,9 @@ pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()>
winerror::ERROR_INVALID_NAME => {
// If source contains trailing slashes, check if we are dealing with
// a file instead of a dir, and if so, throw ENOTDIR.
if let Some(path) = strip_trailing_slashes_and_concatenate(&resolved_old)? {
if let Some(path) =
strip_trailing_slashes_and_concatenate(old_dirfd, old_path_)?
{
if path.is_file() {
return Err(Errno::Notdir);
}
@@ -424,38 +390,11 @@ pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()>
}
}
pub(crate) fn filestat_get(
resolved: PathGet,
_dirflags: types::Lookupflags,
) -> Result<types::Filestat> {
let path = resolved.concatenate()?;
let file = File::open(path)?;
let filestat = (&file).try_into()?;
Ok(filestat)
}
pub(crate) fn filestat_set_times(
resolved: PathGet,
_dirflags: types::Lookupflags,
st_atim: types::Timestamp,
st_mtim: types::Timestamp,
fst_flags: types::Fstflags,
) -> Result<()> {
use winx::file::AccessMode;
let path = resolved.concatenate()?;
let file = OpenOptions::new()
.access_mode(AccessMode::FILE_WRITE_ATTRIBUTES.bits())
.open(path)?;
let modifiable_fd = Descriptor::OsHandle(OsHandle::from(file));
fd::filestat_set_times_impl(&modifiable_fd, st_atim, st_mtim, fst_flags)
}
pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> {
use std::fs;
pub(crate) fn symlink(old_path: &str, new_dirfd: &OsFile, new_path_: &str) -> Result<()> {
use std::os::windows::fs::{symlink_dir, symlink_file};
let old_path = concatenate(&resolved.dirfd().as_os_handle(), Path::new(old_path))?;
let new_path = resolved.concatenate()?;
let old_path = concatenate(new_dirfd, Path::new(old_path))?;
let new_path = concatenate(new_dirfd, new_path_)?;
// Windows distinguishes between file and directory symlinks.
// If the source doesn't exist or is an exotic file type, we fall back
@@ -488,7 +427,9 @@ pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> {
//
// Since POSIX will return EEXIST in such case, we simulate this behavior
winerror::ERROR_INVALID_NAME => {
if let Some(path) = strip_trailing_slashes_and_concatenate(&resolved)? {
if let Some(path) =
strip_trailing_slashes_and_concatenate(new_dirfd, new_path_)?
{
if path.exists() {
return Err(Errno::Exist);
}
@@ -506,10 +447,10 @@ pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> {
}
}
pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> {
pub(crate) fn unlink_file(dirfd: &OsFile, path: &str) -> Result<()> {
use std::fs;
let path = resolved.concatenate()?;
let path = concatenate(dirfd, path)?;
let file_type = path
.symlink_metadata()
.map(|metadata| metadata.file_type())?;
@@ -548,7 +489,7 @@ pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> {
}
}
pub(crate) fn remove_directory(resolved: PathGet) -> Result<()> {
let path = resolved.concatenate()?;
pub(crate) fn remove_directory(dirfd: &OsFile, path: &str) -> Result<()> {
let path = concatenate(dirfd, path)?;
std::fs::remove_dir(&path).map_err(Into::into)
}

View File

@@ -1,11 +1,11 @@
use crate::entry::Descriptor;
use super::super::oshandle::OsHandle;
use crate::poll::{ClockEventData, FdEventData};
use crate::sys::oshandle::AsFile;
use crate::wasi::{types, Errno, Result};
use lazy_static::lazy_static;
use log::{debug, error, trace, warn};
use std::convert::TryInto;
use std::os::windows::io::AsRawHandle;
use std::rc::Rc;
use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError};
use std::sync::Mutex;
use std::thread;
@@ -141,10 +141,18 @@ fn handle_timeout_event(timeout_event: ClockEventData, events: &mut Vec<types::E
}
fn handle_rw_event(event: FdEventData, out_events: &mut Vec<types::Event>) {
let size = match &*event.descriptor.borrow() {
Descriptor::OsHandle(os_handle) => {
let handle = event
.handle
.as_any()
.downcast_ref::<OsHandle>()
.expect("can poll FdEvent for OS resources only");
let size = match handle {
OsHandle::OsFile(file) => {
if event.r#type == types::Eventtype::FdRead {
os_handle.metadata().map(|m| m.len()).map_err(Into::into)
file.as_file()
.metadata()
.map(|m| m.len())
.map_err(Into::into)
} else {
// The spec is unclear what nbytes should actually be for __WASI_EVENTTYPE_FD_WRITE and
// the implementation on Unix just returns 0 here, so it's probably fine
@@ -154,12 +162,9 @@ fn handle_rw_event(event: FdEventData, out_events: &mut Vec<types::Event>) {
}
}
// We return the only universally correct lower bound, see the comment later in the function.
Descriptor::Stdin => Ok(1),
OsHandle::Stdin => Ok(1),
// On Unix, ioctl(FIONREAD) will return 0 for stdout/stderr. Emulate the same behavior on Windows.
Descriptor::Stdout | Descriptor::Stderr => Ok(0),
Descriptor::VirtualFile(_) => {
panic!("virtual files do not get rw events");
}
OsHandle::Stdout | OsHandle::Stderr => Ok(0),
};
let new_event = make_rw_event(&event, size);
@@ -201,21 +206,21 @@ pub(crate) fn oneoff(
let mut pipe_events = vec![];
for event in fd_events {
let descriptor = Rc::clone(&event.descriptor);
match &*descriptor.borrow() {
Descriptor::Stdin if event.r#type == types::Eventtype::FdRead => {
stdin_events.push(event)
}
let handle = event
.handle
.as_any()
.downcast_ref::<OsHandle>()
.expect("can poll FdEvent for OS resources only");
match handle {
OsHandle::Stdin if event.r#type == types::Eventtype::FdRead => stdin_events.push(event),
// stdout/stderr are always considered ready to write because there seems to
// be no way of checking if a write to stdout would block.
//
// If stdin is polled for anything else then reading, then it is also
// considered immediately ready, following the behavior on Linux.
Descriptor::Stdin | Descriptor::Stderr | Descriptor::Stdout => {
immediate_events.push(event)
}
Descriptor::OsHandle(os_handle) => {
let ftype = unsafe { winx::file::get_file_type(os_handle.as_raw_handle()) }?;
OsHandle::Stdin | OsHandle::Stderr | OsHandle::Stdout => immediate_events.push(event),
OsHandle::OsFile(file) => {
let ftype = unsafe { winx::file::get_file_type(file.as_raw_handle()) }?;
if ftype.is_unknown() || ftype.is_char() {
debug!("poll_oneoff: unsupported file type: {:?}", ftype);
handle_error_event(event, Errno::Notsup, events);
@@ -227,9 +232,6 @@ pub(crate) fn oneoff(
unreachable!();
}
}
Descriptor::VirtualFile(_) => {
panic!("virtual files do not get rw events");
}
};
}