Fix fd_readdir on BSD-style nixes (#81)
* 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
This commit is contained in:
17
build.rs
17
build.rs
@@ -154,26 +154,11 @@ fn avoid_keywords(name: &str) -> &str {
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(linux)] {
|
||||
/// Ignore tests that aren't supported yet.
|
||||
fn ignore(_testsuite: &str, _name: &str) -> bool {
|
||||
if testsuite == "misc_testsuite" {
|
||||
match name {
|
||||
"path_rename_trailing_slashes" => true,
|
||||
"path_symlink_trailing_slashes" => true,
|
||||
"remove_directory_trailing_slashes" => true,
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
} else if #[cfg(not(any(linux, windows)))] {
|
||||
if #[cfg(not(windows))] {
|
||||
/// Ignore tests that aren't supported yet.
|
||||
fn ignore(testsuite: &str, name: &str) -> bool {
|
||||
if testsuite == "misc_testsuite" {
|
||||
match name {
|
||||
"fd_readdir" => true,
|
||||
"path_rename_trailing_slashes" => true,
|
||||
"path_symlink_trailing_slashes" => true,
|
||||
"remove_directory_trailing_slashes" => true,
|
||||
|
||||
@@ -1,48 +1,57 @@
|
||||
use crate::sys::fdentry_impl;
|
||||
use crate::sys::fdentry_impl::{determine_type_and_access_rights, OsFile};
|
||||
use crate::{host, Error, Result};
|
||||
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::path::PathBuf;
|
||||
use std::{fs, io};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Descriptor {
|
||||
File(fs::File),
|
||||
pub(crate) enum Descriptor {
|
||||
OsFile(OsFile),
|
||||
Stdin,
|
||||
Stdout,
|
||||
Stderr,
|
||||
}
|
||||
|
||||
impl Descriptor {
|
||||
pub fn as_file(&self) -> Result<&fs::File> {
|
||||
pub(crate) fn as_file(&self) -> Result<&OsFile> {
|
||||
match self {
|
||||
Descriptor::File(f) => Ok(f),
|
||||
Descriptor::OsFile(file) => Ok(file),
|
||||
_ => Err(Error::EBADF),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_file(&self) -> bool {
|
||||
pub(crate) fn as_file_mut(&mut self) -> Result<&mut OsFile> {
|
||||
match self {
|
||||
Descriptor::File(_) => true,
|
||||
Descriptor::OsFile(file) => Ok(file),
|
||||
_ => Err(Error::EBADF),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_file(&self) -> bool {
|
||||
match self {
|
||||
Descriptor::OsFile(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_stdin(&self) -> bool {
|
||||
#[allow(unused)]
|
||||
pub(crate) fn is_stdin(&self) -> bool {
|
||||
match self {
|
||||
Descriptor::Stdin => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_stdout(&self) -> bool {
|
||||
#[allow(unused)]
|
||||
pub(crate) fn is_stdout(&self) -> bool {
|
||||
match self {
|
||||
Descriptor::Stdout => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_stderr(&self) -> bool {
|
||||
#[allow(unused)]
|
||||
pub(crate) fn is_stderr(&self) -> bool {
|
||||
match self {
|
||||
Descriptor::Stderr => true,
|
||||
_ => false,
|
||||
@@ -51,19 +60,19 @@ impl Descriptor {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FdObject {
|
||||
pub file_type: host::__wasi_filetype_t,
|
||||
pub descriptor: ManuallyDrop<Descriptor>,
|
||||
pub needs_close: bool,
|
||||
pub(crate) struct FdObject {
|
||||
pub(crate) file_type: host::__wasi_filetype_t,
|
||||
pub(crate) descriptor: ManuallyDrop<Descriptor>,
|
||||
pub(crate) needs_close: bool,
|
||||
// TODO: directories
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FdEntry {
|
||||
pub fd_object: FdObject,
|
||||
pub rights_base: host::__wasi_rights_t,
|
||||
pub rights_inheriting: host::__wasi_rights_t,
|
||||
pub preopen_path: Option<PathBuf>,
|
||||
pub(crate) struct FdEntry {
|
||||
pub(crate) fd_object: FdObject,
|
||||
pub(crate) rights_base: host::__wasi_rights_t,
|
||||
pub(crate) rights_inheriting: host::__wasi_rights_t,
|
||||
pub(crate) preopen_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Drop for FdObject {
|
||||
@@ -75,12 +84,12 @@ impl Drop for FdObject {
|
||||
}
|
||||
|
||||
impl FdEntry {
|
||||
pub fn from(file: fs::File) -> Result<Self> {
|
||||
fdentry_impl::determine_type_and_access_rights(&file).map(
|
||||
pub(crate) fn from(file: fs::File) -> Result<Self> {
|
||||
determine_type_and_access_rights(&file).map(
|
||||
|(file_type, rights_base, rights_inheriting)| Self {
|
||||
fd_object: FdObject {
|
||||
file_type,
|
||||
descriptor: ManuallyDrop::new(Descriptor::File(file)),
|
||||
descriptor: ManuallyDrop::new(Descriptor::OsFile(OsFile::from(file))),
|
||||
needs_close: true,
|
||||
},
|
||||
rights_base,
|
||||
@@ -90,12 +99,12 @@ impl FdEntry {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn duplicate(file: &fs::File) -> Result<Self> {
|
||||
pub(crate) fn duplicate(file: &fs::File) -> Result<Self> {
|
||||
Self::from(file.try_clone()?)
|
||||
}
|
||||
|
||||
pub fn duplicate_stdin() -> Result<Self> {
|
||||
fdentry_impl::determine_type_and_access_rights(&io::stdin()).map(
|
||||
pub(crate) fn duplicate_stdin() -> Result<Self> {
|
||||
determine_type_and_access_rights(&io::stdin()).map(
|
||||
|(file_type, rights_base, rights_inheriting)| Self {
|
||||
fd_object: FdObject {
|
||||
file_type,
|
||||
@@ -109,8 +118,8 @@ impl FdEntry {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn duplicate_stdout() -> Result<Self> {
|
||||
fdentry_impl::determine_type_and_access_rights(&io::stdout()).map(
|
||||
pub(crate) fn duplicate_stdout() -> Result<Self> {
|
||||
determine_type_and_access_rights(&io::stdout()).map(
|
||||
|(file_type, rights_base, rights_inheriting)| Self {
|
||||
fd_object: FdObject {
|
||||
file_type,
|
||||
@@ -124,8 +133,8 @@ impl FdEntry {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn duplicate_stderr() -> Result<Self> {
|
||||
fdentry_impl::determine_type_and_access_rights(&io::stderr()).map(
|
||||
pub(crate) fn duplicate_stderr() -> Result<Self> {
|
||||
determine_type_and_access_rights(&io::stderr()).map(
|
||||
|(file_type, rights_base, rights_inheriting)| Self {
|
||||
fd_object: FdObject {
|
||||
file_type,
|
||||
|
||||
@@ -43,7 +43,7 @@ hostcalls! {
|
||||
) -> wasm32::__wasi_errno_t;
|
||||
|
||||
pub fn fd_seek(
|
||||
wasi_ctx: &WasiCtx,
|
||||
wasi_ctx: &mut WasiCtx,
|
||||
memory: &mut [u8],
|
||||
fd: wasm32::__wasi_fd_t,
|
||||
offset: wasm32::__wasi_filedelta_t,
|
||||
@@ -52,7 +52,7 @@ hostcalls! {
|
||||
) -> wasm32::__wasi_errno_t;
|
||||
|
||||
pub fn fd_tell(
|
||||
wasi_ctx: &WasiCtx,
|
||||
wasi_ctx: &mut WasiCtx,
|
||||
memory: &mut [u8],
|
||||
fd: wasm32::__wasi_fd_t,
|
||||
newoffset: wasm32::uintptr_t,
|
||||
@@ -139,7 +139,7 @@ hostcalls! {
|
||||
) -> wasm32::__wasi_errno_t;
|
||||
|
||||
pub fn fd_readdir(
|
||||
wasi_ctx: &WasiCtx,
|
||||
wasi_ctx: &mut WasiCtx,
|
||||
memory: &mut [u8],
|
||||
fd: wasm32::__wasi_fd_t,
|
||||
buf: wasm32::uintptr_t,
|
||||
|
||||
@@ -158,7 +158,7 @@ pub(crate) fn fd_read(
|
||||
.collect();
|
||||
|
||||
let maybe_host_nread = match &mut *fe.fd_object.descriptor {
|
||||
Descriptor::File(f) => f.read_vectored(&mut iovs),
|
||||
Descriptor::OsFile(file) => file.read_vectored(&mut iovs),
|
||||
Descriptor::Stdin => io::stdin().lock().read_vectored(&mut iovs),
|
||||
_ => return Err(Error::EBADF),
|
||||
};
|
||||
@@ -203,7 +203,7 @@ pub(crate) fn fd_renumber(
|
||||
.fd_object
|
||||
.descriptor
|
||||
.as_file()
|
||||
.and_then(FdEntry::duplicate)?;
|
||||
.and_then(|file| FdEntry::duplicate(file))?;
|
||||
|
||||
wasi_ctx.fds.insert(to, fe_from_dup);
|
||||
wasi_ctx.fds.remove(&from);
|
||||
@@ -212,7 +212,7 @@ pub(crate) fn fd_renumber(
|
||||
}
|
||||
|
||||
pub(crate) fn fd_seek(
|
||||
wasi_ctx: &WasiCtx,
|
||||
wasi_ctx: &mut WasiCtx,
|
||||
memory: &mut [u8],
|
||||
fd: wasm32::__wasi_fd_t,
|
||||
offset: wasm32::__wasi_filedelta_t,
|
||||
@@ -236,9 +236,9 @@ pub(crate) fn fd_seek(
|
||||
} else {
|
||||
host::__WASI_RIGHT_FD_SEEK | host::__WASI_RIGHT_FD_TELL
|
||||
};
|
||||
let mut fd = wasi_ctx
|
||||
.get_fd_entry(fd, rights, 0)
|
||||
.and_then(|fe| fe.fd_object.descriptor.as_file())?;
|
||||
let fd = wasi_ctx
|
||||
.get_fd_entry_mut(fd, rights, 0)
|
||||
.and_then(|fe| fe.fd_object.descriptor.as_file_mut())?;
|
||||
|
||||
let pos = match whence {
|
||||
host::__WASI_WHENCE_CUR => SeekFrom::Current(offset),
|
||||
@@ -254,7 +254,7 @@ pub(crate) fn fd_seek(
|
||||
}
|
||||
|
||||
pub(crate) fn fd_tell(
|
||||
wasi_ctx: &WasiCtx,
|
||||
wasi_ctx: &mut WasiCtx,
|
||||
memory: &mut [u8],
|
||||
fd: wasm32::__wasi_fd_t,
|
||||
newoffset: wasm32::uintptr_t,
|
||||
@@ -262,9 +262,9 @@ pub(crate) fn fd_tell(
|
||||
trace!("fd_tell(fd={:?}, newoffset={:#x?})", fd, newoffset);
|
||||
|
||||
let fd = dec_fd(fd);
|
||||
let mut fd = wasi_ctx
|
||||
.get_fd_entry(fd, host::__WASI_RIGHT_FD_TELL, 0)
|
||||
.and_then(|fe| fe.fd_object.descriptor.as_file())?;
|
||||
let fd = wasi_ctx
|
||||
.get_fd_entry_mut(fd, host::__WASI_RIGHT_FD_TELL, 0)
|
||||
.and_then(|fe| fe.fd_object.descriptor.as_file_mut())?;
|
||||
|
||||
let host_offset = fd.seek(SeekFrom::Current(0))?;
|
||||
|
||||
@@ -377,7 +377,7 @@ pub(crate) fn fd_write(
|
||||
|
||||
// perform unbuffered writes
|
||||
let host_nwritten = match &mut *fe.fd_object.descriptor {
|
||||
Descriptor::File(f) => f.write_vectored(&iovs)?,
|
||||
Descriptor::OsFile(file) => file.write_vectored(&iovs)?,
|
||||
Descriptor::Stdin => return Err(Error::EBADF),
|
||||
Descriptor::Stdout => {
|
||||
// lock for the duration of the scope
|
||||
@@ -595,7 +595,7 @@ pub(crate) fn path_open(
|
||||
}
|
||||
|
||||
pub(crate) fn fd_readdir(
|
||||
wasi_ctx: &WasiCtx,
|
||||
wasi_ctx: &mut WasiCtx,
|
||||
memory: &mut [u8],
|
||||
fd: wasm32::__wasi_fd_t,
|
||||
buf: wasm32::uintptr_t,
|
||||
@@ -615,17 +615,16 @@ pub(crate) fn fd_readdir(
|
||||
enc_usize_byref(memory, buf_used, 0)?;
|
||||
|
||||
let fd = dec_fd(fd);
|
||||
let fd = wasi_ctx
|
||||
.get_fd_entry(fd, host::__WASI_RIGHT_FD_READDIR, 0)
|
||||
.and_then(|fe| fe.fd_object.descriptor.as_file())?;
|
||||
|
||||
let file = wasi_ctx
|
||||
.get_fd_entry_mut(fd, host::__WASI_RIGHT_FD_READDIR, 0)
|
||||
.and_then(|entry| entry.fd_object.descriptor.as_file_mut())?;
|
||||
let host_buf = dec_slice_of_mut::<u8>(memory, buf, buf_len)?;
|
||||
|
||||
trace!(" | (buf,buf_len)={:?}", host_buf);
|
||||
|
||||
let cookie = dec_dircookie(cookie);
|
||||
|
||||
let host_bufused = hostcalls_impl::fd_readdir(fd, host_buf, cookie)?;
|
||||
let host_bufused = hostcalls_impl::fd_readdir(file, host_buf, cookie)?;
|
||||
|
||||
trace!(" | *buf_used={:?}", host_bufused);
|
||||
|
||||
|
||||
156
src/sys/unix/bsd/hostcalls_impl.rs
Normal file
156
src/sys/unix/bsd/hostcalls_impl.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use super::osfile::OsFile;
|
||||
use crate::sys::host_impl;
|
||||
use crate::{host, Error, Result};
|
||||
use nix::libc::{self, c_long, c_void};
|
||||
use std::convert::TryInto;
|
||||
use std::fs::File;
|
||||
use std::os::unix::prelude::AsRawFd;
|
||||
|
||||
pub(crate) fn fd_readdir(
|
||||
os_file: &mut OsFile,
|
||||
host_buf: &mut [u8],
|
||||
cookie: host::__wasi_dircookie_t,
|
||||
) -> Result<usize> {
|
||||
use crate::sys::unix::bsd::osfile::DirStream;
|
||||
use libc::{fdopendir, memcpy, readdir, rewinddir, seekdir, telldir};
|
||||
use nix::errno::Errno;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::sync::Mutex;
|
||||
|
||||
let dir_stream = match os_file.dir_stream {
|
||||
Some(ref mut dir_stream) => dir_stream,
|
||||
None => {
|
||||
let file = os_file.file.try_clone()?;
|
||||
let dir_ptr = unsafe { fdopendir(file.as_raw_fd()) };
|
||||
os_file.dir_stream.get_or_insert(Mutex::new(DirStream {
|
||||
file: ManuallyDrop::new(file),
|
||||
dir_ptr,
|
||||
}))
|
||||
}
|
||||
};
|
||||
let dir_stream = dir_stream.lock().unwrap();
|
||||
|
||||
let host_buf_ptr = host_buf.as_mut_ptr();
|
||||
let host_buf_len = host_buf.len();
|
||||
|
||||
if cookie != host::__WASI_DIRCOOKIE_START {
|
||||
unsafe { seekdir(dir_stream.dir_ptr, cookie as c_long) };
|
||||
} else {
|
||||
unsafe { rewinddir(dir_stream.dir_ptr) };
|
||||
}
|
||||
|
||||
let mut left = host_buf_len;
|
||||
let mut host_buf_offset: usize = 0;
|
||||
|
||||
loop {
|
||||
let host_entry = unsafe { readdir(dir_stream.dir_ptr) };
|
||||
if host_entry.is_null() {
|
||||
// FIXME
|
||||
// Currently, these are verified to be correct on macOS.
|
||||
// Need to still verify these on other BSD-based OSes.
|
||||
match Errno::last() {
|
||||
Errno::EBADF => return Err(Error::EBADF),
|
||||
Errno::EFAULT => return Err(Error::EFAULT),
|
||||
Errno::EIO => return Err(Error::EIO),
|
||||
_ => break, // not an error
|
||||
}
|
||||
}
|
||||
|
||||
let mut entry: host::__wasi_dirent_t =
|
||||
host_impl::dirent_from_host(&unsafe { *host_entry })?;
|
||||
// Set d_next manually:
|
||||
// * on macOS d_seekoff is not set for some reason
|
||||
// * on FreeBSD d_seekoff doesn't exist; there is d_off but it is
|
||||
// not equivalent to the value read from telldir call
|
||||
entry.d_next = unsafe { telldir(dir_stream.dir_ptr) } as host::__wasi_dircookie_t;
|
||||
|
||||
log::debug!("fd_readdir entry = {:?}", entry);
|
||||
|
||||
let name_len = entry.d_namlen.try_into()?;
|
||||
let required_space = std::mem::size_of_val(&entry) + name_len;
|
||||
if required_space > left {
|
||||
break;
|
||||
}
|
||||
unsafe {
|
||||
let ptr = host_buf_ptr.offset(host_buf_offset.try_into()?) as *mut c_void
|
||||
as *mut host::__wasi_dirent_t;
|
||||
*ptr = entry;
|
||||
}
|
||||
host_buf_offset += std::mem::size_of_val(&entry);
|
||||
let name_ptr = unsafe { *host_entry }.d_name.as_ptr();
|
||||
unsafe {
|
||||
memcpy(
|
||||
host_buf_ptr.offset(host_buf_offset.try_into()?) as *mut _,
|
||||
name_ptr as *const _,
|
||||
name_len,
|
||||
)
|
||||
};
|
||||
host_buf_offset += name_len;
|
||||
left -= required_space;
|
||||
}
|
||||
|
||||
Ok(host_buf_len - left)
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
pub(crate) fn fd_advise(
|
||||
file: &File,
|
||||
advice: host::__wasi_advice_t,
|
||||
offset: host::__wasi_filesize_t,
|
||||
len: host::__wasi_filesize_t,
|
||||
) -> Result<()> {
|
||||
use nix::errno::Errno;
|
||||
|
||||
match advice {
|
||||
host::__WASI_ADVICE_DONTNEED => return Ok(()),
|
||||
// unfortunately, the advisory syscall in macOS doesn't take any flags of this
|
||||
// sort (unlike on Linux), hence, they are left here as a noop
|
||||
host::__WASI_ADVICE_SEQUENTIAL
|
||||
| host::__WASI_ADVICE_WILLNEED
|
||||
| host::__WASI_ADVICE_NOREUSE
|
||||
| host::__WASI_ADVICE_RANDOM
|
||||
| host::__WASI_ADVICE_NORMAL => {}
|
||||
_ => return Err(Error::EINVAL),
|
||||
}
|
||||
|
||||
// From macOS man pages:
|
||||
// F_RDADVISE Issue an advisory read async with no copy to user.
|
||||
//
|
||||
// The F_RDADVISE command operates on the following structure which holds information passed from
|
||||
// the user to the system:
|
||||
//
|
||||
// struct radvisory {
|
||||
// off_t ra_offset; /* offset into the file */
|
||||
// int ra_count; /* size of the read */
|
||||
// };
|
||||
let advisory = libc::radvisory {
|
||||
ra_offset: offset.try_into()?,
|
||||
ra_count: len.try_into()?,
|
||||
};
|
||||
|
||||
let res = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_RDADVISE, &advisory) };
|
||||
Errno::result(res).map(|_| ()).map_err(Error::from)
|
||||
}
|
||||
|
||||
// TODO
|
||||
// It seems that at least some BSDs do support `posix_fadvise`,
|
||||
// so we should investigate further.
|
||||
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
|
||||
pub(crate) fn fd_advise(
|
||||
_file: &File,
|
||||
advice: host::__wasi_advice_t,
|
||||
_offset: host::__wasi_filesize_t,
|
||||
_len: host::__wasi_filesize_t,
|
||||
) -> Result<()> {
|
||||
match advice {
|
||||
host::__WASI_ADVICE_DONTNEED
|
||||
| host::__WASI_ADVICE_SEQUENTIAL
|
||||
| host::__WASI_ADVICE_WILLNEED
|
||||
| host::__WASI_ADVICE_NOREUSE
|
||||
| host::__WASI_ADVICE_RANDOM
|
||||
| host::__WASI_ADVICE_NORMAL => {}
|
||||
_ => return Err(Error::EINVAL),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
2
src/sys/unix/bsd/mod.rs
Normal file
2
src/sys/unix/bsd/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub(crate) mod hostcalls_impl;
|
||||
pub(crate) mod osfile;
|
||||
52
src/sys/unix/bsd/osfile.rs
Normal file
52
src/sys/unix/bsd/osfile.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use std::fs;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::os::unix::prelude::{AsRawFd, RawFd};
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DirStream {
|
||||
pub(crate) file: ManuallyDrop<fs::File>,
|
||||
pub(crate) dir_ptr: *mut libc::DIR,
|
||||
}
|
||||
|
||||
impl Drop for DirStream {
|
||||
fn drop(&mut self) {
|
||||
unsafe { libc::closedir(self.dir_ptr) };
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct OsFile {
|
||||
pub(crate) file: fs::File,
|
||||
pub(crate) dir_stream: Option<Mutex<DirStream>>,
|
||||
}
|
||||
|
||||
impl From<fs::File> for OsFile {
|
||||
fn from(file: fs::File) -> Self {
|
||||
Self {
|
||||
file,
|
||||
dir_stream: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for OsFile {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.file.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for OsFile {
|
||||
type Target = fs::File;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.file
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for OsFile {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.file
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,25 @@ use crate::{host, Error, Result};
|
||||
use std::io;
|
||||
use std::os::unix::prelude::{AsRawFd, FileTypeExt, FromRawFd, RawFd};
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
pub(crate) use super::linux::osfile::*;
|
||||
} else if #[cfg(any(
|
||||
target_os = "macos",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "ios",
|
||||
target_os = "dragonfly"
|
||||
))] {
|
||||
pub(crate) use super::bsd::osfile::*;
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Descriptor {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
match self {
|
||||
Descriptor::File(f) => f.as_raw_fd(),
|
||||
Descriptor::OsFile(file) => file.as_raw_fd(),
|
||||
Descriptor::Stdin => io::stdin().as_raw_fd(),
|
||||
Descriptor::Stdout => io::stdout().as_raw_fd(),
|
||||
Descriptor::Stderr => io::stderr().as_raw_fd(),
|
||||
|
||||
@@ -5,13 +5,28 @@ use crate::helpers::systemtime_to_timestamp;
|
||||
use crate::hostcalls_impl::PathGet;
|
||||
use crate::sys::host_impl;
|
||||
use crate::{host, Error, Result};
|
||||
use nix::libc::{self, c_long, c_void};
|
||||
use nix::libc;
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::CString;
|
||||
use std::fs::{File, Metadata};
|
||||
use std::os::unix::fs::FileExt;
|
||||
use std::os::unix::prelude::{AsRawFd, FromRawFd};
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
pub(crate) use super::super::linux::hostcalls_impl::*;
|
||||
} else if #[cfg(any(
|
||||
target_os = "macos",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "ios",
|
||||
target_os = "dragonfly"
|
||||
))] {
|
||||
pub(crate) use super::super::bsd::hostcalls_impl::*;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn fd_pread(
|
||||
file: &File,
|
||||
buf: &mut [u8],
|
||||
@@ -41,94 +56,6 @@ pub(crate) fn fd_fdstat_set_flags(fd: &File, fdflags: host::__wasi_fdflags_t) ->
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(crate) fn fd_advise(
|
||||
file: &File,
|
||||
advice: host::__wasi_advice_t,
|
||||
offset: host::__wasi_filesize_t,
|
||||
len: host::__wasi_filesize_t,
|
||||
) -> Result<()> {
|
||||
{
|
||||
use nix::fcntl::{posix_fadvise, PosixFadviseAdvice};
|
||||
|
||||
let offset = offset.try_into()?;
|
||||
let len = len.try_into()?;
|
||||
let host_advice = match advice {
|
||||
host::__WASI_ADVICE_DONTNEED => PosixFadviseAdvice::POSIX_FADV_DONTNEED,
|
||||
host::__WASI_ADVICE_SEQUENTIAL => PosixFadviseAdvice::POSIX_FADV_SEQUENTIAL,
|
||||
host::__WASI_ADVICE_WILLNEED => PosixFadviseAdvice::POSIX_FADV_WILLNEED,
|
||||
host::__WASI_ADVICE_NOREUSE => PosixFadviseAdvice::POSIX_FADV_NOREUSE,
|
||||
host::__WASI_ADVICE_RANDOM => PosixFadviseAdvice::POSIX_FADV_RANDOM,
|
||||
host::__WASI_ADVICE_NORMAL => PosixFadviseAdvice::POSIX_FADV_NORMAL,
|
||||
_ => return Err(Error::EINVAL),
|
||||
};
|
||||
|
||||
posix_fadvise(file.as_raw_fd(), offset, len, host_advice)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||
pub(crate) fn fd_advise(
|
||||
_file: &File,
|
||||
advice: host::__wasi_advice_t,
|
||||
_offset: host::__wasi_filesize_t,
|
||||
_len: host::__wasi_filesize_t,
|
||||
) -> Result<()> {
|
||||
match advice {
|
||||
host::__WASI_ADVICE_DONTNEED
|
||||
| host::__WASI_ADVICE_SEQUENTIAL
|
||||
| host::__WASI_ADVICE_WILLNEED
|
||||
| host::__WASI_ADVICE_NOREUSE
|
||||
| host::__WASI_ADVICE_RANDOM
|
||||
| host::__WASI_ADVICE_NORMAL => {}
|
||||
_ => return Err(Error::EINVAL),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) fn fd_advise(
|
||||
file: &File,
|
||||
advice: host::__wasi_advice_t,
|
||||
offset: host::__wasi_filesize_t,
|
||||
len: host::__wasi_filesize_t,
|
||||
) -> Result<()> {
|
||||
use nix::errno::Errno;
|
||||
|
||||
match advice {
|
||||
host::__WASI_ADVICE_DONTNEED => return Ok(()),
|
||||
// unfortunately, the advisory syscall in macOS doesn't take any flags of this
|
||||
// sort (unlike on Linux), hence, they are left here as a noop
|
||||
host::__WASI_ADVICE_SEQUENTIAL
|
||||
| host::__WASI_ADVICE_WILLNEED
|
||||
| host::__WASI_ADVICE_NOREUSE
|
||||
| host::__WASI_ADVICE_RANDOM
|
||||
| host::__WASI_ADVICE_NORMAL => {}
|
||||
_ => return Err(Error::EINVAL),
|
||||
}
|
||||
|
||||
// From macOS man pages:
|
||||
// F_RDADVISE Issue an advisory read async with no copy to user.
|
||||
//
|
||||
// The F_RDADVISE command operates on the following structure which holds information passed from
|
||||
// the user to the system:
|
||||
//
|
||||
// struct radvisory {
|
||||
// off_t ra_offset; /* offset into the file */
|
||||
// int ra_count; /* size of the read */
|
||||
// };
|
||||
let advisory = libc::radvisory {
|
||||
ra_offset: offset.try_into()?,
|
||||
ra_count: len.try_into()?,
|
||||
};
|
||||
|
||||
let res = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_RDADVISE, &advisory) };
|
||||
Errno::result(res).map(|_| ()).map_err(Error::from)
|
||||
}
|
||||
|
||||
pub(crate) fn path_create_directory(resolved: PathGet) -> Result<()> {
|
||||
use nix::libc::mkdirat;
|
||||
let path_cstr = CString::new(resolved.path().as_bytes()).map_err(|_| Error::EILSEQ)?;
|
||||
@@ -249,66 +176,6 @@ pub(crate) fn path_open(
|
||||
Ok(unsafe { File::from_raw_fd(new_fd) })
|
||||
}
|
||||
|
||||
pub(crate) fn fd_readdir(
|
||||
fd: &File,
|
||||
host_buf: &mut [u8],
|
||||
cookie: host::__wasi_dircookie_t,
|
||||
) -> Result<usize> {
|
||||
use libc::{dirent, fdopendir, memcpy, readdir_r, rewinddir, seekdir};
|
||||
|
||||
let host_buf_ptr = host_buf.as_mut_ptr();
|
||||
let host_buf_len = host_buf.len();
|
||||
let dir = unsafe { fdopendir(fd.as_raw_fd()) };
|
||||
if dir.is_null() {
|
||||
return Err(host_impl::errno_from_nix(nix::errno::Errno::last()));
|
||||
}
|
||||
|
||||
if cookie != host::__WASI_DIRCOOKIE_START {
|
||||
unsafe { seekdir(dir, cookie as c_long) };
|
||||
} else {
|
||||
// If cookie set to __WASI_DIRCOOKIE_START, rewind the dir ptr
|
||||
// to the start of the stream.
|
||||
unsafe { rewinddir(dir) };
|
||||
}
|
||||
|
||||
let mut entry_buf = unsafe { std::mem::uninitialized::<dirent>() };
|
||||
let mut left = host_buf_len;
|
||||
let mut host_buf_offset: usize = 0;
|
||||
while left > 0 {
|
||||
let mut host_entry: *mut dirent = std::ptr::null_mut();
|
||||
let res = unsafe { readdir_r(dir, &mut entry_buf, &mut host_entry) };
|
||||
if res == -1 {
|
||||
return Err(host_impl::errno_from_nix(nix::errno::Errno::last()));
|
||||
}
|
||||
if host_entry.is_null() {
|
||||
break;
|
||||
}
|
||||
let entry: host::__wasi_dirent_t = host_impl::dirent_from_host(&unsafe { *host_entry })?;
|
||||
let name_len = entry.d_namlen as usize;
|
||||
let required_space = std::mem::size_of_val(&entry) + name_len;
|
||||
if required_space > left {
|
||||
break;
|
||||
}
|
||||
unsafe {
|
||||
let ptr =
|
||||
host_buf_ptr.add(host_buf_offset) as *mut c_void as *mut host::__wasi_dirent_t;
|
||||
*ptr = entry;
|
||||
}
|
||||
host_buf_offset += std::mem::size_of_val(&entry);
|
||||
let name_ptr = unsafe { *host_entry }.d_name.as_ptr();
|
||||
unsafe {
|
||||
memcpy(
|
||||
host_buf_ptr.add(host_buf_offset) as *mut _,
|
||||
name_ptr as *const _,
|
||||
name_len,
|
||||
)
|
||||
};
|
||||
host_buf_offset += name_len;
|
||||
left -= required_space;
|
||||
}
|
||||
Ok(host_buf_len - left)
|
||||
}
|
||||
|
||||
pub(crate) fn path_readlink(resolved: PathGet, buf: &mut [u8]) -> Result<usize> {
|
||||
use nix::errno::Errno;
|
||||
let path_cstr = CString::new(resolved.path().as_bytes()).map_err(|_| Error::EILSEQ)?;
|
||||
|
||||
104
src/sys/unix/linux/hostcalls_impl.rs
Normal file
104
src/sys/unix/linux/hostcalls_impl.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use super::osfile::OsFile;
|
||||
use crate::sys::host_impl;
|
||||
use crate::{host, Error, Result};
|
||||
use nix::libc::{self, c_long, c_void};
|
||||
use std::convert::TryInto;
|
||||
use std::fs::File;
|
||||
use std::os::unix::prelude::AsRawFd;
|
||||
|
||||
pub(crate) fn fd_readdir(
|
||||
os_file: &mut OsFile,
|
||||
host_buf: &mut [u8],
|
||||
cookie: host::__wasi_dircookie_t,
|
||||
) -> Result<usize> {
|
||||
use libc::{dirent, fdopendir, memcpy, readdir_r, rewinddir, seekdir};
|
||||
|
||||
let host_buf_ptr = host_buf.as_mut_ptr();
|
||||
let host_buf_len = host_buf.len();
|
||||
let dir = unsafe { fdopendir(os_file.as_raw_fd()) };
|
||||
if dir.is_null() {
|
||||
return Err(host_impl::errno_from_nix(nix::errno::Errno::last()));
|
||||
}
|
||||
|
||||
if cookie != host::__WASI_DIRCOOKIE_START {
|
||||
unsafe { seekdir(dir, cookie as c_long) };
|
||||
} else {
|
||||
// If cookie set to __WASI_DIRCOOKIE_START, rewind the dir ptr
|
||||
// to the start of the stream.
|
||||
unsafe { rewinddir(dir) };
|
||||
}
|
||||
|
||||
let mut entry_buf = unsafe { std::mem::uninitialized::<dirent>() };
|
||||
let mut left = host_buf_len;
|
||||
let mut host_buf_offset: usize = 0;
|
||||
while left > 0 {
|
||||
let mut host_entry: *mut dirent = std::ptr::null_mut();
|
||||
|
||||
// TODO
|
||||
// `readdir_r` syscall is being deprecated so we should look into
|
||||
// replacing it with `readdir` call instead.
|
||||
// Also, `readdir_r` returns a positive int on failure, and doesn't
|
||||
// set the errno.
|
||||
let res = unsafe { readdir_r(dir, &mut entry_buf, &mut host_entry) };
|
||||
if res == -1 {
|
||||
return Err(host_impl::errno_from_nix(nix::errno::Errno::last()));
|
||||
}
|
||||
if host_entry.is_null() {
|
||||
break;
|
||||
}
|
||||
let entry: host::__wasi_dirent_t = host_impl::dirent_from_host(&unsafe { *host_entry })?;
|
||||
|
||||
log::debug!("fd_readdir entry = {:?}", entry);
|
||||
|
||||
let name_len = entry.d_namlen.try_into()?;
|
||||
let required_space = std::mem::size_of_val(&entry) + name_len;
|
||||
if required_space > left {
|
||||
break;
|
||||
}
|
||||
unsafe {
|
||||
let ptr = host_buf_ptr.offset(host_buf_offset.try_into()?) as *mut c_void
|
||||
as *mut host::__wasi_dirent_t;
|
||||
*ptr = entry;
|
||||
}
|
||||
host_buf_offset += std::mem::size_of_val(&entry);
|
||||
let name_ptr = unsafe { *host_entry }.d_name.as_ptr();
|
||||
unsafe {
|
||||
memcpy(
|
||||
host_buf_ptr.offset(host_buf_offset.try_into()?) as *mut _,
|
||||
name_ptr as *const _,
|
||||
name_len,
|
||||
)
|
||||
};
|
||||
host_buf_offset += name_len;
|
||||
left -= required_space;
|
||||
}
|
||||
|
||||
Ok(host_buf_len - left)
|
||||
}
|
||||
|
||||
pub(crate) fn fd_advise(
|
||||
file: &File,
|
||||
advice: host::__wasi_advice_t,
|
||||
offset: host::__wasi_filesize_t,
|
||||
len: host::__wasi_filesize_t,
|
||||
) -> Result<()> {
|
||||
{
|
||||
use nix::fcntl::{posix_fadvise, PosixFadviseAdvice};
|
||||
|
||||
let offset = offset.try_into()?;
|
||||
let len = len.try_into()?;
|
||||
let host_advice = match advice {
|
||||
host::__WASI_ADVICE_DONTNEED => PosixFadviseAdvice::POSIX_FADV_DONTNEED,
|
||||
host::__WASI_ADVICE_SEQUENTIAL => PosixFadviseAdvice::POSIX_FADV_SEQUENTIAL,
|
||||
host::__WASI_ADVICE_WILLNEED => PosixFadviseAdvice::POSIX_FADV_WILLNEED,
|
||||
host::__WASI_ADVICE_NOREUSE => PosixFadviseAdvice::POSIX_FADV_NOREUSE,
|
||||
host::__WASI_ADVICE_RANDOM => PosixFadviseAdvice::POSIX_FADV_RANDOM,
|
||||
host::__WASI_ADVICE_NORMAL => PosixFadviseAdvice::POSIX_FADV_NORMAL,
|
||||
_ => return Err(Error::EINVAL),
|
||||
};
|
||||
|
||||
posix_fadvise(file.as_raw_fd(), offset, len, host_advice)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
2
src/sys/unix/linux/mod.rs
Normal file
2
src/sys/unix/linux/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub(crate) mod hostcalls_impl;
|
||||
pub(crate) mod osfile;
|
||||
32
src/sys/unix/linux/osfile.rs
Normal file
32
src/sys/unix/linux/osfile.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use std::fs;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::os::unix::prelude::{AsRawFd, RawFd};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct OsFile(fs::File);
|
||||
|
||||
impl From<fs::File> for OsFile {
|
||||
fn from(file: fs::File) -> Self {
|
||||
Self(file)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for OsFile {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.0.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for OsFile {
|
||||
type Target = fs::File;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for OsFile {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,18 @@ pub(crate) mod fdentry_impl;
|
||||
pub(crate) mod host_impl;
|
||||
pub(crate) mod hostcalls_impl;
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "macos",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "ios",
|
||||
target_os = "dragonfly"
|
||||
))]
|
||||
mod bsd;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
|
||||
use crate::Result;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
|
||||
@@ -2,12 +2,42 @@ 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::File(f) => f.as_raw_handle(),
|
||||
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(),
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::ctx::WasiCtx;
|
||||
use crate::fdentry::FdEntry;
|
||||
use crate::helpers::systemtime_to_timestamp;
|
||||
use crate::hostcalls_impl::{fd_filestat_set_times_impl, PathGet};
|
||||
use crate::sys::fdentry_impl::determine_type_rights;
|
||||
use crate::sys::fdentry_impl::{determine_type_rights, OsFile};
|
||||
use crate::sys::host_impl;
|
||||
use crate::sys::hostcalls_impl::fs_helpers::PathGetExt;
|
||||
use crate::{host, Error, Result};
|
||||
@@ -167,7 +167,7 @@ pub(crate) fn path_open(
|
||||
}
|
||||
|
||||
pub(crate) fn fd_readdir(
|
||||
fd: &File,
|
||||
fd: &mut OsFile,
|
||||
host_buf: &mut [u8],
|
||||
cookie: host::__wasi_dircookie_t,
|
||||
) -> Result<usize> {
|
||||
|
||||
Reference in New Issue
Block a user