Fix bug in fd_readdir impl on BSD
This commit fixes a subtle bug in `fd_readdir` implementation on BSD-style nixes. In particular, the bug only resurfaced when testing in release mode, and I can only assume it was due to a unlucky combination of compiler optimizations and at first sight correct casting of `d_name` pointer from `const* i8` to `const* u8`. This is now fixed by first converting `d_name` to `std::str` by using `std::ffi::CStr::to_str`, and then using the resultant `std::str` to copy the properly validated `u8` contents into the Wasm buffer. Furthermore, this commit fixes incorrect handling of the `readdir` loop break condition. Although undocumented in BSD man pages, the signalling is the same as on Linux where a null ptr returned by `readdir` signals the end of the dir stream when the errno code has not changed since before calling `readdir` inside a loop. Upon a fault such as an invalid file descriptor, the errno *will* change after executing `readdir`.
This commit is contained in:
@@ -147,6 +147,7 @@ pub(crate) fn fd_readdir(
|
|||||||
use crate::sys::unix::bsd::osfile::DirStream;
|
use crate::sys::unix::bsd::osfile::DirStream;
|
||||||
use libc::{fdopendir, readdir, rewinddir, seekdir, telldir};
|
use libc::{fdopendir, readdir, rewinddir, seekdir, telldir};
|
||||||
use nix::errno::Errno;
|
use nix::errno::Errno;
|
||||||
|
use std::ffi::CStr;
|
||||||
use std::mem::ManuallyDrop;
|
use std::mem::ManuallyDrop;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
@@ -176,45 +177,53 @@ pub(crate) fn fd_readdir(
|
|||||||
let mut host_buf_offset: usize = 0;
|
let mut host_buf_offset: usize = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let host_entry = unsafe { readdir(dir_stream.dir_ptr) };
|
let errno = Errno::last();
|
||||||
if host_entry.is_null() {
|
let host_entry_ptr = unsafe { readdir(dir_stream.dir_ptr) };
|
||||||
// FIXME
|
if host_entry_ptr.is_null() {
|
||||||
// Currently, these are verified to be correct on macOS.
|
if errno != Errno::last() {
|
||||||
// Need to still verify these on other BSD-based OSes.
|
// TODO Is this correct?
|
||||||
match Errno::last() {
|
// According to POSIX man (for Linux though!), there was an error
|
||||||
Errno::EBADF => return Err(Error::EBADF),
|
// if the errno value has changed at some point during the sequence
|
||||||
Errno::EFAULT => return Err(Error::EFAULT),
|
// of readdir calls
|
||||||
Errno::EIO => return Err(Error::EIO),
|
return Err(host_impl::errno_from_nix(Errno::last()));
|
||||||
_ => break, // not an error
|
} else {
|
||||||
|
// Not an error
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut entry: wasi::__wasi_dirent_t =
|
let host_entry = unsafe { *host_entry_ptr };
|
||||||
host_impl::dirent_from_host(&unsafe { *host_entry })?;
|
let mut wasi_entry: wasi::__wasi_dirent_t = host_impl::dirent_from_host(&host_entry)?;
|
||||||
// Set d_next manually:
|
// Set d_next manually:
|
||||||
// * on macOS d_seekoff is not set for some reason
|
// * on macOS d_seekoff is not set for some reason
|
||||||
// * on FreeBSD d_seekoff doesn't exist; there is d_off but it is
|
// * on FreeBSD d_seekoff doesn't exist; there is d_off but it is
|
||||||
// not equivalent to the value read from telldir call
|
// not equivalent to the value read from telldir call
|
||||||
entry.d_next = unsafe { telldir(dir_stream.dir_ptr) } as wasi::__wasi_dircookie_t;
|
wasi_entry.d_next = unsafe { telldir(dir_stream.dir_ptr) } as wasi::__wasi_dircookie_t;
|
||||||
|
|
||||||
log::debug!("fd_readdir entry = {:?}", entry);
|
log::debug!("fd_readdir host_entry = {:?}", host_entry);
|
||||||
|
log::debug!("fd_readdir wasi_entry = {:?}", wasi_entry);
|
||||||
|
|
||||||
|
let name_len = host_entry.d_namlen.try_into()?;
|
||||||
|
let required_space = std::mem::size_of_val(&wasi_entry) + name_len;
|
||||||
|
|
||||||
let name_len = entry.d_namlen.try_into()?;
|
|
||||||
let required_space = std::mem::size_of_val(&entry) + name_len;
|
|
||||||
if required_space > left {
|
if required_space > left {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let name = unsafe { CStr::from_ptr(host_entry.d_name.as_ptr()) }.to_str()?;
|
||||||
|
log::debug!("fd_readdir entry name = {}", name);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = host_buf_ptr.offset(host_buf_offset.try_into()?) as *mut c_void
|
let ptr = host_buf_ptr.offset(host_buf_offset.try_into()?) as *mut c_void
|
||||||
as *mut wasi::__wasi_dirent_t;
|
as *mut wasi::__wasi_dirent_t;
|
||||||
*ptr = entry;
|
*ptr = wasi_entry;
|
||||||
}
|
}
|
||||||
host_buf_offset += std::mem::size_of_val(&entry);
|
host_buf_offset += std::mem::size_of_val(&wasi_entry);
|
||||||
let name_ptr = unsafe { *host_entry }.d_name.as_ptr();
|
|
||||||
unsafe {
|
unsafe {
|
||||||
std::ptr::copy_nonoverlapping(
|
std::ptr::copy_nonoverlapping(
|
||||||
name_ptr as *const _,
|
name.as_ptr(),
|
||||||
host_buf_ptr.offset(host_buf_offset.try_into()?) as *mut _,
|
host_buf_ptr.offset(host_buf_offset.try_into()?),
|
||||||
name_len,
|
name_len,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user