Skip to content

Commit

Permalink
Implement __wasi_fd_fdstat_get for Windows.
Browse files Browse the repository at this point in the history
This commit fully implements `__wasi_fd_fdstat_get` on Windows so that
the descriptor flags can be determined.

It does this by calling into `NtQueryInformationFile` (safe to call from
usermode) to get the open mode and access of the underlying OS handle.

`NtQueryInformationFile` isn't included in the `winapi` crate, so it is
manually being linked against.

This commit also fixes several bugs on Windows:

* `__WASI_O_TRUNC` should open files for write.
* Use `FILE_FLAG_WRITE_THROUGH` for the `__WASI_FDFLAG_?SYNC` flags.
* `__WASI_FDFLAG_APPEND` should disallow `FILE_WRITE_DATA` access to
  force append-only on write operations.
* Use `GENERIC_READ` and `GENERIC_WRITE` access flags.  The
  latter is required when opening a file for truncation.

Additionally, the `truncation_rights` test is now enabled.
  • Loading branch information
peterhuene committed Nov 13, 2019
1 parent 6222857 commit 8d38e2e
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 106 deletions.
3 changes: 1 addition & 2 deletions crates/wasi-common/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ mod wasm_tests {
&stemstr.replace("-", "_")
)?;
writeln!(out, " setup_log();")?;
write!(
writeln!(
out,
" let path = std::path::Path::new(r#\"{}\"#);",
path.display()
Expand Down Expand Up @@ -176,7 +176,6 @@ mod wasm_tests {
"readlink_no_buffer" => true,
"dangling_symlink" => true,
"symlink_loop" => true,
"truncation_rights" => true,
"path_rename_trailing_slashes" => true,
"fd_readdir" => true,
"poll_oneoff" => true,
Expand Down
3 changes: 2 additions & 1 deletion crates/wasi-common/src/hostcalls_impl/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,8 @@ pub(crate) unsafe fn path_open(
| wasi::__WASI_RIGHT_FD_WRITE
| wasi::__WASI_RIGHT_FD_ALLOCATE
| wasi::__WASI_RIGHT_FD_FILESTAT_SET_SIZE)
!= 0;
!= 0
|| oflags & wasi::__WASI_O_TRUNC != 0;

let fd = hostcalls_impl::path_open(resolved, read, write, oflags, fs_flags)?;

Expand Down
4 changes: 2 additions & 2 deletions crates/wasi-common/src/sys/windows/fdentry_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ pub(crate) unsafe fn determine_type_and_access_rights<Handle: AsRawHandle>(
wasi::__wasi_rights_t,
wasi::__wasi_rights_t,
)> {
use winx::file::{get_file_access_mode, AccessMode};
use winx::file::{query_access_information, AccessMode};

let (file_type, mut rights_base, rights_inheriting) = determine_type_rights(handle)?;

match file_type {
wasi::__WASI_FILETYPE_DIRECTORY | wasi::__WASI_FILETYPE_REGULAR_FILE => {
let mode = get_file_access_mode(handle.as_raw_handle())?;
let mode = query_access_information(handle.as_raw_handle())?;
if mode.contains(AccessMode::FILE_GENERIC_READ) {
rights_base |= wasi::__WASI_RIGHT_FD_READ;
}
Expand Down
71 changes: 48 additions & 23 deletions crates/wasi-common/src/sys/windows/host_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::ffi::OsStr;
use std::fs::OpenOptions;
use std::os::windows::ffi::OsStrExt;
use std::os::windows::fs::OpenOptionsExt;
use winx::file::{AccessMode, Attributes, CreationDisposition, Flags};
use winx::file::{AccessMode, Attributes, CreationDisposition, FileModeInformation, Flags};

pub(crate) fn errno_from_win(error: winx::winerror::WinError) -> wasi::__wasi_errno_t {
// TODO: implement error mapping between Windows and WASI
Expand Down Expand Up @@ -40,45 +40,70 @@ pub(crate) fn errno_from_win(error: winx::winerror::WinError) -> wasi::__wasi_er
}
}

pub(crate) fn fdflags_from_win(mode: AccessMode) -> wasi::__wasi_fdflags_t {
pub(crate) fn fdflags_from_win(
access: AccessMode,
mode: FileModeInformation,
) -> wasi::__wasi_fdflags_t {
let mut fdflags = 0;
// TODO verify this!
if mode.contains(AccessMode::FILE_APPEND_DATA) {

if access.contains(AccessMode::FILE_APPEND_DATA)
&& !access.contains(AccessMode::FILE_WRITE_DATA)
{
fdflags |= wasi::__WASI_FDFLAG_APPEND;
}
if mode.contains(AccessMode::SYNCHRONIZE) {
fdflags |= wasi::__WASI_FDFLAG_DSYNC;
fdflags |= wasi::__WASI_FDFLAG_RSYNC;

if mode.contains(FileModeInformation::FILE_WRITE_THROUGH) {
// Only report __WASI_FDFLAG_SYNC, which is technically the only one of the O_?SYNC flags Windows supports.
fdflags |= wasi::__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

if !mode.contains(FileModeInformation::FILE_SYNCHRONOUS_IO_ALERT)
&& !mode.contains(FileModeInformation::FILE_SYNCHRONOUS_IO_NONALERT)
{
fdflags |= wasi::__WASI_FDFLAG_NONBLOCK;
}

fdflags
}

pub(crate) fn win_from_fdflags(fdflags: wasi::__wasi_fdflags_t) -> (AccessMode, Flags) {
let mut access_mode = AccessMode::empty();
let mut flags = Flags::empty();
pub(crate) fn win_from_fdflags(
fdflags: wasi::__wasi_fdflags_t,
read: bool,
write: bool,
) -> (AccessMode, Flags) {
let mut access_mode = AccessMode::READ_CONTROL;

// TODO verify this!
if fdflags & wasi::__WASI_FDFLAG_NONBLOCK != 0 {
flags.insert(Flags::FILE_FLAG_OVERLAPPED);
if read {
access_mode.insert(AccessMode::GENERIC_READ);
}

if write {
access_mode.insert(AccessMode::GENERIC_WRITE);
}

// For append, grant the handle FILE_APPEND_DATA access but *not* FILE_WRITE_DATA.
// This makes the handle "append only".
if fdflags & wasi::__WASI_FDFLAG_APPEND != 0 {
access_mode.insert(AccessMode::FILE_APPEND_DATA);
access_mode.remove(AccessMode::FILE_WRITE_DATA);
}

// Enable backup semantics so directories can be opened as files
let mut flags = Flags::FILE_FLAG_BACKUP_SEMANTICS;

// TODO: setting this flag is the correct thing to do on Windows.
// However, the read *must* be overlapped (i.e. asynchronous) or the underlying system calls
// may report operations as completed when they have not.
if fdflags & wasi::__WASI_FDFLAG_NONBLOCK != 0 {
flags.insert(Flags::FILE_FLAG_OVERLAPPED);
}

// Technically, Windows only supports __WASI_FDFLAG_SYNC, but treat all the flags as the same.
if fdflags & wasi::__WASI_FDFLAG_DSYNC != 0
|| fdflags & wasi::__WASI_FDFLAG_RSYNC != 0
|| fdflags & wasi::__WASI_FDFLAG_SYNC != 0
{
access_mode.insert(AccessMode::SYNCHRONIZE);
flags.insert(Flags::FILE_FLAG_WRITE_THROUGH);
}

(access_mode, flags)
Expand Down
29 changes: 9 additions & 20 deletions crates/wasi-common/src/sys/windows/hostcalls_impl/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@ pub(crate) fn fd_pwrite(file: &File, buf: &[u8], offset: wasi::__wasi_filesize_t
}

pub(crate) fn fd_fdstat_get(fd: &File) -> Result<wasi::__wasi_fdflags_t> {
use winx::file::AccessMode;
unsafe { winx::file::get_file_access_mode(fd.as_raw_handle()) }
.map(host_impl::fdflags_from_win)
.map_err(Into::into)
unsafe {
Ok(host_impl::fdflags_from_win(
winx::file::query_access_information(fd.as_raw_handle())?,
winx::file::query_mode_information(fd.as_raw_handle())?,
))
}
}

pub(crate) fn fd_fdstat_set_flags(fd: &File, fdflags: wasi::__wasi_fdflags_t) -> Result<()> {
Expand Down Expand Up @@ -100,16 +102,6 @@ pub(crate) fn path_open(
) -> Result<File> {
use winx::file::{AccessMode, CreationDisposition, Flags};

let mut access_mode = AccessMode::READ_CONTROL;
if read {
access_mode.insert(AccessMode::FILE_GENERIC_READ);
}
if write {
access_mode.insert(AccessMode::FILE_GENERIC_WRITE);
}

let mut flags = Flags::FILE_FLAG_BACKUP_SEMANTICS;

// convert open flags
let mut opts = OpenOptions::new();
match host_impl::win_from_oflags(oflags) {
Expand All @@ -120,16 +112,11 @@ pub(crate) fn path_open(
opts.create_new(true).write(true);
}
CreationDisposition::TRUNCATE_EXISTING => {
opts.truncate(true);
opts.truncate(true).write(true);
}
_ => {}
}

// convert file descriptor flags
let (add_access_mode, add_flags) = host_impl::win_from_fdflags(fdflags);
access_mode.insert(add_access_mode);
flags.insert(add_flags);

let path = resolved.concatenate()?;

match path.symlink_metadata().map(|metadata| metadata.file_type()) {
Expand Down Expand Up @@ -162,6 +149,8 @@ pub(crate) fn path_open(
},
}

let (access_mode, flags) = host_impl::win_from_fdflags(fdflags, read, write);

opts.access_mode(access_mode.bits())
.custom_flags(flags.bits())
.open(&path)
Expand Down
Loading

0 comments on commit 8d38e2e

Please sign in to comment.