diff --git a/build.rs b/build.rs index e3224e3ac5..c7113d5a29 100644 --- a/build.rs +++ b/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, diff --git a/src/fdentry.rs b/src/fdentry.rs index 2d7354778d..99c4bf6297 100644 --- a/src/fdentry.rs +++ b/src/fdentry.rs @@ -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, - pub needs_close: bool, +pub(crate) struct FdObject { + pub(crate) file_type: host::__wasi_filetype_t, + pub(crate) descriptor: ManuallyDrop, + 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, +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, } impl Drop for FdObject { @@ -75,12 +84,12 @@ impl Drop for FdObject { } impl FdEntry { - pub fn from(file: fs::File) -> Result { - fdentry_impl::determine_type_and_access_rights(&file).map( + pub(crate) fn from(file: fs::File) -> Result { + 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 { + pub(crate) fn duplicate(file: &fs::File) -> Result { Self::from(file.try_clone()?) } - pub fn duplicate_stdin() -> Result { - fdentry_impl::determine_type_and_access_rights(&io::stdin()).map( + pub(crate) fn duplicate_stdin() -> Result { + 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 { - fdentry_impl::determine_type_and_access_rights(&io::stdout()).map( + pub(crate) fn duplicate_stdout() -> Result { + 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 { - fdentry_impl::determine_type_and_access_rights(&io::stderr()).map( + pub(crate) fn duplicate_stderr() -> Result { + determine_type_and_access_rights(&io::stderr()).map( |(file_type, rights_base, rights_inheriting)| Self { fd_object: FdObject { file_type, diff --git a/src/hostcalls/fs.rs b/src/hostcalls/fs.rs index 9163c8e428..49c67981de 100644 --- a/src/hostcalls/fs.rs +++ b/src/hostcalls/fs.rs @@ -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, diff --git a/src/hostcalls_impl/fs.rs b/src/hostcalls_impl/fs.rs index f18fe4bbee..064efa3199 100644 --- a/src/hostcalls_impl/fs.rs +++ b/src/hostcalls_impl/fs.rs @@ -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::(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); diff --git a/src/sys/unix/bsd/hostcalls_impl.rs b/src/sys/unix/bsd/hostcalls_impl.rs new file mode 100644 index 0000000000..324e720bbd --- /dev/null +++ b/src/sys/unix/bsd/hostcalls_impl.rs @@ -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 { + 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(()) +} diff --git a/src/sys/unix/bsd/mod.rs b/src/sys/unix/bsd/mod.rs new file mode 100644 index 0000000000..89e059ef9e --- /dev/null +++ b/src/sys/unix/bsd/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod hostcalls_impl; +pub(crate) mod osfile; diff --git a/src/sys/unix/bsd/osfile.rs b/src/sys/unix/bsd/osfile.rs new file mode 100644 index 0000000000..41340149aa --- /dev/null +++ b/src/sys/unix/bsd/osfile.rs @@ -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, + 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>, +} + +impl From 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 + } +} diff --git a/src/sys/unix/fdentry_impl.rs b/src/sys/unix/fdentry_impl.rs index 4744794768..5b37c508fc 100644 --- a/src/sys/unix/fdentry_impl.rs +++ b/src/sys/unix/fdentry_impl.rs @@ -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(), diff --git a/src/sys/unix/hostcalls_impl/fs.rs b/src/sys/unix/hostcalls_impl/fs.rs index 0b60bcaca4..2deb820559 100644 --- a/src/sys/unix/hostcalls_impl/fs.rs +++ b/src/sys/unix/hostcalls_impl/fs.rs @@ -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 { - 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::() }; - 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 { use nix::errno::Errno; let path_cstr = CString::new(resolved.path().as_bytes()).map_err(|_| Error::EILSEQ)?; diff --git a/src/sys/unix/linux/hostcalls_impl.rs b/src/sys/unix/linux/hostcalls_impl.rs new file mode 100644 index 0000000000..bd9508c8f8 --- /dev/null +++ b/src/sys/unix/linux/hostcalls_impl.rs @@ -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 { + 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::() }; + 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(()) +} diff --git a/src/sys/unix/linux/mod.rs b/src/sys/unix/linux/mod.rs new file mode 100644 index 0000000000..89e059ef9e --- /dev/null +++ b/src/sys/unix/linux/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod hostcalls_impl; +pub(crate) mod osfile; diff --git a/src/sys/unix/linux/osfile.rs b/src/sys/unix/linux/osfile.rs new file mode 100644 index 0000000000..b92a9793a6 --- /dev/null +++ b/src/sys/unix/linux/osfile.rs @@ -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 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 + } +} diff --git a/src/sys/unix/mod.rs b/src/sys/unix/mod.rs index d2f53e02c1..c69c587dea 100644 --- a/src/sys/unix/mod.rs +++ b/src/sys/unix/mod.rs @@ -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; diff --git a/src/sys/windows/fdentry_impl.rs b/src/sys/windows/fdentry_impl.rs index 9c4d1209d7..5daaeeee7a 100644 --- a/src/sys/windows/fdentry_impl.rs +++ b/src/sys/windows/fdentry_impl.rs @@ -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 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(), diff --git a/src/sys/windows/hostcalls_impl/fs.rs b/src/sys/windows/hostcalls_impl/fs.rs index ec1c703f05..32286e887c 100644 --- a/src/sys/windows/hostcalls_impl/fs.rs +++ b/src/sys/windows/hostcalls_impl/fs.rs @@ -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 {