* Fix fd_readdir on BSD-style nixes The fix was tested on Darwin-XNU and FreeBSD. The change introduces thread-safe cache of (RawFd, *mut libc::DIR) pairs so that libc::fdopendir syscall is called only once when invoking fd_readdir for the first time, and then the pointer to the directory stream, *mut libc::DIR, is reused until the matching raw file descriptor is closed. This fix allows then correct use (and matching to the implementation on Linux kernels) of libc::seekdir and libc::rewinddir to seek through and rewind the existing directory stream, *mut libc::DIR, which otherwise seems to be reset/invalidated every time libc::fdopendir is called (unlike on Linux, where this behaviour is not observed). * Store dir stream as part of the FdEntry's Descriptor * Move bsd specifics into separate module * Add todo comments and fix formatting * Refactor int conversions * Emphasise in debug logs that we're looking at fd_readdir entry * Change visibility of FdEntry and related to public-private * Rewrite creating DirStream for the first time
130 lines
3.9 KiB
Rust
130 lines
3.9 KiB
Rust
use crate::fdentry::Descriptor;
|
|
use crate::{host, Error, Result};
|
|
use std::fs::File;
|
|
use std::io;
|
|
use std::ops::{Deref, DerefMut};
|
|
use std::os::windows::prelude::{AsRawHandle, FromRawHandle, RawHandle};
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct OsFile(File);
|
|
|
|
impl From<File> for OsFile {
|
|
fn from(file: File) -> Self {
|
|
Self(file)
|
|
}
|
|
}
|
|
|
|
impl AsRawHandle for OsFile {
|
|
fn as_raw_handle(&self) -> RawHandle {
|
|
self.0.as_raw_handle()
|
|
}
|
|
}
|
|
|
|
impl Deref for OsFile {
|
|
type Target = File;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl DerefMut for OsFile {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.0
|
|
}
|
|
}
|
|
|
|
impl AsRawHandle for Descriptor {
|
|
fn as_raw_handle(&self) -> RawHandle {
|
|
match self {
|
|
Descriptor::OsFile(file) => file.as_raw_handle(),
|
|
Descriptor::Stdin => io::stdin().as_raw_handle(),
|
|
Descriptor::Stdout => io::stdout().as_raw_handle(),
|
|
Descriptor::Stderr => io::stderr().as_raw_handle(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn determine_type_and_access_rights<Handle: AsRawHandle>(
|
|
handle: &Handle,
|
|
) -> Result<(
|
|
host::__wasi_filetype_t,
|
|
host::__wasi_rights_t,
|
|
host::__wasi_rights_t,
|
|
)> {
|
|
use winx::file::{get_file_access_mode, AccessMode};
|
|
|
|
let (file_type, mut rights_base, rights_inheriting) = determine_type_rights(handle)?;
|
|
|
|
match file_type {
|
|
host::__WASI_FILETYPE_DIRECTORY | host::__WASI_FILETYPE_REGULAR_FILE => {
|
|
let mode = get_file_access_mode(handle.as_raw_handle())?;
|
|
if mode.contains(AccessMode::FILE_GENERIC_READ) {
|
|
rights_base |= host::__WASI_RIGHT_FD_READ;
|
|
}
|
|
if mode.contains(AccessMode::FILE_GENERIC_WRITE) {
|
|
rights_base |= host::__WASI_RIGHT_FD_WRITE;
|
|
}
|
|
}
|
|
_ => {
|
|
// TODO: is there a way around this? On windows, it seems
|
|
// we cannot check access rights for anything but dirs and regular files
|
|
}
|
|
}
|
|
|
|
Ok((file_type, rights_base, rights_inheriting))
|
|
}
|
|
|
|
pub(crate) fn determine_type_rights<Handle: AsRawHandle>(
|
|
handle: &Handle,
|
|
) -> Result<(
|
|
host::__wasi_filetype_t,
|
|
host::__wasi_rights_t,
|
|
host::__wasi_rights_t,
|
|
)> {
|
|
let (file_type, rights_base, rights_inheriting) = {
|
|
let file_type = winx::file::get_file_type(handle.as_raw_handle())?;
|
|
if file_type.is_char() {
|
|
// character file: LPT device or console
|
|
// TODO: rule out LPT device
|
|
(
|
|
host::__WASI_FILETYPE_CHARACTER_DEVICE,
|
|
host::RIGHTS_TTY_BASE,
|
|
host::RIGHTS_TTY_BASE,
|
|
)
|
|
} else if file_type.is_disk() {
|
|
// disk file: file, dir or disk device
|
|
let file = std::mem::ManuallyDrop::new(unsafe {
|
|
File::from_raw_handle(handle.as_raw_handle())
|
|
});
|
|
let meta = file.metadata().map_err(|_| Error::EINVAL)?;
|
|
if meta.is_dir() {
|
|
(
|
|
host::__WASI_FILETYPE_DIRECTORY,
|
|
host::RIGHTS_DIRECTORY_BASE,
|
|
host::RIGHTS_DIRECTORY_INHERITING,
|
|
)
|
|
} else if meta.is_file() {
|
|
(
|
|
host::__WASI_FILETYPE_REGULAR_FILE,
|
|
host::RIGHTS_REGULAR_FILE_BASE,
|
|
host::RIGHTS_REGULAR_FILE_INHERITING,
|
|
)
|
|
} else {
|
|
return Err(Error::EINVAL);
|
|
}
|
|
} else if file_type.is_pipe() {
|
|
// pipe object: socket, named pipe or anonymous pipe
|
|
// TODO: what about pipes, etc?
|
|
(
|
|
host::__WASI_FILETYPE_SOCKET_STREAM,
|
|
host::RIGHTS_SOCKET_BASE,
|
|
host::RIGHTS_SOCKET_INHERITING,
|
|
)
|
|
} else {
|
|
return Err(Error::EINVAL);
|
|
}
|
|
};
|
|
Ok((file_type, rights_base, rights_inheriting))
|
|
}
|