Files
wasmtime/crates/wasi-common/src/sys/windows/fd.rs

198 lines
7.5 KiB
Rust

use super::file_serial_no;
use super::oshandle::RawOsHandle;
use crate::path;
use crate::sys::osdir::OsDir;
use crate::sys::osfile::OsFile;
use crate::sys::AsFile;
use crate::wasi::{types, Result};
use std::convert::TryInto;
use std::fs::{File, OpenOptions};
use std::os::windows::fs::OpenOptionsExt;
use std::os::windows::prelude::{AsRawHandle, FromRawHandle};
use std::path::Path;
use tracing::trace;
use winx::file::{AccessMode, FileModeInformation, Flags};
pub(crate) fn fdstat_get(file: &File) -> Result<types::Fdflags> {
let mut fdflags = types::Fdflags::empty();
let handle = file.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 |= types::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 |= types::Fdflags::SYNC;
}
// Files do not support the `__WASI_FDFLAGS_NONBLOCK` flag
Ok(fdflags)
}
// TODO Investigate further for Stdio handles. `ReOpenFile` requires the file
// handle came from `CreateFile`, but the Rust's libstd will use `GetStdHandle`
// rather than `CreateFile`. Relevant discussion can be found in:
// https://github.com/rust-lang/rust/issues/40490
pub(crate) fn fdstat_set_flags(
file: &File,
fdflags: types::Fdflags,
) -> Result<Option<RawOsHandle>> {
let handle = file.as_raw_handle();
let access_mode = winx::file::query_access_information(handle)?;
let new_access_mode = file_access_mode_from_fdflags(
fdflags,
access_mode.contains(AccessMode::FILE_READ_DATA),
access_mode.contains(AccessMode::FILE_WRITE_DATA)
| access_mode.contains(AccessMode::FILE_APPEND_DATA),
);
unsafe {
Ok(Some(RawOsHandle::from_raw_handle(winx::file::reopen_file(
handle,
new_access_mode,
fdflags.into(),
)?)))
}
}
pub(crate) fn advise(
_file: &OsFile,
_advice: types::Advice,
_offset: types::Filesize,
_len: types::Filesize,
) -> Result<()> {
Ok(())
}
fn file_access_mode_from_fdflags(fdflags: types::Fdflags, read: bool, write: bool) -> AccessMode {
let mut access_mode = AccessMode::READ_CONTROL;
// Note that `GENERIC_READ` and `GENERIC_WRITE` cannot be used to properly support append-only mode
// The file-specific flags `FILE_GENERIC_READ` and `FILE_GENERIC_WRITE` are used here instead
// These flags have the same semantic meaning for file objects, but allow removal of specific permissions (see below)
if read {
access_mode.insert(AccessMode::FILE_GENERIC_READ);
}
if write {
access_mode.insert(AccessMode::FILE_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.contains(&types::Fdflags::APPEND) {
access_mode.insert(AccessMode::FILE_APPEND_DATA);
access_mode.remove(AccessMode::FILE_WRITE_DATA);
}
access_mode
}
// On Windows there is apparently no support for seeking the directory stream in the OS.
// cf. https://github.com/WebAssembly/WASI/issues/61
//
// The implementation here may perform in O(n^2) if the host buffer is O(1)
// and the number of directory entries is O(n).
// TODO: Add a heuristic optimization to achieve O(n) time in the most common case
// where fd_readdir is resumed where it previously finished
//
// Correctness of this approach relies upon one assumption: that the order of entries
// returned by `FindNextFileW` is stable, i.e. doesn't change if the directory
// contents stay the same. This invariant is crucial to be able to implement
// any kind of seeking whatsoever without having to read the whole directory at once
// and then return the data from cache. (which leaks memory)
//
// The MSDN documentation explicitly says that the order in which the search returns the files
// is not guaranteed, and is dependent on the file system.
// cf. https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findnextfilew
//
// This stackoverflow post suggests that `FindNextFileW` is indeed stable and that
// the order of directory entries depends **only** on the filesystem used, but the
// MSDN documentation is not clear about this.
// cf. https://stackoverflow.com/questions/47380739/is-findfirstfile-and-findnextfile-order-random-even-for-dvd
//
// Implementation details:
// Cookies for the directory entries start from 1. (0 is reserved by wasi::__WASI_DIRCOOKIE_START)
// . gets cookie = 1
// .. gets cookie = 2
// other entries, in order they were returned by FindNextFileW get subsequent integers as their cookies
pub(crate) fn readdir(
dirfd: &OsDir,
cookie: types::Dircookie,
) -> Result<Box<dyn Iterator<Item = Result<(types::Dirent, String)>>>> {
use winx::file::get_file_path;
let cookie = cookie.try_into()?;
let path = get_file_path(&*dirfd.as_file()?)?;
// std::fs::ReadDir doesn't return . and .., so we need to emulate it
let path = Path::new(&path);
// The directory /.. is the same as / on Unix (at least on ext4), so emulate this behavior too
let parent = path.parent().unwrap_or(path);
let dot = dirent_from_path(path, ".", 1)?;
let dotdot = dirent_from_path(parent, "..", 2)?;
trace!(" | fd_readdir impl: executing std::fs::ReadDir");
let iter = path.read_dir()?.zip(3..).map(|(dir, no)| {
let dir: std::fs::DirEntry = dir?;
let ftype = dir.file_type()?;
let name = path::from_host(dir.file_name())?;
let d_ino = File::open(dir.path()).and_then(|f| file_serial_no(&f))?;
let dirent = types::Dirent {
d_namlen: name.len().try_into()?,
d_type: ftype.into(),
d_ino,
d_next: no,
};
Ok((dirent, name))
});
// into_iter for arrays is broken and returns references instead of values,
// so we need to use vec![...] and do heap allocation
// See https://github.com/rust-lang/rust/issues/25725
let iter = vec![dot, dotdot].into_iter().map(Ok).chain(iter);
// Emulate seekdir(). This may give O(n^2) complexity if used with a
// small host_buf, but this is difficult to implement efficiently.
//
// See https://github.com/WebAssembly/WASI/issues/61 for more details.
Ok(Box::new(iter.skip(cookie)))
}
fn dirent_from_path<P: AsRef<Path>>(
path: P,
name: &str,
cookie: types::Dircookie,
) -> Result<(types::Dirent, String)> {
let path = path.as_ref();
trace!("dirent_from_path: opening {}", path.to_string_lossy());
// To open a directory on Windows, FILE_FLAG_BACKUP_SEMANTICS flag must be used
let file = OpenOptions::new()
.custom_flags(Flags::FILE_FLAG_BACKUP_SEMANTICS.bits())
.read(true)
.open(path)?;
let ty = file.metadata()?.file_type();
let name = name.to_owned();
let dirent = types::Dirent {
d_namlen: name.len().try_into()?,
d_next: cookie,
d_type: ty.into(),
d_ino: file_serial_no(&file)?,
};
Ok((dirent, name))
}
pub(crate) fn filestat_get(file: &File) -> Result<types::Filestat> {
let filestat = file.try_into()?;
Ok(filestat)
}