Implement __wasi_fd_fdstat_get for Windows.

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.
This commit is contained in:
Peter Huene
2019-11-12 16:41:54 -08:00
committed by Jakub Konka
parent 3e7bc745a2
commit 0cf54ffeba
11 changed files with 245 additions and 172 deletions

View File

@@ -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
@@ -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_FDFLAGS_APPEND)
if access_mode.contains(AccessMode::FILE_APPEND_DATA)
&& !access_mode.contains(AccessMode::FILE_WRITE_DATA)
{
fdflags |= wasi::__WASI_FDFLAGS_APPEND;
}
if mode.contains(FileModeInformation::FILE_WRITE_THROUGH) {
// Only report __WASI_FDFLAGS_SYNC
// This is technically the only one of the O_?SYNC flags Windows supports.
fdflags |= wasi::__WASI_FDFLAGS_SYNC;
}
// Files do not support the `__WASI_FDFLAGS_NONBLOCK` flag
Ok(fdflags)
}
pub(crate) fn fd_fdstat_set_flags(fd: &File, fdflags: wasi::__wasi_fdflags_t) -> Result<()> {
@@ -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()) {
@@ -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_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
}
}
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_FDFLAGS_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_FDFLAGS_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_FDFLAGS_SYNC, but treat all the flags as the same.
if fdflags & wasi::__WASI_FDFLAGS_DSYNC != 0
|| fdflags & wasi::__WASI_FDFLAGS_RSYNC != 0
|| fdflags & wasi::__WASI_FDFLAGS_SYNC != 0
{
flags.insert(Flags::FILE_FLAG_WRITE_THROUGH);
}
flags
}
fn dirent_from_path<P: AsRef<Path>>(
path: P,
name: &str,