Add enough Windows functionality to run WASI tutorial (#22)

* Add partial impl of determine_type_rights fn

* Add draft of fd_fdstat_get hostcall

* Add writev wrapper for writing IoVec in RawHandle

* Move IoVec and writev to separate helper crate

* Add Win error handling

Clean up closing and duplicating RawHandle

* Wrap Win file type result

* Add draft impl of fd_close and fd_read

* Refactor getting file access rights

* Remove winapi from the main Cargo.toml

* Add very rough draft of open_path (very incomplete)

* Clean up WinError with macro

* Ignore dir handle in openat if path absolute

* Decode oflags and advance open_path hostcall

* Clean up AccessRight and FlagsAndAttributes flags

* Implement path_get (without symlink expansion yet!)

* Add ShareMode and fix path_get for nested paths

* Add some error mappings between Win and WASI

* Clean up fdflags conversions

* Fix sharing violation when calling openat at '.'

* Apply Alex's fix of using ManuallyDrop instead forget

* Clean up

* Explicitly specify workspace to avoid comp errors at tests
This commit is contained in:
Jakub Konka
2019-06-28 02:10:15 +02:00
committed by Dan Gohman
parent 22c69f46f9
commit 7287767a3f
12 changed files with 1178 additions and 125 deletions

View File

@@ -1,12 +1,9 @@
use super::host_impl;
use crate::host;
use std::fs::File;
use std::os::windows::prelude::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle};
use std::path::PathBuf;
use winapi::shared::minwindef::FALSE;
use winapi::um::handleapi::DuplicateHandle;
use winapi::um::processthreadsapi::GetCurrentProcess;
use winapi::um::winnt::DUPLICATE_SAME_ACCESS;
#[derive(Clone, Debug)]
pub struct FdObject {
@@ -27,12 +24,8 @@ pub struct FdEntry {
impl Drop for FdObject {
fn drop(&mut self) {
if self.needs_close {
unsafe {
if winapi::um::handleapi::CloseHandle(self.raw_handle) == 0 {
// TODO: use DWORD WINAPI GetLastError(void) to get error
eprintln!("FdObject::drop(): couldn't close raw Handle");
}
}
winx::handle::close(self.raw_handle)
.unwrap_or_else(|e| eprintln!("FdObject::drop(): {}", e))
}
}
}
@@ -43,37 +36,30 @@ impl FdEntry {
}
pub fn duplicate<F: AsRawHandle>(fd: &F) -> Self {
unsafe {
let source = fd.as_raw_handle();
let mut dest = 0 as RawHandle;
let cur_proc = GetCurrentProcess();
if DuplicateHandle(
cur_proc,
source,
cur_proc,
&mut dest,
0, // dwDesiredAccess; this flag is ignored if DUPLICATE_SAME_ACCESS is specified
FALSE,
DUPLICATE_SAME_ACCESS,
) == FALSE
{
panic!("Couldn't duplicate handle");
}
Self::from_raw_handle(dest)
}
unsafe { Self::from_raw_handle(winx::handle::dup(fd.as_raw_handle()).unwrap()) }
}
}
impl FromRawHandle for FdEntry {
// TODO: implement
unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self {
let (ty, rights_base, rights_inheriting) = (
host::__WASI_FILETYPE_REGULAR_FILE,
host::RIGHTS_REGULAR_FILE_BASE,
host::RIGHTS_REGULAR_FILE_INHERITING,
);
use winx::file::{get_file_access_rights, AccessRight};
let (ty, mut rights_base, rights_inheriting) =
determine_type_rights(raw_handle).expect("can determine type rights");
if ty != host::__WASI_FILETYPE_CHARACTER_DEVICE {
// TODO: is there a way around this? On windows, it seems
// we cannot check access rights for stdout/in handles
let rights =
get_file_access_rights(raw_handle).expect("can determine file access rights");
let rights = AccessRight::from_bits_truncate(rights);
if rights.contains(AccessRight::FILE_GENERIC_READ) {
rights_base |= host::__WASI_RIGHT_FD_READ;
}
if rights.contains(AccessRight::FILE_GENERIC_WRITE) {
rights_base |= host::__WASI_RIGHT_FD_WRITE;
}
}
Self {
fd_object: FdObject {
@@ -87,3 +73,57 @@ impl FromRawHandle for FdEntry {
}
}
}
pub unsafe fn determine_type_rights(
raw_handle: RawHandle,
) -> Result<
(
host::__wasi_filetype_t,
host::__wasi_rights_t,
host::__wasi_rights_t,
),
host::__wasi_errno_t,
> {
let (ty, rights_base, rights_inheriting) = {
let file_type = winx::file::get_file_type(raw_handle).map_err(host_impl::errno_from_win)?;
if file_type.is_char() {
// character file: LPT device or console
// TODO: rule out LPT device
(
host::__WASI_FILETYPE_CHARACTER_DEVICE,
host::RIGHTS_TTY_BASE,
host::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(raw_handle));
let meta = file.metadata().map_err(|_| host::__WASI_EINVAL)?;
if meta.is_dir() {
(
host::__WASI_FILETYPE_DIRECTORY,
host::RIGHTS_DIRECTORY_BASE,
host::RIGHTS_DIRECTORY_INHERITING,
)
} else if meta.is_file() {
(
host::__WASI_FILETYPE_REGULAR_FILE,
host::RIGHTS_REGULAR_FILE_BASE,
host::RIGHTS_REGULAR_FILE_INHERITING,
)
} else {
return Err(host::__WASI_EINVAL);
}
} else if file_type.is_pipe() {
// pipe object: socket, named pipe or anonymous pipe
// TODO: what about pipes, etc?
(
host::__WASI_FILETYPE_SOCKET_STREAM,
host::RIGHTS_SOCKET_BASE,
host::RIGHTS_SOCKET_INHERITING,
)
} else {
return Err(host::__WASI_EINVAL);
}
};
Ok((ty, rights_base, rights_inheriting))
}

View File

@@ -8,81 +8,125 @@ use std::ffi::{OsStr, OsString};
use std::marker::PhantomData;
use std::os::windows::prelude::{OsStrExt, OsStringExt};
use std::slice;
use winapi::shared::{ntdef, ws2def};
// these will be obsolete once https://github.com/rust-lang/rust/pull/60334
// lands in stable
pub struct IoVec<'a> {
vec: ws2def::WSABUF,
_p: PhantomData<&'a [u8]>,
}
pub struct IoVecMut<'a> {
vec: ws2def::WSABUF,
_p: PhantomData<&'a mut [u8]>,
}
impl<'a> IoVec<'a> {
#[inline]
pub fn new(buf: &'a [u8]) -> Self {
assert!(buf.len() <= ntdef::ULONG::max_value() as usize);
Self {
vec: ws2def::WSABUF {
len: buf.len() as ntdef::ULONG,
buf: buf.as_ptr() as *mut u8 as *mut ntdef::CHAR,
},
_p: PhantomData,
}
}
#[inline]
pub fn as_slice(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.vec.buf as *mut u8, self.vec.len as usize) }
pub fn errno_from_win(error: winx::winerror::WinError) -> host::__wasi_errno_t {
// TODO: implement error mapping between Windows and WASI
use winx::winerror::WinError::*;
match error {
ERROR_SUCCESS => host::__WASI_ESUCCESS,
ERROR_BAD_ENVIRONMENT => host::__WASI_E2BIG,
ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND => host::__WASI_ENOENT,
ERROR_TOO_MANY_OPEN_FILES => host::__WASI_ENFILE,
ERROR_ACCESS_DENIED | ERROR_SHARING_VIOLATION => host::__WASI_EACCES,
ERROR_INVALID_HANDLE | ERROR_INVALID_NAME => host::__WASI_EBADF,
ERROR_NOT_ENOUGH_MEMORY | ERROR_OUTOFMEMORY => host::__WASI_ENOMEM,
ERROR_DIR_NOT_EMPTY => host::__WASI_ENOTEMPTY,
ERROR_DEV_NOT_EXIST => host::__WASI_ENODEV,
ERROR_NOT_READY | ERROR_BUSY => host::__WASI_EBUSY,
ERROR_NOT_SUPPORTED => host::__WASI_ENOTSUP,
ERROR_FILE_EXISTS => host::__WASI_EEXIST,
ERROR_BROKEN_PIPE => host::__WASI_EPIPE,
ERROR_BUFFER_OVERFLOW => host::__WASI_ENAMETOOLONG,
ERROR_DISK_FULL => host::__WASI_ENOSPC,
ERROR_SHARING_BUFFER_EXCEEDED => host::__WASI_ENFILE,
_ => host::__WASI_ENOTSUP,
}
}
impl<'a> IoVecMut<'a> {
#[inline]
pub fn new(buf: &'a mut [u8]) -> Self {
assert!(buf.len() <= ntdef::ULONG::max_value() as usize);
Self {
vec: ws2def::WSABUF {
len: buf.len() as ntdef::ULONG,
buf: buf.as_mut_ptr() as *mut u8 as *mut ntdef::CHAR,
},
_p: PhantomData,
}
}
#[inline]
pub fn as_slice(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.vec.buf as *mut u8, self.vec.len as usize) }
}
#[inline]
pub fn as_mut_slice(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.vec.buf as *mut u8, self.vec.len as usize) }
}
}
pub unsafe fn ciovec_to_win<'a>(ciovec: &'a host::__wasi_ciovec_t) -> IoVec<'a> {
pub unsafe fn ciovec_to_win<'a>(ciovec: &'a host::__wasi_ciovec_t) -> winx::io::IoVec<'a> {
let slice = slice::from_raw_parts(ciovec.buf as *const u8, ciovec.buf_len);
IoVec::new(slice)
winx::io::IoVec::new(slice)
}
pub unsafe fn ciovec_to_win_mut<'a>(ciovec: &'a mut host::__wasi_ciovec_t) -> IoVecMut<'a> {
pub unsafe fn ciovec_to_win_mut<'a>(
ciovec: &'a mut host::__wasi_ciovec_t,
) -> winx::io::IoVecMut<'a> {
let slice = slice::from_raw_parts_mut(ciovec.buf as *mut u8, ciovec.buf_len);
IoVecMut::new(slice)
winx::io::IoVecMut::new(slice)
}
pub unsafe fn iovec_to_win<'a>(iovec: &'a host::__wasi_iovec_t) -> IoVec<'a> {
pub unsafe fn iovec_to_win<'a>(iovec: &'a host::__wasi_iovec_t) -> winx::io::IoVec<'a> {
let slice = slice::from_raw_parts(iovec.buf as *const u8, iovec.buf_len);
IoVec::new(slice)
winx::io::IoVec::new(slice)
}
pub unsafe fn iovec_to_win_mut<'a>(iovec: &'a mut host::__wasi_iovec_t) -> IoVecMut<'a> {
pub unsafe fn iovec_to_win_mut<'a>(iovec: &'a mut host::__wasi_iovec_t) -> winx::io::IoVecMut<'a> {
let slice = slice::from_raw_parts_mut(iovec.buf as *mut u8, iovec.buf_len);
IoVecMut::new(slice)
winx::io::IoVecMut::new(slice)
}
pub fn win_from_fdflags(
fdflags: host::__wasi_fdflags_t,
) -> (winx::file::AccessRight, winx::file::FlagsAndAttributes) {
use winx::file::{AccessRight, FlagsAndAttributes};
// TODO verify this!
let mut win_rights = AccessRight::empty();
let mut win_flags_attrs = FlagsAndAttributes::empty();
if fdflags & host::__WASI_FDFLAG_NONBLOCK != 0 {
win_flags_attrs.insert(FlagsAndAttributes::FILE_FLAG_OVERLAPPED);
}
if fdflags & host::__WASI_FDFLAG_APPEND != 0 {
win_rights.insert(AccessRight::FILE_APPEND_DATA);
}
if fdflags & host::__WASI_FDFLAG_DSYNC != 0
|| fdflags & host::__WASI_FDFLAG_RSYNC != 0
|| fdflags & host::__WASI_FDFLAG_SYNC != 0
{
win_rights.insert(AccessRight::SYNCHRONIZE);
}
(win_rights, win_flags_attrs)
}
pub fn fdflags_from_win(rights: winx::file::AccessRight) -> host::__wasi_fdflags_t {
use winx::file::AccessRight;
let mut fdflags = 0;
// TODO verify this!
if rights.contains(AccessRight::FILE_APPEND_DATA) {
fdflags |= host::__WASI_FDFLAG_APPEND;
}
if rights.contains(AccessRight::SYNCHRONIZE) {
fdflags |= host::__WASI_FDFLAG_DSYNC;
fdflags |= host::__WASI_FDFLAG_RSYNC;
fdflags |= host::__WASI_FDFLAG_SYNC;
}
// The NONBLOCK equivalent is FILE_FLAG_OVERLAPPED
// but it seems winapi doesn't provide a mechanism
// for checking whether the handle supports async IO.
// On the contrary, I've found some dicsussion online
// which suggests that on Windows all handles should
// generally be assumed to be opened with async support
// and then the program should fallback should that **not**
// be the case at the time of the operation.
// TODO: this requires further investigation
fdflags
}
pub fn win_from_oflags(
oflags: host::__wasi_oflags_t,
) -> (
winx::file::CreationDisposition,
winx::file::FlagsAndAttributes,
) {
use winx::file::{CreationDisposition, FlagsAndAttributes};
let win_flags_attrs = if oflags & host::__WASI_O_DIRECTORY != 0 {
FlagsAndAttributes::FILE_FLAG_BACKUP_SEMANTICS
} else {
FlagsAndAttributes::FILE_ATTRIBUTE_NORMAL
};
let win_disp = if oflags & host::__WASI_O_CREAT != 0 && oflags & host::__WASI_O_EXCL != 0 {
CreationDisposition::CREATE_NEW
} else if oflags & host::__WASI_O_CREAT != 0 {
CreationDisposition::CREATE_ALWAYS
} else if oflags & host::__WASI_O_TRUNC != 0 {
CreationDisposition::TRUNCATE_EXISTING
} else {
CreationDisposition::OPEN_EXISTING
};
(win_disp, win_flags_attrs)
}
pub fn path_from_raw(raw_path: &[u8]) -> OsString {

View File

@@ -1,16 +1,17 @@
#![allow(non_camel_case_types)]
#![allow(unused_unsafe)]
#![allow(unused)]
use super::fdentry::FdEntry;
use super::fdentry::{determine_type_rights, FdEntry};
use super::fs_helpers::*;
use super::host_impl;
use crate::ctx::WasiCtx;
use crate::host;
use std::ffi::OsStr;
use std::os::windows::prelude::FromRawHandle;
pub(crate) fn fd_close(fd_entry: FdEntry) -> Result<(), host::__wasi_errno_t> {
unimplemented!("fd_close")
winx::handle::close(fd_entry.fd_object.raw_handle).map_err(|e| host_impl::errno_from_win(e))
}
pub(crate) fn fd_datasync(fd_entry: &FdEntry) -> Result<(), host::__wasi_errno_t> {
@@ -37,7 +38,14 @@ pub(crate) fn fd_read(
fd_entry: &FdEntry,
iovs: &mut [host::__wasi_iovec_t],
) -> Result<usize, host::__wasi_errno_t> {
unimplemented!("fd_pread")
use winx::io::{readv, IoVecMut};
let mut iovs: Vec<IoVecMut> = iovs
.iter_mut()
.map(|iov| unsafe { host_impl::iovec_to_win_mut(iov) })
.collect();
readv(fd_entry.fd_object.raw_handle, &mut iovs).map_err(|e| host_impl::errno_from_win(e))
}
pub(crate) fn fd_renumber(
@@ -63,7 +71,13 @@ pub(crate) fn fd_tell(fd_entry: &FdEntry) -> Result<u64, host::__wasi_errno_t> {
pub(crate) fn fd_fdstat_get(
fd_entry: &FdEntry,
) -> Result<host::__wasi_fdflags_t, host::__wasi_errno_t> {
unimplemented!("fd_fdstat_get")
use winx::file::AccessRight;
match winx::file::get_file_access_rights(fd_entry.fd_object.raw_handle)
.map(AccessRight::from_bits_truncate)
{
Ok(rights) => Ok(host_impl::fdflags_from_win(rights)),
Err(e) => Err(host_impl::errno_from_win(e)),
}
}
pub(crate) fn fd_fdstat_set_flags(
@@ -81,32 +95,14 @@ pub(crate) fn fd_write(
fd_entry: &FdEntry,
iovs: &[host::__wasi_iovec_t],
) -> Result<usize, host::__wasi_errno_t> {
use winapi::shared::minwindef::{DWORD, LPVOID};
use winapi::um::fileapi::WriteFile;
use winx::io::{writev, IoVec};
let iovs: Vec<host_impl::IoVec> = iovs
let iovs: Vec<IoVec> = iovs
.iter()
.map(|iov| unsafe { host_impl::iovec_to_win(iov) })
.collect();
let buf = iovs
.iter()
.find(|b| !b.as_slice().is_empty())
.map_or(&[][..], |b| b.as_slice());
let mut host_nwritten = 0;
let len = std::cmp::min(buf.len(), <DWORD>::max_value() as usize) as DWORD;
unsafe {
WriteFile(
fd_entry.fd_object.raw_handle,
buf.as_ptr() as LPVOID,
len,
&mut host_nwritten,
std::ptr::null_mut(),
)
};
Ok(host_nwritten as usize)
writev(fd_entry.fd_object.raw_handle, &iovs).map_err(|e| host_impl::errno_from_win(e))
}
pub(crate) fn fd_advise(
@@ -158,7 +154,70 @@ pub(crate) fn path_open(
mut needed_inheriting: host::__wasi_rights_t,
fs_flags: host::__wasi_fdflags_t,
) -> Result<FdEntry, host::__wasi_errno_t> {
unimplemented!("path_open")
use winx::file::{AccessRight, CreationDisposition, FlagsAndAttributes, ShareMode};
let mut win_rights = AccessRight::READ_CONTROL;
if read {
win_rights.insert(AccessRight::FILE_GENERIC_READ);
}
if write {
win_rights.insert(AccessRight::FILE_GENERIC_WRITE);
}
// convert open flags
let (win_create_disp, mut win_flags_attrs) = host_impl::win_from_oflags(oflags);
if win_create_disp == CreationDisposition::CREATE_NEW {
needed_base |= host::__WASI_RIGHT_PATH_CREATE_FILE;
} else if win_create_disp == CreationDisposition::CREATE_ALWAYS {
needed_base |= host::__WASI_RIGHT_PATH_CREATE_FILE;
} else if win_create_disp == CreationDisposition::TRUNCATE_EXISTING {
needed_base |= host::__WASI_RIGHT_PATH_FILESTAT_SET_SIZE;
}
// convert file descriptor flags
let win_fdflags_res = host_impl::win_from_fdflags(fs_flags);
win_rights.insert(win_fdflags_res.0);
win_flags_attrs.insert(win_fdflags_res.1);
if win_rights.contains(AccessRight::SYNCHRONIZE) {
needed_inheriting |= host::__WASI_RIGHT_FD_DATASYNC;
needed_inheriting |= host::__WASI_RIGHT_FD_SYNC;
}
let (dir, path) = match path_get(
ctx,
dirfd,
dirflags,
path,
needed_base,
needed_inheriting,
!win_flags_attrs.contains(FlagsAndAttributes::FILE_FLAG_BACKUP_SEMANTICS),
) {
Ok((dir, path)) => (dir, path),
Err(e) => return Err(e),
};
let new_handle =
match winx::file::openat(dir, &path, win_rights, win_create_disp, win_flags_attrs) {
Ok(handle) => handle,
Err(e) => return Err(host_impl::errno_from_win(e)),
};
// Determine the type of the new file descriptor and which rights contradict with this type
match unsafe { determine_type_rights(new_handle) } {
Err(e) => {
// if `close` fails, note it but do not override the underlying errno
winx::handle::close(new_handle).unwrap_or_else(|e| {
dbg!(e);
});
Err(e)
}
Ok((_ty, max_base, max_inheriting)) => {
let mut fe = unsafe { FdEntry::from_raw_handle(new_handle) };
fe.rights_base &= max_base;
fe.rights_inheriting &= max_inheriting;
Ok(fe)
}
}
}
pub(crate) fn fd_readdir(

View File

@@ -0,0 +1,139 @@
#![allow(non_camel_case_types)]
#![allow(unused_unsafe)]
use super::host_impl;
use crate::ctx::WasiCtx;
use crate::host;
use std::ffi::{OsStr, OsString};
use std::os::windows::prelude::RawHandle;
use std::path::{Component, Path, PathBuf};
/// Normalizes a path to ensure that the target path is located under the directory provided.
pub fn path_get<P: AsRef<OsStr>>(
wasi_ctx: &WasiCtx,
dirfd: host::__wasi_fd_t,
_dirflags: host::__wasi_lookupflags_t,
path: P,
needed_base: host::__wasi_rights_t,
needed_inheriting: host::__wasi_rights_t,
needs_final_component: bool,
) -> Result<(RawHandle, OsString), host::__wasi_errno_t> {
/// close all the intermediate handles, but make sure not to drop either the original
/// dirfd or the one we return (which may be the same dirfd)
fn ret_dir_success(dir_stack: &mut Vec<RawHandle>) -> RawHandle {
let ret_dir = dir_stack.pop().expect("there is always a dirfd to return");
if let Some(dirfds) = dir_stack.get(1..) {
for dirfd in dirfds {
winx::handle::close(*dirfd).unwrap_or_else(|e| {
dbg!(e);
});
}
}
ret_dir
}
/// close all file descriptors other than the base directory, and return the errno for
/// convenience with `return`
fn ret_error(
dir_stack: &mut Vec<RawHandle>,
errno: host::__wasi_errno_t,
) -> Result<(RawHandle, OsString), host::__wasi_errno_t> {
if let Some(dirfds) = dir_stack.get(1..) {
for dirfd in dirfds {
winx::handle::close(*dirfd).unwrap_or_else(|e| {
dbg!(e);
});
}
}
Err(errno)
}
let dirfe = wasi_ctx.get_fd_entry(dirfd, needed_base, needed_inheriting)?;
// Stack of directory handles. Index 0 always corresponds with the directory provided
// to this function. Entering a directory causes a handle to be pushed, while handling
// ".." entries causes an entry to be popped. Index 0 cannot be popped, as this would imply
// escaping the base directory.
let mut dir_stack = vec![dirfe.fd_object.raw_handle];
// Stack of paths left to process. This is initially the `path` argument to this function, but
// any symlinks we encounter are processed by pushing them on the stack.
let mut path_stack = vec![PathBuf::from(path.as_ref())];
loop {
match path_stack.pop() {
Some(cur_path) => {
// dbg!(&cur_path);
let mut components = cur_path.components();
let head = match components.next() {
None => return ret_error(&mut dir_stack, host::__WASI_ENOENT),
Some(p) => p,
};
let tail = components.as_path();
if tail.components().next().is_some() {
path_stack.push(PathBuf::from(tail));
}
match head {
Component::Prefix(_) | Component::RootDir => {
// path is absolute!
return ret_error(&mut dir_stack, host::__WASI_ENOTCAPABLE);
}
Component::CurDir => {
// "." so skip
continue;
}
Component::ParentDir => {
// ".." so pop a dir
let dirfd = dir_stack.pop().expect("dir_stack is never empty");
// we're not allowed to pop past the original directory
if dir_stack.is_empty() {
return ret_error(&mut dir_stack, host::__WASI_ENOTCAPABLE);
} else {
winx::handle::close(dirfd).unwrap_or_else(|e| {
dbg!(e);
});
}
}
Component::Normal(head) => {
// should the component be a directory? it should if there is more path left to process, or
// if it has a trailing slash and `needs_final_component` is not set
if !path_stack.is_empty()
|| (Path::new(head).is_dir() && !needs_final_component)
{
match winx::file::openat(
*dir_stack.last().expect("dir_stack is never empty"),
head,
winx::file::AccessRight::FILE_GENERIC_READ,
winx::file::CreationDisposition::OPEN_EXISTING,
winx::file::FlagsAndAttributes::FILE_FLAG_BACKUP_SEMANTICS,
) {
Ok(new_dir) => {
dir_stack.push(new_dir);
continue;
}
Err(e) => {
return ret_error(&mut dir_stack, host_impl::errno_from_win(e));
}
}
} else {
// we're done
return Ok((ret_dir_success(&mut dir_stack), head.to_os_string()));
}
}
}
}
None => {
// no further components to process. means we've hit a case like "." or "a/..", or if the
// input path has trailing slashes and `needs_final_component` is not set
return Ok((
ret_dir_success(&mut dir_stack),
OsStr::new(".").to_os_string(),
));
}
}
}
}

View File

@@ -1,6 +1,7 @@
//! Windows-specific hostcalls that implement
//! [WASI](https://github.com/CraneStation/wasmtime-wasi/blob/wasi/docs/WASI-overview.md).
mod fs;
mod fs_helpers;
mod misc;
use super::fdentry;