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
user mode) 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:

* Ignore `__WASI_FDFLAG_NONBLOCK` by not setting `FILE_FLAG_OVERLAPPED`
  on file handles (the POSIX behavior for `O_NONBLOCK` on files).
* 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 26, 2019
1 parent 5cdae1d commit d2be013
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 150 deletions.
8 changes: 7 additions & 1 deletion crates/test-programs/wasi-tests/src/bin/path_filestat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ unsafe fn test_path_filestat(dir_fd: wasi_unstable::Fd) {
| wasi_unstable::RIGHT_FD_WRITE
| wasi_unstable::RIGHT_PATH_FILESTAT_GET,
0,
0,
// Pass some flags for later retrieval
wasi_unstable::FDFLAG_APPEND | wasi_unstable::FDFLAG_SYNC,
&mut file_fd,
);
assert_eq!(
Expand All @@ -62,6 +63,11 @@ unsafe fn test_path_filestat(dir_fd: wasi_unstable::Fd) {
0,
"files shouldn't have rights for path_* syscalls even if manually given",
);
assert_ne!(
fdstat.fs_flags & (wasi_unstable::FDFLAG_APPEND | wasi_unstable::FDFLAG_SYNC),
0,
"file should have the same flags used to create the file"
);

// Check file size
let mut stat = wasi_unstable::FileStat {
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_RIGHTS_FD_READ;
}
Expand Down
64 changes: 0 additions & 64 deletions crates/wasi-common/src/sys/windows/host_impl.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
//! WASI host types specific to Windows host.
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(unused)]
use crate::{wasi, Error, Result};
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};

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,64 +34,6 @@ 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 {
let mut fdflags = 0;
// TODO verify this!
if mode.contains(AccessMode::FILE_APPEND_DATA) {
fdflags |= wasi::__WASI_FDFLAGS_APPEND;
}
if mode.contains(AccessMode::SYNCHRONIZE) {
fdflags |= wasi::__WASI_FDFLAGS_DSYNC;
fdflags |= wasi::__WASI_FDFLAGS_RSYNC;
fdflags |= wasi::__WASI_FDFLAGS_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(crate) fn win_from_fdflags(fdflags: wasi::__wasi_fdflags_t) -> (AccessMode, Flags) {
let mut access_mode = AccessMode::empty();
let mut flags = Flags::empty();

// TODO verify this!
if fdflags & wasi::__WASI_FDFLAGS_NONBLOCK != 0 {
flags.insert(Flags::FILE_FLAG_OVERLAPPED);
}
if fdflags & wasi::__WASI_FDFLAGS_APPEND != 0 {
access_mode.insert(AccessMode::FILE_APPEND_DATA);
}
if fdflags & wasi::__WASI_FDFLAGS_DSYNC != 0
|| fdflags & wasi::__WASI_FDFLAGS_RSYNC != 0
|| fdflags & wasi::__WASI_FDFLAGS_SYNC != 0
{
access_mode.insert(AccessMode::SYNCHRONIZE);
}

(access_mode, flags)
}

pub(crate) fn win_from_oflags(oflags: wasi::__wasi_oflags_t) -> CreationDisposition {
if oflags & wasi::__WASI_OFLAGS_CREAT != 0 {
if oflags & wasi::__WASI_OFLAGS_EXCL != 0 {
CreationDisposition::CREATE_NEW
} else {
CreationDisposition::CREATE_ALWAYS
}
} else if oflags & wasi::__WASI_OFLAGS_TRUNC != 0 {
CreationDisposition::TRUNCATE_EXISTING
} else {
CreationDisposition::OPEN_EXISTING
}
}

/// Creates owned WASI path from OS string.
///
/// NB WASI spec requires OS string to be valid UTF-8. Otherwise,
Expand Down
115 changes: 90 additions & 25 deletions crates/wasi-common/src/sys/windows/hostcalls_impl/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use std::io::{self, Seek, SeekFrom};
use std::os::windows::fs::{FileExt, OpenOptionsExt};
use std::os::windows::prelude::{AsRawHandle, FromRawHandle};
use std::path::{Path, PathBuf};
use winx::file::{AccessMode, Flags};
use winx::file::{AccessMode, CreationDisposition, FileModeInformation, Flags};

fn read_at(mut file: &File, buf: &mut [u8], offset: u64) -> io::Result<usize> {
// get current cursor position
Expand Down Expand Up @@ -53,10 +53,29 @@ 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)
let mut fdflags = 0;

let handle = unsafe { fd.as_raw_handle() };

let access_mode = winx::file::query_access_information(handle)?;
let mode = winx::file::query_mode_information(handle)?;

// Append without write implies append-only (__WASI_FDFLAG_APPEND)
if access_mode.contains(AccessMode::FILE_APPEND_DATA)
&& !access_mode.contains(AccessMode::FILE_WRITE_DATA)
{
fdflags |= wasi::__WASI_FDFLAG_APPEND;
}

if mode.contains(FileModeInformation::FILE_WRITE_THROUGH) {
// Only report __WASI_FDFLAG_SYNC
// This is technically the only one of the O_?SYNC flags Windows supports.
fdflags |= wasi::__WASI_FDFLAG_SYNC;
}

// Files do not support the `__WASI_FDFLAG_NONBLOCK` flag

Ok(fdflags)
}

pub(crate) fn fd_fdstat_set_flags(fd: &File, fdflags: wasi::__wasi_fdflags_t) -> Result<()> {
Expand Down Expand Up @@ -100,36 +119,23 @@ 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
// note: the calls to `write(true)` are to bypass an internal OpenOption check
// the write flag will ultimately be ignored when `access_mode` is called below.
let mut opts = OpenOptions::new();
match host_impl::win_from_oflags(oflags) {
match creation_disposition_from_oflags(oflags) {
CreationDisposition::CREATE_ALWAYS => {
opts.create(true).append(true);
opts.create(true).write(true);
}
CreationDisposition::CREATE_NEW => {
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,12 +168,71 @@ pub(crate) fn path_open(
},
}

opts.access_mode(access_mode.bits())
.custom_flags(flags.bits())
opts.access_mode(file_access_mode_from_fdflags(fdflags, read, write).bits())
.custom_flags(file_flags_from_fdflags(fdflags).bits())
.open(&path)
.map_err(Into::into)
}

fn creation_disposition_from_oflags(oflags: wasi::__wasi_oflags_t) -> CreationDisposition {
if oflags & wasi::__WASI_O_CREAT != 0 {
if oflags & wasi::__WASI_O_EXCL != 0 {
CreationDisposition::CREATE_NEW
} else {
CreationDisposition::CREATE_ALWAYS
}
} else if oflags & wasi::__WASI_O_TRUNC != 0 {
CreationDisposition::TRUNCATE_EXISTING
} else {
CreationDisposition::OPEN_EXISTING
}
}

fn file_access_mode_from_fdflags(
fdflags: wasi::__wasi_fdflags_t,
read: bool,
write: bool,
) -> AccessMode {
let mut access_mode = AccessMode::READ_CONTROL;

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".
// Changes to the file pointer will be ignored (like POSIX's O_APPEND behavior).
if fdflags & wasi::__WASI_FDFLAG_APPEND != 0 {
access_mode.insert(AccessMode::FILE_APPEND_DATA);
access_mode.remove(AccessMode::FILE_WRITE_DATA);
}

access_mode
}

fn file_flags_from_fdflags(fdflags: wasi::__wasi_fdflags_t) -> Flags {
// Enable backup semantics so directories can be opened as files
let mut flags = Flags::FILE_FLAG_BACKUP_SEMANTICS;

// Note: __WASI_FDFLAG_NONBLOCK is purposely being ignored for files
// While Windows does inherently support a non-blocking mode on files, the WASI API will
// treat I/O operations on files as synchronous. WASI might have an async-io API in the future.

// 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
{
flags.insert(Flags::FILE_FLAG_WRITE_THROUGH);
}

flags
}

fn dirent_from_path<P: AsRef<Path>>(
path: P,
name: &str,
Expand Down
Loading

0 comments on commit d2be013

Please sign in to comment.