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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user