diff --git a/crates/wasi-common/Cargo.toml b/crates/wasi-common/Cargo.toml index 8793e5dfc3..f9d0f08ec2 100644 --- a/crates/wasi-common/Cargo.toml +++ b/crates/wasi-common/Cargo.toml @@ -24,7 +24,7 @@ num = { version = "0.2.0", default-features = false } wig = { path = "wig" } [target.'cfg(unix)'.dependencies] -nix = "0.15" +yanix = { path = "yanix" } [target.'cfg(windows)'.dependencies] winx = { path = "winx" } diff --git a/crates/wasi-common/src/error.rs b/crates/wasi-common/src/error.rs index e1728e504a..f6fa7be1da 100644 --- a/crates/wasi-common/src/error.rs +++ b/crates/wasi-common/src/error.rs @@ -3,7 +3,7 @@ use crate::wasi; use std::convert::Infallible; use std::num::TryFromIntError; -use std::{fmt, str}; +use std::{ffi, fmt, str}; use thiserror::Error; #[derive(Clone, Copy, Debug, Error, Eq, PartialEq)] @@ -107,7 +107,7 @@ pub enum Error { Wasi(WasiError), Io(std::io::Error), #[cfg(unix)] - Nix(nix::Error), + Nix(yanix::YanixError), #[cfg(windows)] Win(winx::winerror::WinError), } @@ -119,8 +119,8 @@ impl From for Error { } #[cfg(unix)] -impl From for Error { - fn from(err: nix::Error) -> Self { +impl From for Error { + fn from(err: yanix::YanixError) -> Self { Self::Nix(err) } } @@ -149,6 +149,12 @@ impl From for Error { } } +impl From<&ffi::NulError> for Error { + fn from(_: &ffi::NulError) -> Self { + Self::Wasi(WasiError::EILSEQ) + } +} + #[cfg(windows)] impl From for Error { fn from(err: winx::winerror::WinError) -> Self { @@ -162,16 +168,15 @@ impl Error { Self::Wasi(no) => no.as_raw_errno(), Self::Io(e) => errno_from_ioerror(e.to_owned()), #[cfg(unix)] - Self::Nix(err) => err - .as_errno() - .map_or_else( - || { - log::debug!("Unknown nix errno: {}", err); - Self::ENOSYS - }, - crate::sys::host_impl::errno_from_nix, - ) - .as_wasi_errno(), + Self::Nix(err) => { + use yanix::YanixError::*; + let err = match err { + Errno(errno) => crate::sys::host_impl::errno_from_nix(*errno), + NulError(err) => err.into(), + TryFromIntError(err) => (*err).into(), + }; + err.as_wasi_errno() + } #[cfg(windows)] Self::Win(err) => crate::sys::host_impl::errno_from_win(*err), } diff --git a/crates/wasi-common/src/host.rs b/crates/wasi-common/src/host.rs index 4e37d12c40..8af57ec476 100644 --- a/crates/wasi-common/src/host.rs +++ b/crates/wasi-common/src/host.rs @@ -5,7 +5,8 @@ #![allow(non_snake_case)] use crate::wasi::*; -use std::{io, slice}; +use crate::{Error, Result}; +use std::{convert::TryInto, io, mem, slice}; use wig::witx_host_types; witx_host_types!("snapshot" "wasi_snapshot_preview1"); @@ -20,6 +21,64 @@ pub(crate) unsafe fn iovec_to_host_mut(iovec: &mut __wasi_iovec_t) -> io::IoSlic io::IoSliceMut::new(slice) } +#[allow(dead_code)] // trouble with sockets +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +pub(crate) enum FileType { + Unknown = __WASI_FILETYPE_UNKNOWN, + BlockDevice = __WASI_FILETYPE_BLOCK_DEVICE, + CharacterDevice = __WASI_FILETYPE_CHARACTER_DEVICE, + Directory = __WASI_FILETYPE_DIRECTORY, + RegularFile = __WASI_FILETYPE_REGULAR_FILE, + SocketDgram = __WASI_FILETYPE_SOCKET_DGRAM, + SocketStream = __WASI_FILETYPE_SOCKET_STREAM, + Symlink = __WASI_FILETYPE_SYMBOLIC_LINK, +} + +impl FileType { + pub(crate) fn to_wasi(&self) -> __wasi_filetype_t { + *self as __wasi_filetype_t + } +} + +#[derive(Debug, Clone)] +pub(crate) struct Dirent { + pub name: String, + pub ftype: FileType, + pub ino: u64, + pub cookie: __wasi_dircookie_t, +} + +impl Dirent { + /// Serialize the directory entry to the format define by `__wasi_fd_readdir`, + /// so that the serialized entries can be concatenated by the implementation. + pub fn to_wasi_raw(&self) -> Result> { + let name = self.name.as_bytes(); + let namlen = name.len(); + let dirent_size = mem::size_of::<__wasi_dirent_t>(); + let offset = dirent_size.checked_add(namlen).ok_or(Error::EOVERFLOW)?; + + let mut raw = Vec::::with_capacity(offset); + raw.resize(offset, 0); + + let sys_dirent = raw.as_mut_ptr() as *mut __wasi_dirent_t; + unsafe { + *sys_dirent = __wasi_dirent_t { + d_namlen: namlen.try_into()?, + d_ino: self.ino, + d_next: self.cookie, + d_type: self.ftype.to_wasi(), + }; + } + + let sys_name = unsafe { sys_dirent.offset(1) as *mut u8 }; + let sys_name = unsafe { slice::from_raw_parts_mut(sys_name, namlen) }; + sys_name.copy_from_slice(&name); + + Ok(raw) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/wasi-common/src/hostcalls_impl/fs.rs b/crates/wasi-common/src/hostcalls_impl/fs.rs index 8550efb17e..71d84f52ca 100644 --- a/crates/wasi-common/src/hostcalls_impl/fs.rs +++ b/crates/wasi-common/src/hostcalls_impl/fs.rs @@ -10,10 +10,8 @@ use crate::sys::{host_impl, hostcalls_impl}; use crate::{helpers, host, wasi, wasi32, Error, Result}; use filetime::{set_file_handle_times, FileTime}; use log::trace; -use std::convert::TryInto; use std::fs::File; use std::io::{self, Read, Seek, SeekFrom, Write}; -use std::mem; use std::time::{Duration, SystemTime, UNIX_EPOCH}; pub(crate) unsafe fn fd_close(wasi_ctx: &mut WasiCtx, fd: wasi::__wasi_fd_t) -> Result<()> { @@ -1041,63 +1039,3 @@ pub(crate) unsafe fn fd_readdir( enc_usize_byref(memory, buf_used, host_bufused) } - -#[allow(dead_code)] // trouble with sockets -#[derive(Clone, Copy, Debug)] -#[repr(u8)] -pub(crate) enum FileType { - Unknown = wasi::__WASI_FILETYPE_UNKNOWN, - BlockDevice = wasi::__WASI_FILETYPE_BLOCK_DEVICE, - CharacterDevice = wasi::__WASI_FILETYPE_CHARACTER_DEVICE, - Directory = wasi::__WASI_FILETYPE_DIRECTORY, - RegularFile = wasi::__WASI_FILETYPE_REGULAR_FILE, - SocketDgram = wasi::__WASI_FILETYPE_SOCKET_DGRAM, - SocketStream = wasi::__WASI_FILETYPE_SOCKET_STREAM, - Symlink = wasi::__WASI_FILETYPE_SYMBOLIC_LINK, -} - -impl FileType { - pub(crate) fn to_wasi(&self) -> wasi::__wasi_filetype_t { - *self as wasi::__wasi_filetype_t - } -} - -#[derive(Debug, Clone)] -pub(crate) struct Dirent { - pub name: String, - pub ftype: FileType, - pub ino: u64, - pub cookie: wasi::__wasi_dircookie_t, -} - -impl Dirent { - /// Serialize the directory entry to the format define by `__wasi_fd_readdir`, - /// so that the serialized entries can be concatenated by the implementation. - pub fn to_wasi_raw(&self) -> Result> { - use std::slice; - - let name = self.name.as_bytes(); - let namlen = name.len(); - let dirent_size = mem::size_of::(); - let offset = dirent_size.checked_add(namlen).ok_or(Error::EOVERFLOW)?; - - let mut raw = Vec::::with_capacity(offset); - raw.resize(offset, 0); - - let sys_dirent = raw.as_mut_ptr() as *mut wasi::__wasi_dirent_t; - unsafe { - sys_dirent.write_unaligned(wasi::__wasi_dirent_t { - d_namlen: namlen.try_into()?, - d_ino: self.ino, - d_next: self.cookie, - d_type: self.ftype.to_wasi(), - }); - } - - let sys_name = unsafe { sys_dirent.offset(1) as *mut u8 }; - let sys_name = unsafe { slice::from_raw_parts_mut(sys_name, namlen) }; - sys_name.copy_from_slice(&name); - - Ok(raw) - } -} diff --git a/crates/wasi-common/src/old/snapshot_0/error.rs b/crates/wasi-common/src/old/snapshot_0/error.rs index 70566892c3..33dce550f5 100644 --- a/crates/wasi-common/src/old/snapshot_0/error.rs +++ b/crates/wasi-common/src/old/snapshot_0/error.rs @@ -3,7 +3,7 @@ use crate::old::snapshot_0::wasi; use std::convert::Infallible; use std::num::TryFromIntError; -use std::{fmt, str}; +use std::{ffi, fmt, str}; use thiserror::Error; #[derive(Clone, Copy, Debug, Error, Eq, PartialEq)] @@ -107,7 +107,7 @@ pub enum Error { Wasi(WasiError), Io(std::io::Error), #[cfg(unix)] - Nix(nix::Error), + Nix(yanix::YanixError), #[cfg(windows)] Win(winx::winerror::WinError), } @@ -119,8 +119,8 @@ impl From for Error { } #[cfg(unix)] -impl From for Error { - fn from(err: nix::Error) -> Self { +impl From for Error { + fn from(err: yanix::YanixError) -> Self { Self::Nix(err) } } @@ -149,6 +149,12 @@ impl From for Error { } } +impl From<&ffi::NulError> for Error { + fn from(_: &ffi::NulError) -> Self { + Self::Wasi(WasiError::EILSEQ) + } +} + #[cfg(windows)] impl From for Error { fn from(err: winx::winerror::WinError) -> Self { @@ -162,16 +168,15 @@ impl Error { Self::Wasi(no) => no.as_raw_errno(), Self::Io(e) => errno_from_ioerror(e.to_owned()), #[cfg(unix)] - Self::Nix(err) => err - .as_errno() - .map_or_else( - || { - log::debug!("Unknown nix errno: {}", err); - Self::ENOSYS - }, - crate::old::snapshot_0::sys::host_impl::errno_from_nix, - ) - .as_wasi_errno(), + Self::Nix(err) => { + use yanix::YanixError::*; + let err = match err { + Errno(errno) => crate::old::snapshot_0::sys::host_impl::errno_from_nix(*errno), + NulError(err) => err.into(), + TryFromIntError(err) => (*err).into(), + }; + err.as_wasi_errno() + } #[cfg(windows)] Self::Win(err) => crate::old::snapshot_0::sys::host_impl::errno_from_win(*err), } diff --git a/crates/wasi-common/src/old/snapshot_0/host.rs b/crates/wasi-common/src/old/snapshot_0/host.rs index ae6f156b40..87d8eb3845 100644 --- a/crates/wasi-common/src/old/snapshot_0/host.rs +++ b/crates/wasi-common/src/old/snapshot_0/host.rs @@ -5,7 +5,8 @@ #![allow(non_snake_case)] use crate::old::snapshot_0::wasi::*; -use std::{io, slice}; +use crate::old::snapshot_0::{Error, Result}; +use std::{convert::TryInto, io, mem, slice}; use wig::witx_host_types; witx_host_types!("old/snapshot_0" "wasi_unstable"); @@ -20,6 +21,64 @@ pub(crate) unsafe fn iovec_to_host_mut(iovec: &mut __wasi_iovec_t) -> io::IoSlic io::IoSliceMut::new(slice) } +#[allow(dead_code)] // trouble with sockets +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +pub(crate) enum FileType { + Unknown = __WASI_FILETYPE_UNKNOWN, + BlockDevice = __WASI_FILETYPE_BLOCK_DEVICE, + CharacterDevice = __WASI_FILETYPE_CHARACTER_DEVICE, + Directory = __WASI_FILETYPE_DIRECTORY, + RegularFile = __WASI_FILETYPE_REGULAR_FILE, + SocketDgram = __WASI_FILETYPE_SOCKET_DGRAM, + SocketStream = __WASI_FILETYPE_SOCKET_STREAM, + Symlink = __WASI_FILETYPE_SYMBOLIC_LINK, +} + +impl FileType { + pub(crate) fn to_wasi(&self) -> __wasi_filetype_t { + *self as __wasi_filetype_t + } +} + +#[derive(Debug, Clone)] +pub(crate) struct Dirent { + pub name: String, + pub ftype: FileType, + pub ino: u64, + pub cookie: __wasi_dircookie_t, +} + +impl Dirent { + /// Serialize the directory entry to the format define by `__wasi_fd_readdir`, + /// so that the serialized entries can be concatenated by the implementation. + pub fn to_wasi_raw(&self) -> Result> { + let name = self.name.as_bytes(); + let namlen = name.len(); + let dirent_size = mem::size_of::<__wasi_dirent_t>(); + let offset = dirent_size.checked_add(namlen).ok_or(Error::EOVERFLOW)?; + + let mut raw = Vec::::with_capacity(offset); + raw.resize(offset, 0); + + let sys_dirent = raw.as_mut_ptr() as *mut __wasi_dirent_t; + unsafe { + *sys_dirent = __wasi_dirent_t { + d_namlen: namlen.try_into()?, + d_ino: self.ino, + d_next: self.cookie, + d_type: self.ftype.to_wasi(), + }; + } + + let sys_name = unsafe { sys_dirent.offset(1) as *mut u8 }; + let sys_name = unsafe { slice::from_raw_parts_mut(sys_name, namlen) }; + sys_name.copy_from_slice(&name); + + Ok(raw) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/wasi-common/src/old/snapshot_0/hostcalls_impl/fs.rs b/crates/wasi-common/src/old/snapshot_0/hostcalls_impl/fs.rs index 4481dc461f..da51e01f2e 100644 --- a/crates/wasi-common/src/old/snapshot_0/hostcalls_impl/fs.rs +++ b/crates/wasi-common/src/old/snapshot_0/hostcalls_impl/fs.rs @@ -10,10 +10,8 @@ use crate::old::snapshot_0::sys::{host_impl, hostcalls_impl}; use crate::old::snapshot_0::{helpers, host, wasi, wasi32, Error, Result}; use filetime::{set_file_handle_times, FileTime}; use log::trace; -use std::convert::TryInto; use std::fs::File; use std::io::{self, Read, Seek, SeekFrom, Write}; -use std::mem; use std::time::{Duration, SystemTime, UNIX_EPOCH}; pub(crate) unsafe fn fd_close(wasi_ctx: &mut WasiCtx, fd: wasi::__wasi_fd_t) -> Result<()> { @@ -1041,63 +1039,3 @@ pub(crate) unsafe fn fd_readdir( enc_usize_byref(memory, buf_used, host_bufused) } - -#[allow(dead_code)] // trouble with sockets -#[derive(Clone, Copy, Debug)] -#[repr(u8)] -pub(crate) enum FileType { - Unknown = wasi::__WASI_FILETYPE_UNKNOWN, - BlockDevice = wasi::__WASI_FILETYPE_BLOCK_DEVICE, - CharacterDevice = wasi::__WASI_FILETYPE_CHARACTER_DEVICE, - Directory = wasi::__WASI_FILETYPE_DIRECTORY, - RegularFile = wasi::__WASI_FILETYPE_REGULAR_FILE, - SocketDgram = wasi::__WASI_FILETYPE_SOCKET_DGRAM, - SocketStream = wasi::__WASI_FILETYPE_SOCKET_STREAM, - Symlink = wasi::__WASI_FILETYPE_SYMBOLIC_LINK, -} - -impl FileType { - pub(crate) fn to_wasi(&self) -> wasi::__wasi_filetype_t { - *self as wasi::__wasi_filetype_t - } -} - -#[derive(Debug, Clone)] -pub(crate) struct Dirent { - pub name: String, - pub ftype: FileType, - pub ino: u64, - pub cookie: wasi::__wasi_dircookie_t, -} - -impl Dirent { - /// Serialize the directory entry to the format define by `__wasi_fd_readdir`, - /// so that the serialized entries can be concatenated by the implementation. - pub fn to_wasi_raw(&self) -> Result> { - use std::slice; - - let name = self.name.as_bytes(); - let namlen = name.len(); - let dirent_size = mem::size_of::(); - let offset = dirent_size.checked_add(namlen).ok_or(Error::EOVERFLOW)?; - - let mut raw = Vec::::with_capacity(offset); - raw.resize(offset, 0); - - let sys_dirent = raw.as_mut_ptr() as *mut wasi::__wasi_dirent_t; - unsafe { - *sys_dirent = wasi::__wasi_dirent_t { - d_namlen: namlen.try_into()?, - d_ino: self.ino, - d_next: self.cookie, - d_type: self.ftype.to_wasi(), - }; - } - - let sys_name = unsafe { sys_dirent.offset(1) as *mut u8 }; - let sys_name = unsafe { slice::from_raw_parts_mut(sys_name, namlen) }; - sys_name.copy_from_slice(&name); - - Ok(raw) - } -} diff --git a/crates/wasi-common/src/old/snapshot_0/sys/mod.rs b/crates/wasi-common/src/old/snapshot_0/sys/mod.rs index 960c6337e4..2e62d6f1c4 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/mod.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/mod.rs @@ -7,7 +7,7 @@ cfg_if! { pub(crate) use self::unix::*; pub(crate) fn errno_from_host(err: i32) -> wasi::__wasi_errno_t { - host_impl::errno_from_nix(nix::errno::from_i32(err)).as_wasi_errno() + host_impl::errno_from_nix(yanix::Errno::from_i32(err)).as_wasi_errno() } } else if #[cfg(windows)] { mod windows; diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/hostcalls_impl.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/hostcalls_impl.rs index edcd0c80db..7db8808ea7 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/hostcalls_impl.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/hostcalls_impl.rs @@ -1,27 +1,22 @@ -use super::super::dir::{Dir, Entry, SeekLoc}; -use super::oshandle::OsHandle; -use crate::old::snapshot_0::hostcalls_impl::{Dirent, PathGet}; +use crate::old::snapshot_0::hostcalls_impl::PathGet; use crate::old::snapshot_0::sys::host_impl; -use crate::old::snapshot_0::sys::unix::str_to_cstring; -use crate::old::snapshot_0::{wasi, Error, Result}; -use nix::libc; -use std::convert::TryInto; -use std::fs::File; +use crate::old::snapshot_0::{Error, Result}; use std::os::unix::prelude::AsRawFd; -use std::sync::MutexGuard; pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> { - use nix::errno; - use nix::libc::unlinkat; - - let path_cstr = str_to_cstring(resolved.path())?; - - // nix doesn't expose unlinkat() yet - match unsafe { unlinkat(resolved.dirfd().as_raw_fd(), path_cstr.as_ptr(), 0) } { - 0 => Ok(()), - _ => { - let mut e = errno::Errno::last(); - + use yanix::{ + file::{unlinkat, AtFlag}, + Errno, YanixError, + }; + unsafe { + unlinkat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + AtFlag::empty(), + ) + } + .map_err(|err| { + if let YanixError::Errno(mut errno) = err { // Non-Linux implementations may return EPERM when attempting to remove a // directory without REMOVEDIR. While that's what POSIX specifies, it's // less useful. Adjust this to EISDIR. It doesn't matter that this is not @@ -29,84 +24,84 @@ pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> { // is created before fstatat sees it, we're racing with that change anyway // and unlinkat could have legitimately seen the directory if the race had // turned out differently. - use nix::fcntl::AtFlags; - use nix::sys::stat::{fstatat, SFlag}; + use yanix::file::{fstatat, SFlag}; - if e == errno::Errno::EPERM { - if let Ok(stat) = fstatat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - AtFlags::AT_SYMLINK_NOFOLLOW, - ) { - if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFDIR) { - e = errno::Errno::EISDIR; + if errno == Errno::EPERM { + if let Ok(stat) = unsafe { + fstatat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + AtFlag::SYMLINK_NOFOLLOW, + ) + } { + if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::IFDIR) { + errno = Errno::EISDIR; } } else { - e = errno::Errno::last(); + errno = Errno::last(); } } - - Err(host_impl::errno_from_nix(e)) + errno.into() + } else { + err } - } + }) + .map_err(Into::into) } pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> Result<()> { - use nix::{errno::Errno, fcntl::AtFlags, libc::symlinkat, sys::stat::fstatat}; - - let old_path_cstr = str_to_cstring(old_path)?; - let new_path_cstr = str_to_cstring(resolved.path())?; + use yanix::{ + file::{fstatat, symlinkat, AtFlag}, + Errno, YanixError, + }; log::debug!("path_symlink old_path = {:?}", old_path); log::debug!("path_symlink resolved = {:?}", resolved); - let res = unsafe { - symlinkat( - old_path_cstr.as_ptr(), - resolved.dirfd().as_raw_fd(), - new_path_cstr.as_ptr(), - ) - }; - if res != 0 { - match Errno::last() { - Errno::ENOTDIR => { - // On BSD, symlinkat returns ENOTDIR when it should in fact - // return a EEXIST. It seems that it gets confused with by - // the trailing slash in the target path. Thus, we strip - // the trailing slash and check if the path exists, and - // adjust the error code appropriately. - let new_path = resolved.path().trim_end_matches('/'); - if let Ok(_) = fstatat( - resolved.dirfd().as_raw_fd(), - new_path, - AtFlags::AT_SYMLINK_NOFOLLOW, - ) { - Err(Error::EEXIST) - } else { - Err(Error::ENOTDIR) + unsafe { symlinkat(old_path, resolved.dirfd().as_raw_fd(), resolved.path()) }.or_else(|err| { + if let YanixError::Errno(errno) = err { + match errno { + Errno::ENOTDIR => { + // On BSD, symlinkat returns ENOTDIR when it should in fact + // return a EEXIST. It seems that it gets confused with by + // the trailing slash in the target path. Thus, we strip + // the trailing slash and check if the path exists, and + // adjust the error code appropriately. + let new_path = resolved.path().trim_end_matches('/'); + if let Ok(_) = unsafe { + fstatat( + resolved.dirfd().as_raw_fd(), + new_path, + AtFlag::SYMLINK_NOFOLLOW, + ) + } { + Err(Error::EEXIST) + } else { + Err(Error::ENOTDIR) + } } + x => Err(host_impl::errno_from_nix(x)), } - x => Err(host_impl::errno_from_nix(x)), + } else { + Err(err.into()) } - } else { - Ok(()) - } + }) } pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> { - use nix::{errno::Errno, fcntl::AtFlags, libc::renameat, sys::stat::fstatat}; - let old_path_cstr = str_to_cstring(resolved_old.path())?; - let new_path_cstr = str_to_cstring(resolved_new.path())?; - - let res = unsafe { + use yanix::{ + file::{fstatat, renameat, AtFlag}, + Errno, YanixError, + }; + unsafe { renameat( resolved_old.dirfd().as_raw_fd(), - old_path_cstr.as_ptr(), + resolved_old.path(), resolved_new.dirfd().as_raw_fd(), - new_path_cstr.as_ptr(), + resolved_new.path(), ) - }; - if res != 0 { + } + .or_else(|err| { // Currently, this is verified to be correct on macOS, where // ENOENT can be returned in case when we try to rename a file // into a name with a trailing slash. On macOS, if the latter does @@ -116,175 +111,60 @@ pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Resul // // TODO // Verify on other BSD-based OSes. - match Errno::last() { - Errno::ENOENT => { - // check if the source path exists - if let Ok(_) = fstatat( - resolved_old.dirfd().as_raw_fd(), - resolved_old.path(), - AtFlags::AT_SYMLINK_NOFOLLOW, - ) { - // check if destination contains a trailing slash - if resolved_new.path().contains('/') { - Err(Error::ENOTDIR) + if let YanixError::Errno(errno) = err { + match errno { + Errno::ENOENT => { + // check if the source path exists + if let Ok(_) = unsafe { + fstatat( + resolved_old.dirfd().as_raw_fd(), + resolved_old.path(), + AtFlag::SYMLINK_NOFOLLOW, + ) + } { + // check if destination contains a trailing slash + if resolved_new.path().contains('/') { + Err(Error::ENOTDIR) + } else { + Err(Error::ENOENT) + } } else { Err(Error::ENOENT) } - } else { - Err(Error::ENOENT) } + x => Err(host_impl::errno_from_nix(x)), } - x => Err(host_impl::errno_from_nix(x)), + } else { + Err(err.into()) } - } else { - Ok(()) - } + }) } -#[cfg(any(target_os = "macos", target_os = "ios"))] -pub(crate) fn fd_advise( - file: &File, - advice: wasi::__wasi_advice_t, - offset: wasi::__wasi_filesize_t, - len: wasi::__wasi_filesize_t, -) -> Result<()> { - use nix::errno::Errno; +pub(crate) mod fd_readdir_impl { + use crate::old::snapshot_0::sys::fdentry_impl::OsHandle; + use crate::old::snapshot_0::Result; + use std::sync::{Mutex, MutexGuard}; + use yanix::dir::Dir; - match advice { - wasi::__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 - wasi::__WASI_ADVICE_SEQUENTIAL - | wasi::__WASI_ADVICE_WILLNEED - | wasi::__WASI_ADVICE_NOREUSE - | wasi::__WASI_ADVICE_RANDOM - | wasi::__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: wasi::__wasi_advice_t, - _offset: wasi::__wasi_filesize_t, - _len: wasi::__wasi_filesize_t, -) -> Result<()> { - match advice { - wasi::__WASI_ADVICE_DONTNEED - | wasi::__WASI_ADVICE_SEQUENTIAL - | wasi::__WASI_ADVICE_WILLNEED - | wasi::__WASI_ADVICE_NOREUSE - | wasi::__WASI_ADVICE_RANDOM - | wasi::__WASI_ADVICE_NORMAL => {} - _ => return Err(Error::EINVAL), - } - - Ok(()) -} - -pub(crate) fn fd_readdir<'a>( - os_handle: &'a mut OsHandle, - cookie: wasi::__wasi_dircookie_t, -) -> Result> + 'a> { - use std::sync::Mutex; - - let dir = match os_handle.dir { - Some(ref mut dir) => dir, - None => { - // We need to duplicate the fd, because `opendir(3)`: - // Upon successful return from fdopendir(), the file descriptor is under - // control of the system, and if any attempt is made to close the file - // descriptor, or to modify the state of the associated description other - // than by means of closedir(), readdir(), readdir_r(), or rewinddir(), - // the behaviour is undefined. - let fd = (*os_handle).try_clone()?; - let dir = Dir::from(fd)?; - os_handle.dir.get_or_insert(Mutex::new(dir)) - } - }; - let mut dir = dir.lock().unwrap(); - - // Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START, - // new items may not be returned to the caller. - if cookie == wasi::__WASI_DIRCOOKIE_START { - log::trace!(" | fd_readdir: doing rewinddir"); - dir.rewind(); - } else { - log::trace!(" | fd_readdir: doing seekdir to {}", cookie); - let loc = unsafe { SeekLoc::from_raw(cookie as i64) }; - dir.seek(loc); - } - - Ok(DirIter(dir).map(|entry| { - let (entry, loc): (Entry, SeekLoc) = entry?; - Ok(Dirent { - name: entry - // TODO can we reuse path_from_host for CStr? - .file_name() - .to_str()? - .to_owned(), - ino: entry.ino(), - ftype: entry.file_type().into(), - // Set cookie 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 - cookie: loc.to_raw().try_into()?, - }) - })) -} - -struct DirIter<'a>(MutexGuard<'a, Dir>); - -impl<'a> Iterator for DirIter<'a> { - type Item = nix::Result<(Entry, SeekLoc)>; - - fn next(&mut self) -> Option { - use libc::readdir; - use nix::{errno::Errno, Error}; - - unsafe { - let errno = Errno::last(); - let ent = readdir((self.0).0.as_ptr()); - if ent.is_null() { - if errno != Errno::last() { - // TODO This should be verified on different BSD-flavours. - // - // According to 4.3BSD/POSIX.1-2001 man pages, there was an error - // if the errno value has changed at some point during the sequence - // of readdir calls. - Some(Err(Error::last())) - } else { - // Not an error. We've simply reached the end of the stream. - None - } - } else { - let entry = Entry(*ent); - let loc = self.0.tell(); - Some(Ok((entry, loc))) + pub(crate) fn get_dir_from_os_handle<'a>( + os_handle: &'a mut OsHandle, + ) -> Result> { + let dir = match os_handle.dir { + Some(ref mut dir) => dir, + None => { + // We need to duplicate the fd, because `opendir(3)`: + // Upon successful return from fdopendir(), the file descriptor is under + // control of the system, and if any attempt is made to close the file + // descriptor, or to modify the state of the associated description other + // than by means of closedir(), readdir(), readdir_r(), or rewinddir(), + // the behaviour is undefined. + let fd = (*os_handle).try_clone()?; + let dir = Dir::from(fd)?; + os_handle.dir.get_or_insert(Mutex::new(dir)) } - } + }; + // Note that from this point on, until the end of the parent scope (i.e., enclosing this + // function), we're locking the `Dir` member of this `OsHandle`. + Ok(dir.lock().unwrap()) } } diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/mod.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/mod.rs index bdc611b279..7b14ae69ad 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/mod.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/mod.rs @@ -2,36 +2,17 @@ pub(crate) mod filetime; pub(crate) mod hostcalls_impl; pub(crate) mod oshandle; -pub(crate) mod fdentry_impl { - use crate::old::snapshot_0::{sys::host_impl, Result}; - use std::os::unix::prelude::AsRawFd; - - pub(crate) unsafe fn isatty(fd: &impl AsRawFd) -> Result { - let res = libc::isatty(fd.as_raw_fd()); - if res == 1 { - // isatty() returns 1 if fd is an open file descriptor referring to a terminal... - Ok(true) - } else { - // ... otherwise 0 is returned, and errno is set to indicate the error. - match nix::errno::Errno::last() { - nix::errno::Errno::ENOTTY => Ok(false), - x => Err(host_impl::errno_from_nix(x)), - } - } - } -} - pub(crate) mod host_impl { use crate::old::snapshot_0::{wasi, Result}; use std::convert::TryFrom; - pub(crate) const O_RSYNC: nix::fcntl::OFlag = nix::fcntl::OFlag::O_SYNC; + pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::SYNC; - pub(crate) fn stdev_from_nix(dev: nix::libc::dev_t) -> Result { + pub(crate) fn stdev_from_nix(dev: libc::dev_t) -> Result { wasi::__wasi_device_t::try_from(dev).map_err(Into::into) } - pub(crate) fn stino_from_nix(ino: nix::libc::ino_t) -> Result { + pub(crate) fn stino_from_nix(ino: libc::ino_t) -> Result { wasi::__wasi_device_t::try_from(ino).map_err(Into::into) } } diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/oshandle.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/oshandle.rs index eccc7ab33e..70d1a8c4e8 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/oshandle.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/oshandle.rs @@ -1,8 +1,8 @@ -use super::super::dir::Dir; use std::fs; use std::ops::{Deref, DerefMut}; use std::os::unix::prelude::{AsRawFd, RawFd}; use std::sync::Mutex; +use yanix::dir::Dir; #[derive(Debug)] pub(crate) struct OsHandle { diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/dir.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/dir.rs deleted file mode 100644 index 81bb2e6877..0000000000 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/dir.rs +++ /dev/null @@ -1,174 +0,0 @@ -// Based on src/dir.rs from nix -use crate::old::snapshot_0::hostcalls_impl::FileType; -use libc; -use nix::{Error, Result}; -use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; -use std::{ffi, ptr}; - -#[cfg(target_os = "linux")] -use libc::dirent64 as dirent; - -#[cfg(not(target_os = "linux",))] -use libc::dirent; - -/// An open directory. -/// -/// This is a lower-level interface than `std::fs::ReadDir`. Notable differences: -/// * can be opened from a file descriptor (as returned by `openat`, perhaps before knowing -/// if the path represents a file or directory). -/// * implements `AsRawFd`, so it can be passed to `fstat`, `openat`, etc. -/// The file descriptor continues to be owned by the `Dir`, so callers must not keep a `RawFd` -/// after the `Dir` is dropped. -/// * can be iterated through multiple times without closing and reopening the file -/// descriptor. Each iteration rewinds when finished. -/// * returns entries for `.` (current directory) and `..` (parent directory). -/// * returns entries' names as a `CStr` (no allocation or conversion beyond whatever libc -/// does). -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub(crate) struct Dir(pub(crate) ptr::NonNull); - -impl Dir { - /// Converts from a descriptor-based object, closing the descriptor on success or failure. - #[inline] - pub(crate) fn from(fd: F) -> Result { - unsafe { Self::from_fd(fd.into_raw_fd()) } - } - - /// Converts from a file descriptor, closing it on success or failure. - unsafe fn from_fd(fd: RawFd) -> Result { - let d = libc::fdopendir(fd); - if d.is_null() { - let e = Error::last(); - libc::close(fd); - return Err(e); - }; - // Always guaranteed to be non-null by the previous check - Ok(Self(ptr::NonNull::new(d).unwrap())) - } - - /// Set the position of the directory stream, see `seekdir(3)`. - #[cfg(not(target_os = "android"))] - pub(crate) fn seek(&mut self, loc: SeekLoc) { - unsafe { libc::seekdir(self.0.as_ptr(), loc.0) } - } - - /// Reset directory stream, see `rewinddir(3)`. - pub(crate) fn rewind(&mut self) { - unsafe { libc::rewinddir(self.0.as_ptr()) } - } - - /// Get the current position in the directory stream. - /// - /// If this location is given to `Dir::seek`, the entries up to the previously returned - /// will be omitted and the iteration will start from the currently pending directory entry. - #[cfg(not(target_os = "android"))] - #[allow(dead_code)] - pub(crate) fn tell(&self) -> SeekLoc { - let loc = unsafe { libc::telldir(self.0.as_ptr()) }; - SeekLoc(loc) - } -} - -// `Dir` is not `Sync`. With the current implementation, it could be, but according to -// https://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html, -// future versions of POSIX are likely to obsolete `readdir_r` and specify that it's unsafe to -// call `readdir` simultaneously from multiple threads. -// -// `Dir` is safe to pass from one thread to another, as it's not reference-counted. -unsafe impl Send for Dir {} - -impl AsRawFd for Dir { - fn as_raw_fd(&self) -> RawFd { - unsafe { libc::dirfd(self.0.as_ptr()) } - } -} - -impl Drop for Dir { - fn drop(&mut self) { - unsafe { libc::closedir(self.0.as_ptr()) }; - } -} - -/// A directory entry, similar to `std::fs::DirEntry`. -/// -/// Note that unlike the std version, this may represent the `.` or `..` entries. -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] -#[repr(transparent)] -pub(crate) struct Entry(pub(crate) dirent); - -pub(crate) type Type = FileType; - -impl Entry { - /// Returns the inode number (`d_ino`) of the underlying `dirent`. - #[cfg(any( - target_os = "android", - target_os = "emscripten", - target_os = "fuchsia", - target_os = "haiku", - target_os = "ios", - target_os = "l4re", - target_os = "linux", - target_os = "macos", - target_os = "solaris" - ))] - pub(crate) fn ino(&self) -> u64 { - self.0.d_ino.into() - } - - /// Returns the inode number (`d_fileno`) of the underlying `dirent`. - #[cfg(not(any( - target_os = "android", - target_os = "emscripten", - target_os = "fuchsia", - target_os = "haiku", - target_os = "ios", - target_os = "l4re", - target_os = "linux", - target_os = "macos", - target_os = "solaris" - )))] - pub(crate) fn ino(&self) -> u64 { - u64::from(self.0.d_fileno) - } - - /// Returns the bare file name of this directory entry without any other leading path component. - pub(crate) fn file_name(&self) -> &ffi::CStr { - unsafe { ::std::ffi::CStr::from_ptr(self.0.d_name.as_ptr()) } - } - - /// Returns the type of this directory entry, if known. - /// - /// See platform `readdir(3)` or `dirent(5)` manpage for when the file type is known; - /// notably, some Linux filesystems don't implement this. The caller should use `stat` or - /// `fstat` if this returns `None`. - pub(crate) fn file_type(&self) -> FileType { - match self.0.d_type { - libc::DT_CHR => Type::CharacterDevice, - libc::DT_DIR => Type::Directory, - libc::DT_BLK => Type::BlockDevice, - libc::DT_REG => Type::RegularFile, - libc::DT_LNK => Type::Symlink, - /* libc::DT_UNKNOWN | libc::DT_SOCK | libc::DT_FIFO */ _ => Type::Unknown, - } - } - - #[cfg(target_os = "linux")] - pub(crate) fn seek_loc(&self) -> SeekLoc { - unsafe { SeekLoc::from_raw(self.0.d_off) } - } -} - -#[cfg(not(target_os = "android"))] -#[derive(Clone, Copy, Debug)] -pub(crate) struct SeekLoc(libc::c_long); - -#[cfg(not(target_os = "android"))] -impl SeekLoc { - pub(crate) unsafe fn from_raw(loc: i64) -> Self { - Self(loc.into()) - } - - pub(crate) fn to_raw(&self) -> i64 { - self.0.into() - } -} diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/fdentry_impl.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/fdentry_impl.rs index 3a628123d7..5adaf22f30 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/fdentry_impl.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/fdentry_impl.rs @@ -8,7 +8,6 @@ use std::os::unix::prelude::{AsRawFd, FileTypeExt, FromRawFd, RawFd}; cfg_if::cfg_if! { if #[cfg(target_os = "linux")] { pub(crate) use super::linux::oshandle::*; - pub(crate) use super::linux::fdentry_impl::*; } else if #[cfg(any( target_os = "macos", target_os = "netbsd", @@ -18,7 +17,6 @@ cfg_if::cfg_if! { target_os = "dragonfly" ))] { pub(crate) use super::bsd::oshandle::*; - pub(crate) use super::bsd::fdentry_impl::*; } } @@ -51,13 +49,12 @@ pub(crate) unsafe fn determine_type_and_access_rights( )> { let (file_type, mut rights_base, rights_inheriting) = determine_type_rights(fd)?; - use nix::fcntl::{fcntl, OFlag, F_GETFL}; - let flags_bits = fcntl(fd.as_raw_fd(), F_GETFL)?; - let flags = OFlag::from_bits_truncate(flags_bits); - let accmode = flags & OFlag::O_ACCMODE; - if accmode == OFlag::O_RDONLY { + use yanix::{fcntl, file::OFlag}; + let flags = fcntl::get_status_flags(fd.as_raw_fd())?; + let accmode = flags & OFlag::ACCMODE; + if accmode == OFlag::RDONLY { rights_base &= !wasi::__WASI_RIGHTS_FD_WRITE; - } else if accmode == OFlag::O_WRONLY { + } else if accmode == OFlag::WRONLY { rights_base &= !wasi::__WASI_RIGHTS_FD_READ; } @@ -85,7 +82,8 @@ pub(crate) unsafe fn determine_type_rights( ) } else if ft.is_char_device() { log::debug!("Host fd {:?} is a char device", fd.as_raw_fd()); - if isatty(fd)? { + use yanix::file::isatty; + if isatty(fd.as_raw_fd())? { ( wasi::__WASI_FILETYPE_CHARACTER_DEVICE, wasi::RIGHTS_TTY_BASE, @@ -114,14 +112,14 @@ pub(crate) unsafe fn determine_type_rights( ) } else if ft.is_socket() { log::debug!("Host fd {:?} is a socket", fd.as_raw_fd()); - use nix::sys::socket; - match socket::getsockopt(fd.as_raw_fd(), socket::sockopt::SockType)? { - socket::SockType::Datagram => ( + use yanix::socket::{get_socket_type, SockType}; + match get_socket_type(fd.as_raw_fd())? { + SockType::Datagram => ( wasi::__WASI_FILETYPE_SOCKET_DGRAM, wasi::RIGHTS_SOCKET_BASE, wasi::RIGHTS_SOCKET_INHERITING, ), - socket::SockType::Stream => ( + SockType::Stream => ( wasi::__WASI_FILETYPE_SOCKET_STREAM, wasi::RIGHTS_SOCKET_BASE, wasi::RIGHTS_SOCKET_INHERITING, diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/host_impl.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/host_impl.rs index 534468c6bd..cd15382587 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/host_impl.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/host_impl.rs @@ -2,11 +2,14 @@ #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(dead_code)] -use crate::old::snapshot_0::hostcalls_impl::FileType; +use crate::old::snapshot_0::host::FileType; use crate::old::snapshot_0::{helpers, wasi, Error, Result}; -use log::warn; use std::ffi::OsStr; use std::os::unix::prelude::OsStrExt; +use yanix::{ + file::{OFlag, SFlag}, + Errno, +}; cfg_if::cfg_if! { if #[cfg(target_os = "linux")] { @@ -23,178 +26,168 @@ cfg_if::cfg_if! { } } -pub(crate) fn errno_from_nix(errno: nix::errno::Errno) -> Error { +pub(crate) fn errno_from_nix(errno: Errno) -> Error { match errno { - nix::errno::Errno::EPERM => Error::EPERM, - nix::errno::Errno::ENOENT => Error::ENOENT, - nix::errno::Errno::ESRCH => Error::ESRCH, - nix::errno::Errno::EINTR => Error::EINTR, - nix::errno::Errno::EIO => Error::EIO, - nix::errno::Errno::ENXIO => Error::ENXIO, - nix::errno::Errno::E2BIG => Error::E2BIG, - nix::errno::Errno::ENOEXEC => Error::ENOEXEC, - nix::errno::Errno::EBADF => Error::EBADF, - nix::errno::Errno::ECHILD => Error::ECHILD, - nix::errno::Errno::EAGAIN => Error::EAGAIN, - nix::errno::Errno::ENOMEM => Error::ENOMEM, - nix::errno::Errno::EACCES => Error::EACCES, - nix::errno::Errno::EFAULT => Error::EFAULT, - nix::errno::Errno::EBUSY => Error::EBUSY, - nix::errno::Errno::EEXIST => Error::EEXIST, - nix::errno::Errno::EXDEV => Error::EXDEV, - nix::errno::Errno::ENODEV => Error::ENODEV, - nix::errno::Errno::ENOTDIR => Error::ENOTDIR, - nix::errno::Errno::EISDIR => Error::EISDIR, - nix::errno::Errno::EINVAL => Error::EINVAL, - nix::errno::Errno::ENFILE => Error::ENFILE, - nix::errno::Errno::EMFILE => Error::EMFILE, - nix::errno::Errno::ENOTTY => Error::ENOTTY, - nix::errno::Errno::ETXTBSY => Error::ETXTBSY, - nix::errno::Errno::EFBIG => Error::EFBIG, - nix::errno::Errno::ENOSPC => Error::ENOSPC, - nix::errno::Errno::ESPIPE => Error::ESPIPE, - nix::errno::Errno::EROFS => Error::EROFS, - nix::errno::Errno::EMLINK => Error::EMLINK, - nix::errno::Errno::EPIPE => Error::EPIPE, - nix::errno::Errno::EDOM => Error::EDOM, - nix::errno::Errno::ERANGE => Error::ERANGE, - nix::errno::Errno::EDEADLK => Error::EDEADLK, - nix::errno::Errno::ENAMETOOLONG => Error::ENAMETOOLONG, - nix::errno::Errno::ENOLCK => Error::ENOLCK, - nix::errno::Errno::ENOSYS => Error::ENOSYS, - nix::errno::Errno::ENOTEMPTY => Error::ENOTEMPTY, - nix::errno::Errno::ELOOP => Error::ELOOP, - nix::errno::Errno::ENOMSG => Error::ENOMSG, - nix::errno::Errno::EIDRM => Error::EIDRM, - nix::errno::Errno::ENOLINK => Error::ENOLINK, - nix::errno::Errno::EPROTO => Error::EPROTO, - nix::errno::Errno::EMULTIHOP => Error::EMULTIHOP, - nix::errno::Errno::EBADMSG => Error::EBADMSG, - nix::errno::Errno::EOVERFLOW => Error::EOVERFLOW, - nix::errno::Errno::EILSEQ => Error::EILSEQ, - nix::errno::Errno::ENOTSOCK => Error::ENOTSOCK, - nix::errno::Errno::EDESTADDRREQ => Error::EDESTADDRREQ, - nix::errno::Errno::EMSGSIZE => Error::EMSGSIZE, - nix::errno::Errno::EPROTOTYPE => Error::EPROTOTYPE, - nix::errno::Errno::ENOPROTOOPT => Error::ENOPROTOOPT, - nix::errno::Errno::EPROTONOSUPPORT => Error::EPROTONOSUPPORT, - nix::errno::Errno::EAFNOSUPPORT => Error::EAFNOSUPPORT, - nix::errno::Errno::EADDRINUSE => Error::EADDRINUSE, - nix::errno::Errno::EADDRNOTAVAIL => Error::EADDRNOTAVAIL, - nix::errno::Errno::ENETDOWN => Error::ENETDOWN, - nix::errno::Errno::ENETUNREACH => Error::ENETUNREACH, - nix::errno::Errno::ENETRESET => Error::ENETRESET, - nix::errno::Errno::ECONNABORTED => Error::ECONNABORTED, - nix::errno::Errno::ECONNRESET => Error::ECONNRESET, - nix::errno::Errno::ENOBUFS => Error::ENOBUFS, - nix::errno::Errno::EISCONN => Error::EISCONN, - nix::errno::Errno::ENOTCONN => Error::ENOTCONN, - nix::errno::Errno::ETIMEDOUT => Error::ETIMEDOUT, - nix::errno::Errno::ECONNREFUSED => Error::ECONNREFUSED, - nix::errno::Errno::EHOSTUNREACH => Error::EHOSTUNREACH, - nix::errno::Errno::EALREADY => Error::EALREADY, - nix::errno::Errno::EINPROGRESS => Error::EINPROGRESS, - nix::errno::Errno::ESTALE => Error::ESTALE, - nix::errno::Errno::EDQUOT => Error::EDQUOT, - nix::errno::Errno::ECANCELED => Error::ECANCELED, - nix::errno::Errno::EOWNERDEAD => Error::EOWNERDEAD, - nix::errno::Errno::ENOTRECOVERABLE => Error::ENOTRECOVERABLE, - other => { - warn!("Unknown error from nix: {}", other); - Error::ENOSYS - } + Errno::EPERM => Error::EPERM, + Errno::ENOENT => Error::ENOENT, + Errno::ESRCH => Error::ESRCH, + Errno::EINTR => Error::EINTR, + Errno::EIO => Error::EIO, + Errno::ENXIO => Error::ENXIO, + Errno::E2BIG => Error::E2BIG, + Errno::ENOEXEC => Error::ENOEXEC, + Errno::EBADF => Error::EBADF, + Errno::ECHILD => Error::ECHILD, + Errno::EAGAIN => Error::EAGAIN, + Errno::ENOMEM => Error::ENOMEM, + Errno::EACCES => Error::EACCES, + Errno::EFAULT => Error::EFAULT, + Errno::EBUSY => Error::EBUSY, + Errno::EEXIST => Error::EEXIST, + Errno::EXDEV => Error::EXDEV, + Errno::ENODEV => Error::ENODEV, + Errno::ENOTDIR => Error::ENOTDIR, + Errno::EISDIR => Error::EISDIR, + Errno::EINVAL => Error::EINVAL, + Errno::ENFILE => Error::ENFILE, + Errno::EMFILE => Error::EMFILE, + Errno::ENOTTY => Error::ENOTTY, + Errno::ETXTBSY => Error::ETXTBSY, + Errno::EFBIG => Error::EFBIG, + Errno::ENOSPC => Error::ENOSPC, + Errno::ESPIPE => Error::ESPIPE, + Errno::EROFS => Error::EROFS, + Errno::EMLINK => Error::EMLINK, + Errno::EPIPE => Error::EPIPE, + Errno::EDOM => Error::EDOM, + Errno::ERANGE => Error::ERANGE, + Errno::EDEADLK => Error::EDEADLK, + Errno::ENAMETOOLONG => Error::ENAMETOOLONG, + Errno::ENOLCK => Error::ENOLCK, + Errno::ENOSYS => Error::ENOSYS, + Errno::ENOTEMPTY => Error::ENOTEMPTY, + Errno::ELOOP => Error::ELOOP, + Errno::ENOMSG => Error::ENOMSG, + Errno::EIDRM => Error::EIDRM, + Errno::ENOLINK => Error::ENOLINK, + Errno::EPROTO => Error::EPROTO, + Errno::EMULTIHOP => Error::EMULTIHOP, + Errno::EBADMSG => Error::EBADMSG, + Errno::EOVERFLOW => Error::EOVERFLOW, + Errno::EILSEQ => Error::EILSEQ, + Errno::ENOTSOCK => Error::ENOTSOCK, + Errno::EDESTADDRREQ => Error::EDESTADDRREQ, + Errno::EMSGSIZE => Error::EMSGSIZE, + Errno::EPROTOTYPE => Error::EPROTOTYPE, + Errno::ENOPROTOOPT => Error::ENOPROTOOPT, + Errno::EPROTONOSUPPORT => Error::EPROTONOSUPPORT, + Errno::EAFNOSUPPORT => Error::EAFNOSUPPORT, + Errno::EADDRINUSE => Error::EADDRINUSE, + Errno::EADDRNOTAVAIL => Error::EADDRNOTAVAIL, + Errno::ENETDOWN => Error::ENETDOWN, + Errno::ENETUNREACH => Error::ENETUNREACH, + Errno::ENETRESET => Error::ENETRESET, + Errno::ECONNABORTED => Error::ECONNABORTED, + Errno::ECONNRESET => Error::ECONNRESET, + Errno::ENOBUFS => Error::ENOBUFS, + Errno::EISCONN => Error::EISCONN, + Errno::ENOTCONN => Error::ENOTCONN, + Errno::ETIMEDOUT => Error::ETIMEDOUT, + Errno::ECONNREFUSED => Error::ECONNREFUSED, + Errno::EHOSTUNREACH => Error::EHOSTUNREACH, + Errno::EALREADY => Error::EALREADY, + Errno::EINPROGRESS => Error::EINPROGRESS, + Errno::ESTALE => Error::ESTALE, + Errno::EDQUOT => Error::EDQUOT, + Errno::ECANCELED => Error::ECANCELED, + Errno::EOWNERDEAD => Error::EOWNERDEAD, + Errno::ENOTRECOVERABLE => Error::ENOTRECOVERABLE, } } -pub(crate) fn nix_from_fdflags(fdflags: wasi::__wasi_fdflags_t) -> nix::fcntl::OFlag { - use nix::fcntl::OFlag; +pub(crate) fn nix_from_fdflags(fdflags: wasi::__wasi_fdflags_t) -> OFlag { let mut nix_flags = OFlag::empty(); if fdflags & wasi::__WASI_FDFLAGS_APPEND != 0 { - nix_flags.insert(OFlag::O_APPEND); + nix_flags.insert(OFlag::APPEND); } if fdflags & wasi::__WASI_FDFLAGS_DSYNC != 0 { - nix_flags.insert(OFlag::O_DSYNC); + nix_flags.insert(OFlag::DSYNC); } if fdflags & wasi::__WASI_FDFLAGS_NONBLOCK != 0 { - nix_flags.insert(OFlag::O_NONBLOCK); + nix_flags.insert(OFlag::NONBLOCK); } if fdflags & wasi::__WASI_FDFLAGS_RSYNC != 0 { nix_flags.insert(O_RSYNC); } if fdflags & wasi::__WASI_FDFLAGS_SYNC != 0 { - nix_flags.insert(OFlag::O_SYNC); + nix_flags.insert(OFlag::SYNC); } nix_flags } -pub(crate) fn fdflags_from_nix(oflags: nix::fcntl::OFlag) -> wasi::__wasi_fdflags_t { - use nix::fcntl::OFlag; +pub(crate) fn fdflags_from_nix(oflags: OFlag) -> wasi::__wasi_fdflags_t { let mut fdflags = 0; - if oflags.contains(OFlag::O_APPEND) { + if oflags.contains(OFlag::APPEND) { fdflags |= wasi::__WASI_FDFLAGS_APPEND; } - if oflags.contains(OFlag::O_DSYNC) { + if oflags.contains(OFlag::DSYNC) { fdflags |= wasi::__WASI_FDFLAGS_DSYNC; } - if oflags.contains(OFlag::O_NONBLOCK) { + if oflags.contains(OFlag::NONBLOCK) { fdflags |= wasi::__WASI_FDFLAGS_NONBLOCK; } if oflags.contains(O_RSYNC) { fdflags |= wasi::__WASI_FDFLAGS_RSYNC; } - if oflags.contains(OFlag::O_SYNC) { + if oflags.contains(OFlag::SYNC) { fdflags |= wasi::__WASI_FDFLAGS_SYNC; } fdflags } -pub(crate) fn nix_from_oflags(oflags: wasi::__wasi_oflags_t) -> nix::fcntl::OFlag { - use nix::fcntl::OFlag; +pub(crate) fn nix_from_oflags(oflags: wasi::__wasi_oflags_t) -> OFlag { let mut nix_flags = OFlag::empty(); if oflags & wasi::__WASI_OFLAGS_CREAT != 0 { - nix_flags.insert(OFlag::O_CREAT); + nix_flags.insert(OFlag::CREAT); } if oflags & wasi::__WASI_OFLAGS_DIRECTORY != 0 { - nix_flags.insert(OFlag::O_DIRECTORY); + nix_flags.insert(OFlag::DIRECTORY); } if oflags & wasi::__WASI_OFLAGS_EXCL != 0 { - nix_flags.insert(OFlag::O_EXCL); + nix_flags.insert(OFlag::EXCL); } if oflags & wasi::__WASI_OFLAGS_TRUNC != 0 { - nix_flags.insert(OFlag::O_TRUNC); + nix_flags.insert(OFlag::TRUNC); } nix_flags } -pub(crate) fn filetype_from_nix(sflags: nix::sys::stat::SFlag) -> FileType { - use nix::sys::stat::SFlag; - if sflags.contains(SFlag::S_IFCHR) { +pub(crate) fn filetype_from_nix(sflags: SFlag) -> FileType { + if sflags.contains(SFlag::IFCHR) { FileType::CharacterDevice - } else if sflags.contains(SFlag::S_IFBLK) { + } else if sflags.contains(SFlag::IFBLK) { FileType::BlockDevice - } else if sflags.contains(SFlag::S_IFSOCK) { + } else if sflags.contains(SFlag::IFSOCK) { FileType::SocketStream - } else if sflags.contains(SFlag::S_IFDIR) { + } else if sflags.contains(SFlag::IFDIR) { FileType::Directory - } else if sflags.contains(SFlag::S_IFREG) { + } else if sflags.contains(SFlag::IFREG) { FileType::RegularFile - } else if sflags.contains(SFlag::S_IFLNK) { + } else if sflags.contains(SFlag::IFLNK) { FileType::Symlink } else { FileType::Unknown } } -pub(crate) fn filestat_from_nix( - filestat: nix::sys::stat::FileStat, -) -> Result { +pub(crate) fn filestat_from_nix(filestat: libc::stat) -> Result { fn filestat_to_timestamp(secs: u64, nsecs: u64) -> Result { secs.checked_mul(1_000_000_000) .and_then(|sec_nsec| sec_nsec.checked_add(nsecs)) .ok_or(Error::EOVERFLOW) } - let filetype = nix::sys::stat::SFlag::from_bits_truncate(filestat.st_mode); + let filetype = SFlag::from_bits_truncate(filestat.st_mode); let dev = stdev_from_nix(filestat.st_dev)?; let ino = stino_from_nix(filestat.st_ino)?; let atim = filestat_to_timestamp(filestat.st_atime as u64, filestat.st_atime_nsec as u64)?; @@ -214,7 +207,7 @@ pub(crate) fn filestat_from_nix( } pub(crate) fn dirent_filetype_from_host( - host_entry: &nix::libc::dirent, + host_entry: &libc::dirent, ) -> Result { match host_entry.d_type { libc::DT_FIFO => Ok(wasi::__WASI_FILETYPE_UNKNOWN), @@ -243,3 +236,17 @@ pub(crate) fn dirent_filetype_from_host( pub(crate) fn path_from_host>(s: S) -> Result { helpers::path_from_slice(s.as_ref().as_bytes()).map(String::from) } + +impl From for FileType { + fn from(ft: yanix::dir::FileType) -> Self { + use yanix::dir::FileType::*; + match ft { + RegularFile => Self::RegularFile, + Symlink => Self::Symlink, + Directory => Self::Directory, + BlockDevice => Self::BlockDevice, + CharacterDevice => Self::CharacterDevice, + /* Unknown | Socket | Fifo */ _ => Self::Unknown, + } + } +} diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/hostcalls_impl/fs.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/hostcalls_impl/fs.rs index cafce8ba0f..3cb42f4e72 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/hostcalls_impl/fs.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/hostcalls_impl/fs.rs @@ -1,11 +1,10 @@ #![allow(non_camel_case_types)] #![allow(unused_unsafe)] use crate::old::snapshot_0::helpers::systemtime_to_timestamp; -use crate::old::snapshot_0::hostcalls_impl::{FileType, PathGet}; -use crate::old::snapshot_0::sys::host_impl; -use crate::old::snapshot_0::sys::unix::str_to_cstring; +use crate::old::snapshot_0::host::{Dirent, FileType}; +use crate::old::snapshot_0::hostcalls_impl::PathGet; +use crate::old::snapshot_0::sys::{fdentry_impl::OsHandle, host_impl}; use crate::old::snapshot_0::{wasi, Error, Result}; -use nix::libc; use std::convert::TryInto; use std::fs::{File, Metadata}; use std::os::unix::fs::FileExt; @@ -39,53 +38,61 @@ pub(crate) fn fd_pwrite(file: &File, buf: &[u8], offset: wasi::__wasi_filesize_t } pub(crate) fn fd_fdstat_get(fd: &File) -> Result { - use nix::fcntl::{fcntl, OFlag, F_GETFL}; - match fcntl(fd.as_raw_fd(), F_GETFL).map(OFlag::from_bits_truncate) { - Ok(flags) => Ok(host_impl::fdflags_from_nix(flags)), - Err(e) => Err(host_impl::errno_from_nix(e.as_errno().unwrap())), - } + unsafe { yanix::fcntl::get_status_flags(fd.as_raw_fd()) } + .map(host_impl::fdflags_from_nix) + .map_err(Into::into) } pub(crate) fn fd_fdstat_set_flags(fd: &File, fdflags: wasi::__wasi_fdflags_t) -> Result<()> { - use nix::fcntl::{fcntl, F_SETFL}; let nix_flags = host_impl::nix_from_fdflags(fdflags); - match fcntl(fd.as_raw_fd(), F_SETFL(nix_flags)) { - Ok(_) => Ok(()), - Err(e) => Err(host_impl::errno_from_nix(e.as_errno().unwrap())), - } + unsafe { yanix::fcntl::set_status_flags(fd.as_raw_fd(), nix_flags) }.map_err(Into::into) +} + +pub(crate) fn fd_advise( + file: &File, + advice: wasi::__wasi_advice_t, + offset: wasi::__wasi_filesize_t, + len: wasi::__wasi_filesize_t, +) -> Result<()> { + use yanix::fadvise::{posix_fadvise, PosixFadviseAdvice}; + let offset = offset.try_into()?; + let len = len.try_into()?; + let host_advice = match advice { + wasi::__WASI_ADVICE_DONTNEED => PosixFadviseAdvice::DontNeed, + wasi::__WASI_ADVICE_SEQUENTIAL => PosixFadviseAdvice::Sequential, + wasi::__WASI_ADVICE_WILLNEED => PosixFadviseAdvice::WillNeed, + wasi::__WASI_ADVICE_NOREUSE => PosixFadviseAdvice::NoReuse, + wasi::__WASI_ADVICE_RANDOM => PosixFadviseAdvice::Random, + wasi::__WASI_ADVICE_NORMAL => PosixFadviseAdvice::Normal, + _ => return Err(Error::EINVAL), + }; + unsafe { posix_fadvise(file.as_raw_fd(), offset, len, host_advice) }.map_err(Into::into) } pub(crate) fn path_create_directory(resolved: PathGet) -> Result<()> { - use nix::libc::mkdirat; - let path_cstr = str_to_cstring(resolved.path())?; - // nix doesn't expose mkdirat() yet - match unsafe { mkdirat(resolved.dirfd().as_raw_fd(), path_cstr.as_ptr(), 0o777) } { - 0 => Ok(()), - _ => Err(host_impl::errno_from_nix(nix::errno::Errno::last())), + use yanix::file::{mkdirat, Mode}; + unsafe { + mkdirat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + Mode::from_bits_truncate(0o777), + ) } + .map_err(Into::into) } pub(crate) fn path_link(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> { - use nix::libc::linkat; - let old_path_cstr = str_to_cstring(resolved_old.path())?; - let new_path_cstr = str_to_cstring(resolved_new.path())?; - - // Not setting AT_SYMLINK_FOLLOW fails on most filesystems - let atflags = libc::AT_SYMLINK_FOLLOW; - let res = unsafe { + use yanix::file::{linkat, AtFlag}; + unsafe { linkat( resolved_old.dirfd().as_raw_fd(), - old_path_cstr.as_ptr(), + resolved_old.path(), resolved_new.dirfd().as_raw_fd(), - new_path_cstr.as_ptr(), - atflags, + resolved_new.path(), + AtFlag::SYMLINK_FOLLOW, ) - }; - if res != 0 { - Err(host_impl::errno_from_nix(nix::errno::Errno::last())) - } else { - Ok(()) } + .map_err(Into::into) } pub(crate) fn path_open( @@ -95,20 +102,21 @@ pub(crate) fn path_open( oflags: wasi::__wasi_oflags_t, fs_flags: wasi::__wasi_fdflags_t, ) -> Result { - use nix::errno::Errno; - use nix::fcntl::{openat, AtFlags, OFlag}; - use nix::sys::stat::{fstatat, Mode, SFlag}; + use yanix::{ + file::{fstatat, openat, AtFlag, Mode, OFlag, SFlag}, + Errno, + }; let mut nix_all_oflags = if read && write { - OFlag::O_RDWR + OFlag::RDWR } else if write { - OFlag::O_WRONLY + OFlag::WRONLY } else { - OFlag::O_RDONLY + OFlag::RDONLY }; // on non-Capsicum systems, we always want nofollow - nix_all_oflags.insert(OFlag::O_NOFOLLOW); + nix_all_oflags.insert(OFlag::NOFOLLOW); // convert open flags nix_all_oflags.insert(host_impl::nix_from_oflags(oflags)); @@ -123,54 +131,63 @@ pub(crate) fn path_open( log::debug!("path_open resolved = {:?}", resolved); log::debug!("path_open oflags = {:?}", nix_all_oflags); - let new_fd = match openat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - nix_all_oflags, - Mode::from_bits_truncate(0o666), - ) { + let new_fd = match unsafe { + openat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + nix_all_oflags, + Mode::from_bits_truncate(0o666), + ) + } { Ok(fd) => fd, Err(e) => { - match e.as_errno() { - // Linux returns ENXIO instead of EOPNOTSUPP when opening a socket - Some(Errno::ENXIO) => { - if let Ok(stat) = fstatat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - AtFlags::AT_SYMLINK_NOFOLLOW, - ) { - if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFSOCK) { - return Err(Error::ENOTSUP); + if let yanix::YanixError::Errno(errno) = e { + match errno { + // Linux returns ENXIO instead of EOPNOTSUPP when opening a socket + Errno::ENXIO => { + if let Ok(stat) = unsafe { + fstatat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + AtFlag::SYMLINK_NOFOLLOW, + ) + } { + if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::IFSOCK) { + return Err(Error::ENOTSUP); + } else { + return Err(Error::ENXIO); + } } else { return Err(Error::ENXIO); } - } else { - return Err(Error::ENXIO); } - } - // Linux returns ENOTDIR instead of ELOOP when using O_NOFOLLOW|O_DIRECTORY - // on a symlink. - Some(Errno::ENOTDIR) - if !(nix_all_oflags & (OFlag::O_NOFOLLOW | OFlag::O_DIRECTORY)).is_empty() => - { - if let Ok(stat) = fstatat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - AtFlags::AT_SYMLINK_NOFOLLOW, - ) { - if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFLNK) { - return Err(Error::ELOOP); + // Linux returns ENOTDIR instead of ELOOP when using O_NOFOLLOW|O_DIRECTORY + // on a symlink. + Errno::ENOTDIR + if !(nix_all_oflags & (OFlag::NOFOLLOW | OFlag::DIRECTORY)).is_empty() => + { + if let Ok(stat) = unsafe { + fstatat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + AtFlag::SYMLINK_NOFOLLOW, + ) + } { + if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::IFLNK) { + return Err(Error::ELOOP); + } } + return Err(Error::ENOTDIR); } - return Err(Error::ENOTDIR); + // FreeBSD returns EMLINK instead of ELOOP when using O_NOFOLLOW on + // a symlink. + Errno::EMLINK if !(nix_all_oflags & OFlag::NOFOLLOW).is_empty() => { + return Err(Error::ELOOP); + } + errno => return Err(host_impl::errno_from_nix(errno)), } - // FreeBSD returns EMLINK instead of ELOOP when using O_NOFOLLOW on - // a symlink. - Some(Errno::EMLINK) if !(nix_all_oflags & OFlag::O_NOFOLLOW).is_empty() => { - return Err(Error::ELOOP); - } - Some(e) => return Err(host_impl::errno_from_nix(e)), - None => return Err(Error::ENOSYS), + } else { + return Err(e.into()); } } }; @@ -182,34 +199,16 @@ pub(crate) fn path_open( } pub(crate) fn path_readlink(resolved: PathGet, buf: &mut [u8]) -> Result { - use nix::errno::Errno; - let path_cstr = str_to_cstring(resolved.path())?; - - // Linux requires that the buffer size is positive, whereas POSIX does not. - // Use a fake buffer to store the results if the size is zero. - // TODO: instead of using raw libc::readlinkat call here, this should really - // be fixed in `nix` crate - let fakebuf: &mut [u8] = &mut [0]; - let buf_len = buf.len(); - let len = unsafe { - libc::readlinkat( - resolved.dirfd().as_raw_fd(), - path_cstr.as_ptr() as *const libc::c_char, - if buf_len == 0 { - fakebuf.as_mut_ptr() - } else { - buf.as_mut_ptr() - } as *mut libc::c_char, - if buf_len == 0 { fakebuf.len() } else { buf_len }, - ) - }; - - if len < 0 { - Err(host_impl::errno_from_nix(Errno::last())) - } else { - let len = len as usize; - Ok(if len < buf_len { len } else { buf_len }) + use std::cmp::min; + use yanix::file::readlinkat; + let read_link = unsafe { readlinkat(resolved.dirfd().as_raw_fd(), resolved.path()) } + .map_err(Into::into) + .and_then(host_impl::path_from_host)?; + let copy_len = min(read_link.len(), buf.len()); + if copy_len > 0 { + buf[..copy_len].copy_from_slice(&read_link.as_bytes()[..copy_len]); } + Ok(copy_len) } pub(crate) fn fd_filestat_get_impl(file: &std::fs::File) -> Result { @@ -229,8 +228,8 @@ pub(crate) fn fd_filestat_get_impl(file: &std::fs::File) -> Result Result { - use nix::sys::socket::{self, SockType}; use std::os::unix::fs::FileTypeExt; + use yanix::socket::{get_socket_type, SockType}; let ftype = metadata.file_type(); if ftype.is_file() { Ok(FileType::RegularFile) @@ -243,10 +242,7 @@ fn filetype(file: &File, metadata: &Metadata) -> Result { } else if ftype.is_block_device() { Ok(FileType::BlockDevice) } else if ftype.is_socket() { - match socket::getsockopt(file.as_raw_fd(), socket::sockopt::SockType) - .map_err(|err| err.as_errno().unwrap()) - .map_err(host_impl::errno_from_nix)? - { + match unsafe { get_socket_type(file.as_raw_fd())? } { SockType::Datagram => Ok(FileType::SocketDgram), SockType::Stream => Ok(FileType::SocketStream), _ => Ok(FileType::Unknown), @@ -260,17 +256,14 @@ pub(crate) fn path_filestat_get( resolved: PathGet, dirflags: wasi::__wasi_lookupflags_t, ) -> Result { - use nix::fcntl::AtFlags; - use nix::sys::stat::fstatat; - + use yanix::file::{fstatat, AtFlag}; let atflags = match dirflags { - 0 => AtFlags::empty(), - _ => AtFlags::AT_SYMLINK_NOFOLLOW, + 0 => AtFlag::empty(), + _ => AtFlag::SYMLINK_NOFOLLOW, }; - - let filestat = fstatat(resolved.dirfd().as_raw_fd(), resolved.path(), atflags) - .map_err(|err| host_impl::errno_from_nix(err.as_errno().unwrap()))?; - host_impl::filestat_from_nix(filestat) + unsafe { fstatat(resolved.dirfd().as_raw_fd(), resolved.path(), atflags) } + .map_err(Into::into) + .and_then(host_impl::filestat_from_nix) } pub(crate) fn path_filestat_set_times( @@ -321,20 +314,49 @@ pub(crate) fn path_filestat_set_times( } pub(crate) fn path_remove_directory(resolved: PathGet) -> Result<()> { - use nix::errno; - use nix::libc::{unlinkat, AT_REMOVEDIR}; - - let path_cstr = str_to_cstring(resolved.path())?; - - // nix doesn't expose unlinkat() yet - match unsafe { + use yanix::file::{unlinkat, AtFlag}; + unsafe { unlinkat( resolved.dirfd().as_raw_fd(), - path_cstr.as_ptr(), - AT_REMOVEDIR, + resolved.path(), + AtFlag::REMOVEDIR, ) - } { - 0 => Ok(()), - _ => Err(host_impl::errno_from_nix(errno::Errno::last())), } + .map_err(Into::into) +} + +pub(crate) fn fd_readdir<'a>( + os_handle: &'a mut OsHandle, + cookie: wasi::__wasi_dircookie_t, +) -> Result> + 'a> { + use yanix::dir::{DirIter, Entry, EntryExt, SeekLoc}; + + // Get an instance of `Dir`; this is host-specific due to intricasies + // of managing a dir stream between Linux and BSD *nixes + let mut dir = fd_readdir_impl::get_dir_from_os_handle(os_handle)?; + + // Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START, + // new items may not be returned to the caller. + if cookie == wasi::__WASI_DIRCOOKIE_START { + log::trace!(" | fd_readdir: doing rewinddir"); + dir.rewind(); + } else { + log::trace!(" | fd_readdir: doing seekdir to {}", cookie); + let loc = unsafe { SeekLoc::from_raw(cookie as i64) }; + dir.seek(loc); + } + + Ok(DirIter::new(dir).map(|entry| { + let entry: Entry = entry?; + Ok(Dirent { + name: entry + // TODO can we reuse path_from_host for CStr? + .file_name() + .to_str()? + .to_owned(), + ino: entry.ino(), + ftype: entry.file_type().into(), + cookie: entry.seek_loc().to_raw().try_into()?, + }) + })) } diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/hostcalls_impl/fs_helpers.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/hostcalls_impl/fs_helpers.rs index eb83287599..609e42dc5c 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/hostcalls_impl/fs_helpers.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/hostcalls_impl/fs_helpers.rs @@ -3,6 +3,7 @@ use crate::old::snapshot_0::sys::host_impl; use crate::old::snapshot_0::{wasi, Result}; use std::fs::File; +use yanix::file::OFlag; pub(crate) fn path_open_rights( rights_base: wasi::__wasi_rights_t, @@ -10,27 +11,25 @@ pub(crate) fn path_open_rights( oflags: wasi::__wasi_oflags_t, fs_flags: wasi::__wasi_fdflags_t, ) -> (wasi::__wasi_rights_t, wasi::__wasi_rights_t) { - use nix::fcntl::OFlag; - // which rights are needed on the dirfd? let mut needed_base = wasi::__WASI_RIGHTS_PATH_OPEN; let mut needed_inheriting = rights_base | rights_inheriting; // convert open flags let oflags = host_impl::nix_from_oflags(oflags); - if oflags.contains(OFlag::O_CREAT) { + if oflags.contains(OFlag::CREAT) { needed_base |= wasi::__WASI_RIGHTS_PATH_CREATE_FILE; } - if oflags.contains(OFlag::O_TRUNC) { + if oflags.contains(OFlag::TRUNC) { needed_base |= wasi::__WASI_RIGHTS_PATH_FILESTAT_SET_SIZE; } // convert file descriptor flags let fdflags = host_impl::nix_from_fdflags(fs_flags); - if fdflags.contains(OFlag::O_DSYNC) { + if fdflags.contains(OFlag::DSYNC) { needed_inheriting |= wasi::__WASI_RIGHTS_FD_DATASYNC; } - if fdflags.intersects(host_impl::O_RSYNC | OFlag::O_SYNC) { + if fdflags.intersects(host_impl::O_RSYNC | OFlag::SYNC) { needed_inheriting |= wasi::__WASI_RIGHTS_FD_SYNC; } @@ -38,31 +37,30 @@ pub(crate) fn path_open_rights( } pub(crate) fn openat(dirfd: &File, path: &str) -> Result { - use nix::fcntl::{self, OFlag}; - use nix::sys::stat::Mode; use std::os::unix::prelude::{AsRawFd, FromRawFd}; + use yanix::file::{openat, Mode}; log::debug!("path_get openat path = {:?}", path); - fcntl::openat( - dirfd.as_raw_fd(), - path, - OFlag::O_RDONLY | OFlag::O_DIRECTORY | OFlag::O_NOFOLLOW, - Mode::empty(), - ) + unsafe { + openat( + dirfd.as_raw_fd(), + path, + OFlag::RDONLY | OFlag::DIRECTORY | OFlag::NOFOLLOW, + Mode::empty(), + ) + } .map(|new_fd| unsafe { File::from_raw_fd(new_fd) }) .map_err(Into::into) } pub(crate) fn readlinkat(dirfd: &File, path: &str) -> Result { - use nix::fcntl; use std::os::unix::prelude::AsRawFd; + use yanix::file::readlinkat; log::debug!("path_get readlinkat path = {:?}", path); - let readlink_buf = &mut [0u8; libc::PATH_MAX as usize + 1]; - - fcntl::readlinkat(dirfd.as_raw_fd(), path, readlink_buf) + unsafe { readlinkat(dirfd.as_raw_fd(), path) } .map_err(Into::into) .and_then(host_impl::path_from_host) } diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/hostcalls_impl/misc.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/hostcalls_impl/misc.rs index 43acdf1fd9..ac84e3b7c2 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/hostcalls_impl/misc.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/hostcalls_impl/misc.rs @@ -3,29 +3,22 @@ use crate::old::snapshot_0::hostcalls_impl::{ClockEventData, FdEventData}; use crate::old::snapshot_0::sys::host_impl; use crate::old::snapshot_0::{wasi, Error, Result}; -use nix::libc::{self, c_int}; -use std::mem::MaybeUninit; +use yanix::clock::{clock_getres, ClockId}; -fn wasi_clock_id_to_unix(clock_id: wasi::__wasi_clockid_t) -> Result { - // convert the supported clocks to the libc types, or return EINVAL +fn wasi_clock_id_to_unix(clock_id: wasi::__wasi_clockid_t) -> Result { + // convert the supported clocks to libc types, or return EINVAL match clock_id { - wasi::__WASI_CLOCKID_REALTIME => Ok(libc::CLOCK_REALTIME), - wasi::__WASI_CLOCKID_MONOTONIC => Ok(libc::CLOCK_MONOTONIC), - wasi::__WASI_CLOCKID_PROCESS_CPUTIME_ID => Ok(libc::CLOCK_PROCESS_CPUTIME_ID), - wasi::__WASI_CLOCKID_THREAD_CPUTIME_ID => Ok(libc::CLOCK_THREAD_CPUTIME_ID), + wasi::__WASI_CLOCKID_REALTIME => Ok(ClockId::Realtime), + wasi::__WASI_CLOCKID_MONOTONIC => Ok(ClockId::Monotonic), + wasi::__WASI_CLOCKID_PROCESS_CPUTIME_ID => Ok(ClockId::ProcessCPUTime), + wasi::__WASI_CLOCKID_THREAD_CPUTIME_ID => Ok(ClockId::ThreadCPUTime), _ => Err(Error::EINVAL), } } pub(crate) fn clock_res_get(clock_id: wasi::__wasi_clockid_t) -> Result { let clock_id = wasi_clock_id_to_unix(clock_id)?; - // no `nix` wrapper for clock_getres, so we do it ourselves - let mut timespec = MaybeUninit::::uninit(); - let res = unsafe { libc::clock_getres(clock_id, timespec.as_mut_ptr()) }; - if res != 0 { - return Err(host_impl::errno_from_nix(nix::errno::Errno::last())); - } - let timespec = unsafe { timespec.assume_init() }; + let timespec = clock_getres(clock_id)?; // convert to nanoseconds, returning EOVERFLOW in case of overflow; // this is freelancing a bit from the spec but seems like it'll @@ -46,13 +39,7 @@ pub(crate) fn clock_res_get(clock_id: wasi::__wasi_clockid_t) -> Result Result { let clock_id = wasi_clock_id_to_unix(clock_id)?; - // no `nix` wrapper for clock_getres, so we do it ourselves - let mut timespec = MaybeUninit::::uninit(); - let res = unsafe { libc::clock_gettime(clock_id, timespec.as_mut_ptr()) }; - if res != 0 { - return Err(host_impl::errno_from_nix(nix::errno::Errno::last())); - } - let timespec = unsafe { timespec.assume_init() }; + let timespec = clock_getres(clock_id)?; // convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit // from the spec but seems like it'll be an unusual situation to hit @@ -67,11 +54,11 @@ pub(crate) fn poll_oneoff( fd_events: Vec, events: &mut Vec, ) -> Result<()> { - use nix::{ - errno::Errno, - poll::{poll, PollFd, PollFlags}, - }; use std::{convert::TryInto, os::unix::prelude::AsRawFd}; + use yanix::{ + poll::{poll, PollFd, PollFlags}, + Errno, + }; if fd_events.is_empty() && timeout.is_none() { return Ok(()); @@ -89,13 +76,13 @@ pub(crate) fn poll_oneoff( // events we filtered before. If we get something else here, the code has a serious bug. _ => unreachable!(), }; - PollFd::new(event.descriptor.as_raw_fd(), flags) + unsafe { PollFd::new(event.descriptor.as_raw_fd(), flags) } }) .collect(); let poll_timeout = timeout.map_or(-1, |timeout| { let delay = timeout.delay / 1_000_000; // poll syscall requires delay to expressed in milliseconds - delay.try_into().unwrap_or(c_int::max_value()) + delay.try_into().unwrap_or(libc::c_int::max_value()) }); log::debug!("poll_oneoff poll_timeout = {:?}", poll_timeout); @@ -107,7 +94,7 @@ pub(crate) fn poll_oneoff( } return Err(host_impl::errno_from_nix(Errno::last())); } - Ok(ready) => break ready as usize, + Ok(ready) => break ready, } }; @@ -119,9 +106,6 @@ pub(crate) fn poll_oneoff( }) } -// define the `fionread()` function, equivalent to `ioctl(fd, FIONREAD, *bytes)` -nix::ioctl_read_bad!(fionread, nix::libc::FIONREAD, c_int); - fn poll_oneoff_handle_timeout_event( timeout: ClockEventData, events: &mut Vec, @@ -140,11 +124,11 @@ fn poll_oneoff_handle_timeout_event( } fn poll_oneoff_handle_fd_event<'a>( - ready_events: impl Iterator, nix::poll::PollFd)>, + ready_events: impl Iterator, yanix::poll::PollFd)>, events: &mut Vec, ) -> Result<()> { - use nix::poll::PollFlags; use std::{convert::TryInto, os::unix::prelude::AsRawFd}; + use yanix::{file::fionread, poll::PollFlags}; for (fd_event, poll_fd) in ready_events { log::debug!("poll_oneoff_handle_fd_event fd_event = {:?}", fd_event); @@ -157,10 +141,11 @@ fn poll_oneoff_handle_fd_event<'a>( log::debug!("poll_oneoff_handle_fd_event revents = {:?}", revents); - let mut nbytes = 0; - if fd_event.r#type == wasi::__WASI_EVENTTYPE_FD_READ { - let _ = unsafe { fionread(fd_event.descriptor.as_raw_fd(), &mut nbytes) }; - } + let nbytes = if fd_event.r#type == wasi::__WASI_EVENTTYPE_FD_READ { + unsafe { fionread(fd_event.descriptor.as_raw_fd())? } + } else { + 0 + }; let output_event = if revents.contains(PollFlags::POLLNVAL) { wasi::__wasi_event_t { diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/hostcalls_impl.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/hostcalls_impl.rs index 538eed841c..86b1ec443e 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/hostcalls_impl.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/hostcalls_impl.rs @@ -1,176 +1,60 @@ -use super::super::dir::{Dir, Entry, SeekLoc}; -use crate::old::snapshot_0::hostcalls_impl::{Dirent, PathGet}; -use crate::old::snapshot_0::sys::host_impl; -use crate::old::snapshot_0::sys::unix::str_to_cstring; -use crate::old::snapshot_0::{wasi, Error, Result}; -use log::trace; -use std::convert::TryInto; -use std::fs::File; +use crate::old::snapshot_0::hostcalls_impl::PathGet; +use crate::old::snapshot_0::Result; use std::os::unix::prelude::AsRawFd; pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> { - use nix::errno; - use nix::libc::unlinkat; - - let path_cstr = str_to_cstring(resolved.path())?; - - // nix doesn't expose unlinkat() yet - let res = unsafe { unlinkat(resolved.dirfd().as_raw_fd(), path_cstr.as_ptr(), 0) }; - if res == 0 { - Ok(()) - } else { - Err(host_impl::errno_from_nix(errno::Errno::last())) + use yanix::file::{unlinkat, AtFlag}; + unsafe { + unlinkat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + AtFlag::empty(), + ) } + .map_err(Into::into) } pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> Result<()> { - use nix::{errno::Errno, libc::symlinkat}; - - let old_path_cstr = str_to_cstring(old_path)?; - let new_path_cstr = str_to_cstring(resolved.path())?; + use yanix::file::symlinkat; log::debug!("path_symlink old_path = {:?}", old_path); log::debug!("path_symlink resolved = {:?}", resolved); - let res = unsafe { - symlinkat( - old_path_cstr.as_ptr(), - resolved.dirfd().as_raw_fd(), - new_path_cstr.as_ptr(), - ) - }; - if res != 0 { - Err(host_impl::errno_from_nix(Errno::last())) - } else { - Ok(()) - } + unsafe { symlinkat(old_path, resolved.dirfd().as_raw_fd(), resolved.path()) } + .map_err(Into::into) } pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> { - use nix::libc::renameat; - let old_path_cstr = str_to_cstring(resolved_old.path())?; - let new_path_cstr = str_to_cstring(resolved_new.path())?; - - let res = unsafe { + use yanix::file::renameat; + unsafe { renameat( resolved_old.dirfd().as_raw_fd(), - old_path_cstr.as_ptr(), + resolved_old.path(), resolved_new.dirfd().as_raw_fd(), - new_path_cstr.as_ptr(), + resolved_new.path(), ) - }; - if res != 0 { - Err(host_impl::errno_from_nix(nix::errno::Errno::last())) - } else { - Ok(()) } + .map_err(Into::into) } -pub(crate) fn fd_readdir( - fd: &File, - cookie: wasi::__wasi_dircookie_t, -) -> Result>> { - // We need to duplicate the fd, because `opendir(3)`: - // After a successful call to fdopendir(), fd is used internally by the implementation, - // and should not otherwise be used by the application. - // `opendir(3p)` also says that it's undefined behavior to - // modify the state of the fd in a different way than by accessing DIR*. - // - // Still, rewinddir will be needed because the two file descriptors - // share progress. But we can safely execute closedir now. - let fd = fd.try_clone()?; - let mut dir = Dir::from(fd)?; +pub(crate) mod fd_readdir_impl { + use crate::old::snapshot_0::sys::fdentry_impl::OsHandle; + use crate::old::snapshot_0::Result; + use yanix::dir::Dir; - // Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START, - // new items may not be returned to the caller. - // - // According to `opendir(3p)`: - // If a file is removed from or added to the directory after the most recent call - // to opendir() or rewinddir(), whether a subsequent call to readdir() returns an entry - // for that file is unspecified. - if cookie == wasi::__WASI_DIRCOOKIE_START { - trace!(" | fd_readdir: doing rewinddir"); - dir.rewind(); - } else { - trace!(" | fd_readdir: doing seekdir to {}", cookie); - let loc = unsafe { SeekLoc::from_raw(cookie as i64) }; - dir.seek(loc); - } - - Ok(DirIter(dir).map(|entry| { - let entry: Entry = entry?; - Ok(Dirent { - name: entry // TODO can we reuse path_from_host for CStr? - .file_name() - .to_str()? - .to_owned(), - ino: entry.ino(), - ftype: entry.file_type().into(), - cookie: entry.seek_loc().to_raw().try_into()?, - }) - })) -} - -struct DirIter(Dir); - -impl Iterator for DirIter { - type Item = nix::Result; - - fn next(&mut self) -> Option { - use libc::{dirent64, readdir64_r}; - use nix::errno::Errno; - - unsafe { - // Note: POSIX specifies that portable applications should dynamically allocate a - // buffer with room for a `d_name` field of size `pathconf(..., _PC_NAME_MAX)` plus 1 - // for the NUL byte. It doesn't look like the std library does this; it just uses - // fixed-sized buffers (and libc's dirent seems to be sized so this is appropriate). - // Probably fine here too then. - // - // See `impl Iterator for ReadDir` [1] for more details. - // [1] https://github.com/rust-lang/rust/blob/master/src/libstd/sys/unix/fs.rs - let mut ent = std::mem::MaybeUninit::::uninit(); - let mut result = std::ptr::null_mut(); - if let Err(e) = Errno::result(readdir64_r( - (self.0).0.as_ptr(), - ent.as_mut_ptr(), - &mut result, - )) { - return Some(Err(e)); - } - if result.is_null() { - None - } else { - assert_eq!(result, ent.as_mut_ptr(), "readdir_r specification violated"); - Some(Ok(Entry(ent.assume_init()))) - } - } + pub(crate) fn get_dir_from_os_handle(os_handle: &mut OsHandle) -> Result> { + // We need to duplicate the fd, because `opendir(3)`: + // After a successful call to fdopendir(), fd is used internally by the implementation, + // and should not otherwise be used by the application. + // `opendir(3p)` also says that it's undefined behavior to + // modify the state of the fd in a different way than by accessing DIR*. + // + // Still, rewinddir will be needed because the two file descriptors + // share progress. But we can safely execute closedir now. + let fd = os_handle.try_clone()?; + // TODO This doesn't look very clean. Can we do something about it? + // Boxing is needed here in order to satisfy `yanix`'s trait requirement for the `DirIter` + // where `T: Deref`. + Ok(Box::new(Dir::from(fd)?)) } } - -pub(crate) fn fd_advise( - file: &File, - advice: wasi::__wasi_advice_t, - offset: wasi::__wasi_filesize_t, - len: wasi::__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 { - wasi::__WASI_ADVICE_DONTNEED => PosixFadviseAdvice::POSIX_FADV_DONTNEED, - wasi::__WASI_ADVICE_SEQUENTIAL => PosixFadviseAdvice::POSIX_FADV_SEQUENTIAL, - wasi::__WASI_ADVICE_WILLNEED => PosixFadviseAdvice::POSIX_FADV_WILLNEED, - wasi::__WASI_ADVICE_NOREUSE => PosixFadviseAdvice::POSIX_FADV_NOREUSE, - wasi::__WASI_ADVICE_RANDOM => PosixFadviseAdvice::POSIX_FADV_RANDOM, - wasi::__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/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/mod.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/mod.rs index e16410b61e..c177e9d6e4 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/mod.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/mod.rs @@ -2,42 +2,16 @@ pub(crate) mod filetime; pub(crate) mod hostcalls_impl; pub(crate) mod oshandle; -pub(crate) mod fdentry_impl { - use crate::old::snapshot_0::{sys::host_impl, Result}; - use std::os::unix::prelude::AsRawFd; - - pub(crate) unsafe fn isatty(fd: &impl AsRawFd) -> Result { - use nix::errno::Errno; - - let res = libc::isatty(fd.as_raw_fd()); - if res == 1 { - // isatty() returns 1 if fd is an open file descriptor referring to a terminal... - Ok(true) - } else { - // ... otherwise 0 is returned, and errno is set to indicate the error. - match Errno::last() { - // While POSIX specifies ENOTTY if the passed - // fd is *not* a tty, on Linux, some implementations - // may return EINVAL instead. - // - // https://linux.die.net/man/3/isatty - Errno::ENOTTY | Errno::EINVAL => Ok(false), - x => Err(host_impl::errno_from_nix(x)), - } - } - } -} - pub(crate) mod host_impl { use crate::old::snapshot_0::{wasi, Result}; - pub(crate) const O_RSYNC: nix::fcntl::OFlag = nix::fcntl::OFlag::O_RSYNC; + pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::RSYNC; - pub(crate) fn stdev_from_nix(dev: nix::libc::dev_t) -> Result { + pub(crate) fn stdev_from_nix(dev: libc::dev_t) -> Result { Ok(wasi::__wasi_device_t::from(dev)) } - pub(crate) fn stino_from_nix(ino: nix::libc::ino_t) -> Result { + pub(crate) fn stino_from_nix(ino: libc::ino_t) -> Result { Ok(wasi::__wasi_device_t::from(ino)) } } diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/mod.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/mod.rs index b6288512dd..3a4db31aea 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/mod.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/mod.rs @@ -2,7 +2,6 @@ pub(crate) mod fdentry_impl; pub(crate) mod host_impl; pub(crate) mod hostcalls_impl; -mod dir; mod filetime; #[cfg(any( @@ -17,8 +16,7 @@ mod bsd; #[cfg(target_os = "linux")] mod linux; -use crate::old::snapshot_0::{Error, Result}; -use std::ffi::CString; +use crate::old::snapshot_0::Result; use std::fs::{File, OpenOptions}; pub(crate) fn dev_null() -> Result { @@ -28,7 +26,3 @@ pub(crate) fn dev_null() -> Result { .open("/dev/null") .map_err(Into::into) } - -pub(crate) fn str_to_cstring(s: &str) -> Result { - CString::new(s.as_bytes()).map_err(|_| Error::EILSEQ) -} diff --git a/crates/wasi-common/src/old/snapshot_0/sys/windows/hostcalls_impl/fs.rs b/crates/wasi-common/src/old/snapshot_0/sys/windows/hostcalls_impl/fs.rs index a3c8acee9d..84228c28d9 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/windows/hostcalls_impl/fs.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/windows/hostcalls_impl/fs.rs @@ -4,9 +4,8 @@ use super::fs_helpers::*; use crate::old::snapshot_0::ctx::WasiCtx; use crate::old::snapshot_0::fdentry::FdEntry; use crate::old::snapshot_0::helpers::systemtime_to_timestamp; -use crate::old::snapshot_0::hostcalls_impl::{ - fd_filestat_set_times_impl, Dirent, FileType, PathGet, -}; +use crate::old::snapshot_0::host::{Dirent, FileType}; +use crate::old::snapshot_0::hostcalls_impl::{fd_filestat_set_times_impl, PathGet}; use crate::old::snapshot_0::sys::fdentry_impl::determine_type_rights; use crate::old::snapshot_0::sys::host_impl::{self, path_from_host}; use crate::old::snapshot_0::sys::hostcalls_impl::fs_helpers::PathGetExt; diff --git a/crates/wasi-common/src/sys/mod.rs b/crates/wasi-common/src/sys/mod.rs index 972726bd56..db73bf1a78 100644 --- a/crates/wasi-common/src/sys/mod.rs +++ b/crates/wasi-common/src/sys/mod.rs @@ -8,7 +8,7 @@ cfg_if! { pub use self::unix::preopen_dir; pub(crate) fn errno_from_host(err: i32) -> wasi::__wasi_errno_t { - host_impl::errno_from_nix(nix::errno::from_i32(err)).as_wasi_errno() + host_impl::errno_from_nix(yanix::Errno::from_i32(err)).as_wasi_errno() } } else if #[cfg(windows)] { mod windows; diff --git a/crates/wasi-common/src/sys/unix/bsd/hostcalls_impl.rs b/crates/wasi-common/src/sys/unix/bsd/hostcalls_impl.rs index 67194f02ac..015c52da4f 100644 --- a/crates/wasi-common/src/sys/unix/bsd/hostcalls_impl.rs +++ b/crates/wasi-common/src/sys/unix/bsd/hostcalls_impl.rs @@ -1,27 +1,22 @@ -use super::super::dir::{Dir, Entry, SeekLoc}; -use super::oshandle::OsHandle; -use crate::hostcalls_impl::{Dirent, PathGet}; +use crate::hostcalls_impl::PathGet; use crate::sys::host_impl; -use crate::sys::unix::str_to_cstring; -use crate::{wasi, Error, Result}; -use nix::libc; -use std::convert::TryInto; -use std::fs::File; +use crate::{Error, Result}; use std::os::unix::prelude::AsRawFd; -use std::sync::MutexGuard; pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> { - use nix::errno; - use nix::libc::unlinkat; - - let path_cstr = str_to_cstring(resolved.path())?; - - // nix doesn't expose unlinkat() yet - match unsafe { unlinkat(resolved.dirfd().as_raw_fd(), path_cstr.as_ptr(), 0) } { - 0 => Ok(()), - _ => { - let mut e = errno::Errno::last(); - + use yanix::{ + file::{unlinkat, AtFlag}, + Errno, YanixError, + }; + unsafe { + unlinkat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + AtFlag::empty(), + ) + } + .map_err(|err| { + if let YanixError::Errno(mut errno) = err { // Non-Linux implementations may return EPERM when attempting to remove a // directory without REMOVEDIR. While that's what POSIX specifies, it's // less useful. Adjust this to EISDIR. It doesn't matter that this is not @@ -29,84 +24,84 @@ pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> { // is created before fstatat sees it, we're racing with that change anyway // and unlinkat could have legitimately seen the directory if the race had // turned out differently. - use nix::fcntl::AtFlags; - use nix::sys::stat::{fstatat, SFlag}; + use yanix::file::{fstatat, SFlag}; - if e == errno::Errno::EPERM { - if let Ok(stat) = fstatat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - AtFlags::AT_SYMLINK_NOFOLLOW, - ) { - if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFDIR) { - e = errno::Errno::EISDIR; + if errno == Errno::EPERM { + if let Ok(stat) = unsafe { + fstatat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + AtFlag::SYMLINK_NOFOLLOW, + ) + } { + if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::IFDIR) { + errno = Errno::EISDIR; } } else { - e = errno::Errno::last(); + errno = Errno::last(); } } - - Err(host_impl::errno_from_nix(e)) + errno.into() + } else { + err } - } + }) + .map_err(Into::into) } pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> Result<()> { - use nix::{errno::Errno, fcntl::AtFlags, libc::symlinkat, sys::stat::fstatat}; - - let old_path_cstr = str_to_cstring(old_path)?; - let new_path_cstr = str_to_cstring(resolved.path())?; + use yanix::{ + file::{fstatat, symlinkat, AtFlag}, + Errno, YanixError, + }; log::debug!("path_symlink old_path = {:?}", old_path); log::debug!("path_symlink resolved = {:?}", resolved); - let res = unsafe { - symlinkat( - old_path_cstr.as_ptr(), - resolved.dirfd().as_raw_fd(), - new_path_cstr.as_ptr(), - ) - }; - if res != 0 { - match Errno::last() { - Errno::ENOTDIR => { - // On BSD, symlinkat returns ENOTDIR when it should in fact - // return a EEXIST. It seems that it gets confused with by - // the trailing slash in the target path. Thus, we strip - // the trailing slash and check if the path exists, and - // adjust the error code appropriately. - let new_path = resolved.path().trim_end_matches('/'); - if let Ok(_) = fstatat( - resolved.dirfd().as_raw_fd(), - new_path, - AtFlags::AT_SYMLINK_NOFOLLOW, - ) { - Err(Error::EEXIST) - } else { - Err(Error::ENOTDIR) + unsafe { symlinkat(old_path, resolved.dirfd().as_raw_fd(), resolved.path()) }.or_else(|err| { + if let YanixError::Errno(errno) = err { + match errno { + Errno::ENOTDIR => { + // On BSD, symlinkat returns ENOTDIR when it should in fact + // return a EEXIST. It seems that it gets confused with by + // the trailing slash in the target path. Thus, we strip + // the trailing slash and check if the path exists, and + // adjust the error code appropriately. + let new_path = resolved.path().trim_end_matches('/'); + if let Ok(_) = unsafe { + fstatat( + resolved.dirfd().as_raw_fd(), + new_path, + AtFlag::SYMLINK_NOFOLLOW, + ) + } { + Err(Error::EEXIST) + } else { + Err(Error::ENOTDIR) + } } + x => Err(host_impl::errno_from_nix(x)), } - x => Err(host_impl::errno_from_nix(x)), + } else { + Err(err.into()) } - } else { - Ok(()) - } + }) } pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> { - use nix::{errno::Errno, fcntl::AtFlags, libc::renameat, sys::stat::fstatat}; - let old_path_cstr = str_to_cstring(resolved_old.path())?; - let new_path_cstr = str_to_cstring(resolved_new.path())?; - - let res = unsafe { + use yanix::{ + file::{fstatat, renameat, AtFlag}, + Errno, YanixError, + }; + unsafe { renameat( resolved_old.dirfd().as_raw_fd(), - old_path_cstr.as_ptr(), + resolved_old.path(), resolved_new.dirfd().as_raw_fd(), - new_path_cstr.as_ptr(), + resolved_new.path(), ) - }; - if res != 0 { + } + .or_else(|err| { // Currently, this is verified to be correct on macOS, where // ENOENT can be returned in case when we try to rename a file // into a name with a trailing slash. On macOS, if the latter does @@ -116,175 +111,60 @@ pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Resul // // TODO // Verify on other BSD-based OSes. - match Errno::last() { - Errno::ENOENT => { - // check if the source path exists - if let Ok(_) = fstatat( - resolved_old.dirfd().as_raw_fd(), - resolved_old.path(), - AtFlags::AT_SYMLINK_NOFOLLOW, - ) { - // check if destination contains a trailing slash - if resolved_new.path().contains('/') { - Err(Error::ENOTDIR) + if let YanixError::Errno(errno) = err { + match errno { + Errno::ENOENT => { + // check if the source path exists + if let Ok(_) = unsafe { + fstatat( + resolved_old.dirfd().as_raw_fd(), + resolved_old.path(), + AtFlag::SYMLINK_NOFOLLOW, + ) + } { + // check if destination contains a trailing slash + if resolved_new.path().contains('/') { + Err(Error::ENOTDIR) + } else { + Err(Error::ENOENT) + } } else { Err(Error::ENOENT) } - } else { - Err(Error::ENOENT) } + x => Err(host_impl::errno_from_nix(x)), } - x => Err(host_impl::errno_from_nix(x)), + } else { + Err(err.into()) } - } else { - Ok(()) - } + }) } -#[cfg(any(target_os = "macos", target_os = "ios"))] -pub(crate) fn fd_advise( - file: &File, - advice: wasi::__wasi_advice_t, - offset: wasi::__wasi_filesize_t, - len: wasi::__wasi_filesize_t, -) -> Result<()> { - use nix::errno::Errno; +pub(crate) mod fd_readdir_impl { + use crate::sys::fdentry_impl::OsHandle; + use crate::Result; + use std::sync::{Mutex, MutexGuard}; + use yanix::dir::Dir; - match advice { - wasi::__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 - wasi::__WASI_ADVICE_SEQUENTIAL - | wasi::__WASI_ADVICE_WILLNEED - | wasi::__WASI_ADVICE_NOREUSE - | wasi::__WASI_ADVICE_RANDOM - | wasi::__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: wasi::__wasi_advice_t, - _offset: wasi::__wasi_filesize_t, - _len: wasi::__wasi_filesize_t, -) -> Result<()> { - match advice { - wasi::__WASI_ADVICE_DONTNEED - | wasi::__WASI_ADVICE_SEQUENTIAL - | wasi::__WASI_ADVICE_WILLNEED - | wasi::__WASI_ADVICE_NOREUSE - | wasi::__WASI_ADVICE_RANDOM - | wasi::__WASI_ADVICE_NORMAL => {} - _ => return Err(Error::EINVAL), - } - - Ok(()) -} - -pub(crate) fn fd_readdir<'a>( - os_handle: &'a mut OsHandle, - cookie: wasi::__wasi_dircookie_t, -) -> Result> + 'a> { - use std::sync::Mutex; - - let dir = match os_handle.dir { - Some(ref mut dir) => dir, - None => { - // We need to duplicate the fd, because `opendir(3)`: - // Upon successful return from fdopendir(), the file descriptor is under - // control of the system, and if any attempt is made to close the file - // descriptor, or to modify the state of the associated description other - // than by means of closedir(), readdir(), readdir_r(), or rewinddir(), - // the behaviour is undefined. - let fd = (*os_handle).try_clone()?; - let dir = Dir::from(fd)?; - os_handle.dir.get_or_insert(Mutex::new(dir)) - } - }; - let mut dir = dir.lock().unwrap(); - - // Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START, - // new items may not be returned to the caller. - if cookie == wasi::__WASI_DIRCOOKIE_START { - log::trace!(" | fd_readdir: doing rewinddir"); - dir.rewind(); - } else { - log::trace!(" | fd_readdir: doing seekdir to {}", cookie); - let loc = unsafe { SeekLoc::from_raw(cookie as i64) }; - dir.seek(loc); - } - - Ok(DirIter(dir).map(|entry| { - let (entry, loc): (Entry, SeekLoc) = entry?; - Ok(Dirent { - name: entry - // TODO can we reuse path_from_host for CStr? - .file_name() - .to_str()? - .to_owned(), - ino: entry.ino(), - ftype: entry.file_type().into(), - // Set cookie 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 - cookie: loc.to_raw().try_into()?, - }) - })) -} - -struct DirIter<'a>(MutexGuard<'a, Dir>); - -impl<'a> Iterator for DirIter<'a> { - type Item = nix::Result<(Entry, SeekLoc)>; - - fn next(&mut self) -> Option { - use libc::readdir; - use nix::{errno::Errno, Error}; - - unsafe { - let errno = Errno::last(); - let ent = readdir((self.0).0.as_ptr()); - if ent.is_null() { - if errno != Errno::last() { - // TODO This should be verified on different BSD-flavours. - // - // According to 4.3BSD/POSIX.1-2001 man pages, there was an error - // if the errno value has changed at some point during the sequence - // of readdir calls. - Some(Err(Error::last())) - } else { - // Not an error. We've simply reached the end of the stream. - None - } - } else { - let entry = Entry(*ent); - let loc = self.0.tell(); - Some(Ok((entry, loc))) + pub(crate) fn get_dir_from_os_handle<'a>( + os_handle: &'a mut OsHandle, + ) -> Result> { + let dir = match os_handle.dir { + Some(ref mut dir) => dir, + None => { + // We need to duplicate the fd, because `opendir(3)`: + // Upon successful return from fdopendir(), the file descriptor is under + // control of the system, and if any attempt is made to close the file + // descriptor, or to modify the state of the associated description other + // than by means of closedir(), readdir(), readdir_r(), or rewinddir(), + // the behaviour is undefined. + let fd = (*os_handle).try_clone()?; + let dir = Dir::from(fd)?; + os_handle.dir.get_or_insert(Mutex::new(dir)) } - } + }; + // Note that from this point on, until the end of the parent scope (i.e., enclosing this + // function), we're locking the `Dir` member of this `OsHandle`. + Ok(dir.lock().unwrap()) } } diff --git a/crates/wasi-common/src/sys/unix/bsd/mod.rs b/crates/wasi-common/src/sys/unix/bsd/mod.rs index 4fe76b1be7..bdb8dc6f68 100644 --- a/crates/wasi-common/src/sys/unix/bsd/mod.rs +++ b/crates/wasi-common/src/sys/unix/bsd/mod.rs @@ -2,36 +2,17 @@ pub(crate) mod filetime; pub(crate) mod hostcalls_impl; pub(crate) mod oshandle; -pub(crate) mod fdentry_impl { - use crate::{sys::host_impl, Result}; - use std::os::unix::prelude::AsRawFd; - - pub(crate) unsafe fn isatty(fd: &impl AsRawFd) -> Result { - let res = libc::isatty(fd.as_raw_fd()); - if res == 1 { - // isatty() returns 1 if fd is an open file descriptor referring to a terminal... - Ok(true) - } else { - // ... otherwise 0 is returned, and errno is set to indicate the error. - match nix::errno::Errno::last() { - nix::errno::Errno::ENOTTY => Ok(false), - x => Err(host_impl::errno_from_nix(x)), - } - } - } -} - pub(crate) mod host_impl { use crate::{wasi, Result}; use std::convert::TryFrom; - pub(crate) const O_RSYNC: nix::fcntl::OFlag = nix::fcntl::OFlag::O_SYNC; + pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::SYNC; - pub(crate) fn stdev_from_nix(dev: nix::libc::dev_t) -> Result { + pub(crate) fn stdev_from_nix(dev: libc::dev_t) -> Result { wasi::__wasi_device_t::try_from(dev).map_err(Into::into) } - pub(crate) fn stino_from_nix(ino: nix::libc::ino_t) -> Result { + pub(crate) fn stino_from_nix(ino: libc::ino_t) -> Result { wasi::__wasi_device_t::try_from(ino).map_err(Into::into) } } diff --git a/crates/wasi-common/src/sys/unix/bsd/oshandle.rs b/crates/wasi-common/src/sys/unix/bsd/oshandle.rs index eccc7ab33e..70d1a8c4e8 100644 --- a/crates/wasi-common/src/sys/unix/bsd/oshandle.rs +++ b/crates/wasi-common/src/sys/unix/bsd/oshandle.rs @@ -1,8 +1,8 @@ -use super::super::dir::Dir; use std::fs; use std::ops::{Deref, DerefMut}; use std::os::unix::prelude::{AsRawFd, RawFd}; use std::sync::Mutex; +use yanix::dir::Dir; #[derive(Debug)] pub(crate) struct OsHandle { diff --git a/crates/wasi-common/src/sys/unix/dir.rs b/crates/wasi-common/src/sys/unix/dir.rs deleted file mode 100644 index e3612dea1c..0000000000 --- a/crates/wasi-common/src/sys/unix/dir.rs +++ /dev/null @@ -1,174 +0,0 @@ -// Based on src/dir.rs from nix -use crate::hostcalls_impl::FileType; -use libc; -use nix::{Error, Result}; -use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; -use std::{ffi, ptr}; - -#[cfg(target_os = "linux")] -use libc::dirent64 as dirent; - -#[cfg(not(target_os = "linux",))] -use libc::dirent; - -/// An open directory. -/// -/// This is a lower-level interface than `std::fs::ReadDir`. Notable differences: -/// * can be opened from a file descriptor (as returned by `openat`, perhaps before knowing -/// if the path represents a file or directory). -/// * implements `AsRawFd`, so it can be passed to `fstat`, `openat`, etc. -/// The file descriptor continues to be owned by the `Dir`, so callers must not keep a `RawFd` -/// after the `Dir` is dropped. -/// * can be iterated through multiple times without closing and reopening the file -/// descriptor. Each iteration rewinds when finished. -/// * returns entries for `.` (current directory) and `..` (parent directory). -/// * returns entries' names as a `CStr` (no allocation or conversion beyond whatever libc -/// does). -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub(crate) struct Dir(pub(crate) ptr::NonNull); - -impl Dir { - /// Converts from a descriptor-based object, closing the descriptor on success or failure. - #[inline] - pub(crate) fn from(fd: F) -> Result { - unsafe { Self::from_fd(fd.into_raw_fd()) } - } - - /// Converts from a file descriptor, closing it on success or failure. - unsafe fn from_fd(fd: RawFd) -> Result { - let d = libc::fdopendir(fd); - if d.is_null() { - let e = Error::last(); - libc::close(fd); - return Err(e); - }; - // Always guaranteed to be non-null by the previous check - Ok(Self(ptr::NonNull::new(d).unwrap())) - } - - /// Set the position of the directory stream, see `seekdir(3)`. - #[cfg(not(target_os = "android"))] - pub(crate) fn seek(&mut self, loc: SeekLoc) { - unsafe { libc::seekdir(self.0.as_ptr(), loc.0) } - } - - /// Reset directory stream, see `rewinddir(3)`. - pub(crate) fn rewind(&mut self) { - unsafe { libc::rewinddir(self.0.as_ptr()) } - } - - /// Get the current position in the directory stream. - /// - /// If this location is given to `Dir::seek`, the entries up to the previously returned - /// will be omitted and the iteration will start from the currently pending directory entry. - #[cfg(not(target_os = "android"))] - #[allow(dead_code)] - pub(crate) fn tell(&self) -> SeekLoc { - let loc = unsafe { libc::telldir(self.0.as_ptr()) }; - SeekLoc(loc) - } -} - -// `Dir` is not `Sync`. With the current implementation, it could be, but according to -// https://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html, -// future versions of POSIX are likely to obsolete `readdir_r` and specify that it's unsafe to -// call `readdir` simultaneously from multiple threads. -// -// `Dir` is safe to pass from one thread to another, as it's not reference-counted. -unsafe impl Send for Dir {} - -impl AsRawFd for Dir { - fn as_raw_fd(&self) -> RawFd { - unsafe { libc::dirfd(self.0.as_ptr()) } - } -} - -impl Drop for Dir { - fn drop(&mut self) { - unsafe { libc::closedir(self.0.as_ptr()) }; - } -} - -/// A directory entry, similar to `std::fs::DirEntry`. -/// -/// Note that unlike the std version, this may represent the `.` or `..` entries. -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] -#[repr(transparent)] -pub(crate) struct Entry(pub(crate) dirent); - -pub(crate) type Type = FileType; - -impl Entry { - /// Returns the inode number (`d_ino`) of the underlying `dirent`. - #[cfg(any( - target_os = "android", - target_os = "emscripten", - target_os = "fuchsia", - target_os = "haiku", - target_os = "ios", - target_os = "l4re", - target_os = "linux", - target_os = "macos", - target_os = "solaris" - ))] - pub(crate) fn ino(&self) -> u64 { - self.0.d_ino.into() - } - - /// Returns the inode number (`d_fileno`) of the underlying `dirent`. - #[cfg(not(any( - target_os = "android", - target_os = "emscripten", - target_os = "fuchsia", - target_os = "haiku", - target_os = "ios", - target_os = "l4re", - target_os = "linux", - target_os = "macos", - target_os = "solaris" - )))] - pub(crate) fn ino(&self) -> u64 { - u64::from(self.0.d_fileno) - } - - /// Returns the bare file name of this directory entry without any other leading path component. - pub(crate) fn file_name(&self) -> &ffi::CStr { - unsafe { ::std::ffi::CStr::from_ptr(self.0.d_name.as_ptr()) } - } - - /// Returns the type of this directory entry, if known. - /// - /// See platform `readdir(3)` or `dirent(5)` manpage for when the file type is known; - /// notably, some Linux filesystems don't implement this. The caller should use `stat` or - /// `fstat` if this returns `None`. - pub(crate) fn file_type(&self) -> FileType { - match self.0.d_type { - libc::DT_CHR => Type::CharacterDevice, - libc::DT_DIR => Type::Directory, - libc::DT_BLK => Type::BlockDevice, - libc::DT_REG => Type::RegularFile, - libc::DT_LNK => Type::Symlink, - /* libc::DT_UNKNOWN | libc::DT_SOCK | libc::DT_FIFO */ _ => Type::Unknown, - } - } - - #[cfg(target_os = "linux")] - pub(crate) fn seek_loc(&self) -> SeekLoc { - unsafe { SeekLoc::from_raw(self.0.d_off) } - } -} - -#[cfg(not(target_os = "android"))] -#[derive(Clone, Copy, Debug)] -pub(crate) struct SeekLoc(libc::c_long); - -#[cfg(not(target_os = "android"))] -impl SeekLoc { - pub(crate) unsafe fn from_raw(loc: i64) -> Self { - Self(loc.into()) - } - - pub(crate) fn to_raw(&self) -> i64 { - self.0.into() - } -} diff --git a/crates/wasi-common/src/sys/unix/fdentry_impl.rs b/crates/wasi-common/src/sys/unix/fdentry_impl.rs index 8015eebf6f..b178fcb38e 100644 --- a/crates/wasi-common/src/sys/unix/fdentry_impl.rs +++ b/crates/wasi-common/src/sys/unix/fdentry_impl.rs @@ -8,7 +8,6 @@ use std::os::unix::prelude::{AsRawFd, FileTypeExt, FromRawFd, RawFd}; cfg_if::cfg_if! { if #[cfg(target_os = "linux")] { pub(crate) use super::linux::oshandle::*; - pub(crate) use super::linux::fdentry_impl::*; } else if #[cfg(any( target_os = "macos", target_os = "netbsd", @@ -18,7 +17,6 @@ cfg_if::cfg_if! { target_os = "dragonfly" ))] { pub(crate) use super::bsd::oshandle::*; - pub(crate) use super::bsd::fdentry_impl::*; } } @@ -51,13 +49,12 @@ pub(crate) unsafe fn determine_type_and_access_rights( )> { let (file_type, mut rights_base, rights_inheriting) = determine_type_rights(fd)?; - use nix::fcntl::{fcntl, OFlag, F_GETFL}; - let flags_bits = fcntl(fd.as_raw_fd(), F_GETFL)?; - let flags = OFlag::from_bits_truncate(flags_bits); - let accmode = flags & OFlag::O_ACCMODE; - if accmode == OFlag::O_RDONLY { + use yanix::{fcntl, file::OFlag}; + let flags = fcntl::get_status_flags(fd.as_raw_fd())?; + let accmode = flags & OFlag::ACCMODE; + if accmode == OFlag::RDONLY { rights_base &= !wasi::__WASI_RIGHTS_FD_WRITE; - } else if accmode == OFlag::O_WRONLY { + } else if accmode == OFlag::WRONLY { rights_base &= !wasi::__WASI_RIGHTS_FD_READ; } @@ -85,7 +82,8 @@ pub(crate) unsafe fn determine_type_rights( ) } else if ft.is_char_device() { log::debug!("Host fd {:?} is a char device", fd.as_raw_fd()); - if isatty(fd)? { + use yanix::file::isatty; + if isatty(fd.as_raw_fd())? { ( wasi::__WASI_FILETYPE_CHARACTER_DEVICE, wasi::RIGHTS_TTY_BASE, @@ -114,14 +112,14 @@ pub(crate) unsafe fn determine_type_rights( ) } else if ft.is_socket() { log::debug!("Host fd {:?} is a socket", fd.as_raw_fd()); - use nix::sys::socket; - match socket::getsockopt(fd.as_raw_fd(), socket::sockopt::SockType)? { - socket::SockType::Datagram => ( + use yanix::socket::{get_socket_type, SockType}; + match get_socket_type(fd.as_raw_fd())? { + SockType::Datagram => ( wasi::__WASI_FILETYPE_SOCKET_DGRAM, wasi::RIGHTS_SOCKET_BASE, wasi::RIGHTS_SOCKET_INHERITING, ), - socket::SockType::Stream => ( + SockType::Stream => ( wasi::__WASI_FILETYPE_SOCKET_STREAM, wasi::RIGHTS_SOCKET_BASE, wasi::RIGHTS_SOCKET_INHERITING, diff --git a/crates/wasi-common/src/sys/unix/host_impl.rs b/crates/wasi-common/src/sys/unix/host_impl.rs index 9807cda5f2..41306837b0 100644 --- a/crates/wasi-common/src/sys/unix/host_impl.rs +++ b/crates/wasi-common/src/sys/unix/host_impl.rs @@ -2,11 +2,14 @@ #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(dead_code)] -use crate::hostcalls_impl::FileType; +use crate::host::FileType; use crate::{helpers, wasi, Error, Result}; -use log::warn; use std::ffi::OsStr; use std::os::unix::prelude::OsStrExt; +use yanix::{ + file::{OFlag, SFlag}, + Errno, +}; cfg_if::cfg_if! { if #[cfg(target_os = "linux")] { @@ -23,178 +26,168 @@ cfg_if::cfg_if! { } } -pub(crate) fn errno_from_nix(errno: nix::errno::Errno) -> Error { +pub(crate) fn errno_from_nix(errno: Errno) -> Error { match errno { - nix::errno::Errno::EPERM => Error::EPERM, - nix::errno::Errno::ENOENT => Error::ENOENT, - nix::errno::Errno::ESRCH => Error::ESRCH, - nix::errno::Errno::EINTR => Error::EINTR, - nix::errno::Errno::EIO => Error::EIO, - nix::errno::Errno::ENXIO => Error::ENXIO, - nix::errno::Errno::E2BIG => Error::E2BIG, - nix::errno::Errno::ENOEXEC => Error::ENOEXEC, - nix::errno::Errno::EBADF => Error::EBADF, - nix::errno::Errno::ECHILD => Error::ECHILD, - nix::errno::Errno::EAGAIN => Error::EAGAIN, - nix::errno::Errno::ENOMEM => Error::ENOMEM, - nix::errno::Errno::EACCES => Error::EACCES, - nix::errno::Errno::EFAULT => Error::EFAULT, - nix::errno::Errno::EBUSY => Error::EBUSY, - nix::errno::Errno::EEXIST => Error::EEXIST, - nix::errno::Errno::EXDEV => Error::EXDEV, - nix::errno::Errno::ENODEV => Error::ENODEV, - nix::errno::Errno::ENOTDIR => Error::ENOTDIR, - nix::errno::Errno::EISDIR => Error::EISDIR, - nix::errno::Errno::EINVAL => Error::EINVAL, - nix::errno::Errno::ENFILE => Error::ENFILE, - nix::errno::Errno::EMFILE => Error::EMFILE, - nix::errno::Errno::ENOTTY => Error::ENOTTY, - nix::errno::Errno::ETXTBSY => Error::ETXTBSY, - nix::errno::Errno::EFBIG => Error::EFBIG, - nix::errno::Errno::ENOSPC => Error::ENOSPC, - nix::errno::Errno::ESPIPE => Error::ESPIPE, - nix::errno::Errno::EROFS => Error::EROFS, - nix::errno::Errno::EMLINK => Error::EMLINK, - nix::errno::Errno::EPIPE => Error::EPIPE, - nix::errno::Errno::EDOM => Error::EDOM, - nix::errno::Errno::ERANGE => Error::ERANGE, - nix::errno::Errno::EDEADLK => Error::EDEADLK, - nix::errno::Errno::ENAMETOOLONG => Error::ENAMETOOLONG, - nix::errno::Errno::ENOLCK => Error::ENOLCK, - nix::errno::Errno::ENOSYS => Error::ENOSYS, - nix::errno::Errno::ENOTEMPTY => Error::ENOTEMPTY, - nix::errno::Errno::ELOOP => Error::ELOOP, - nix::errno::Errno::ENOMSG => Error::ENOMSG, - nix::errno::Errno::EIDRM => Error::EIDRM, - nix::errno::Errno::ENOLINK => Error::ENOLINK, - nix::errno::Errno::EPROTO => Error::EPROTO, - nix::errno::Errno::EMULTIHOP => Error::EMULTIHOP, - nix::errno::Errno::EBADMSG => Error::EBADMSG, - nix::errno::Errno::EOVERFLOW => Error::EOVERFLOW, - nix::errno::Errno::EILSEQ => Error::EILSEQ, - nix::errno::Errno::ENOTSOCK => Error::ENOTSOCK, - nix::errno::Errno::EDESTADDRREQ => Error::EDESTADDRREQ, - nix::errno::Errno::EMSGSIZE => Error::EMSGSIZE, - nix::errno::Errno::EPROTOTYPE => Error::EPROTOTYPE, - nix::errno::Errno::ENOPROTOOPT => Error::ENOPROTOOPT, - nix::errno::Errno::EPROTONOSUPPORT => Error::EPROTONOSUPPORT, - nix::errno::Errno::EAFNOSUPPORT => Error::EAFNOSUPPORT, - nix::errno::Errno::EADDRINUSE => Error::EADDRINUSE, - nix::errno::Errno::EADDRNOTAVAIL => Error::EADDRNOTAVAIL, - nix::errno::Errno::ENETDOWN => Error::ENETDOWN, - nix::errno::Errno::ENETUNREACH => Error::ENETUNREACH, - nix::errno::Errno::ENETRESET => Error::ENETRESET, - nix::errno::Errno::ECONNABORTED => Error::ECONNABORTED, - nix::errno::Errno::ECONNRESET => Error::ECONNRESET, - nix::errno::Errno::ENOBUFS => Error::ENOBUFS, - nix::errno::Errno::EISCONN => Error::EISCONN, - nix::errno::Errno::ENOTCONN => Error::ENOTCONN, - nix::errno::Errno::ETIMEDOUT => Error::ETIMEDOUT, - nix::errno::Errno::ECONNREFUSED => Error::ECONNREFUSED, - nix::errno::Errno::EHOSTUNREACH => Error::EHOSTUNREACH, - nix::errno::Errno::EALREADY => Error::EALREADY, - nix::errno::Errno::EINPROGRESS => Error::EINPROGRESS, - nix::errno::Errno::ESTALE => Error::ESTALE, - nix::errno::Errno::EDQUOT => Error::EDQUOT, - nix::errno::Errno::ECANCELED => Error::ECANCELED, - nix::errno::Errno::EOWNERDEAD => Error::EOWNERDEAD, - nix::errno::Errno::ENOTRECOVERABLE => Error::ENOTRECOVERABLE, - other => { - warn!("Unknown error from nix: {}", other); - Error::ENOSYS - } + Errno::EPERM => Error::EPERM, + Errno::ENOENT => Error::ENOENT, + Errno::ESRCH => Error::ESRCH, + Errno::EINTR => Error::EINTR, + Errno::EIO => Error::EIO, + Errno::ENXIO => Error::ENXIO, + Errno::E2BIG => Error::E2BIG, + Errno::ENOEXEC => Error::ENOEXEC, + Errno::EBADF => Error::EBADF, + Errno::ECHILD => Error::ECHILD, + Errno::EAGAIN => Error::EAGAIN, + Errno::ENOMEM => Error::ENOMEM, + Errno::EACCES => Error::EACCES, + Errno::EFAULT => Error::EFAULT, + Errno::EBUSY => Error::EBUSY, + Errno::EEXIST => Error::EEXIST, + Errno::EXDEV => Error::EXDEV, + Errno::ENODEV => Error::ENODEV, + Errno::ENOTDIR => Error::ENOTDIR, + Errno::EISDIR => Error::EISDIR, + Errno::EINVAL => Error::EINVAL, + Errno::ENFILE => Error::ENFILE, + Errno::EMFILE => Error::EMFILE, + Errno::ENOTTY => Error::ENOTTY, + Errno::ETXTBSY => Error::ETXTBSY, + Errno::EFBIG => Error::EFBIG, + Errno::ENOSPC => Error::ENOSPC, + Errno::ESPIPE => Error::ESPIPE, + Errno::EROFS => Error::EROFS, + Errno::EMLINK => Error::EMLINK, + Errno::EPIPE => Error::EPIPE, + Errno::EDOM => Error::EDOM, + Errno::ERANGE => Error::ERANGE, + Errno::EDEADLK => Error::EDEADLK, + Errno::ENAMETOOLONG => Error::ENAMETOOLONG, + Errno::ENOLCK => Error::ENOLCK, + Errno::ENOSYS => Error::ENOSYS, + Errno::ENOTEMPTY => Error::ENOTEMPTY, + Errno::ELOOP => Error::ELOOP, + Errno::ENOMSG => Error::ENOMSG, + Errno::EIDRM => Error::EIDRM, + Errno::ENOLINK => Error::ENOLINK, + Errno::EPROTO => Error::EPROTO, + Errno::EMULTIHOP => Error::EMULTIHOP, + Errno::EBADMSG => Error::EBADMSG, + Errno::EOVERFLOW => Error::EOVERFLOW, + Errno::EILSEQ => Error::EILSEQ, + Errno::ENOTSOCK => Error::ENOTSOCK, + Errno::EDESTADDRREQ => Error::EDESTADDRREQ, + Errno::EMSGSIZE => Error::EMSGSIZE, + Errno::EPROTOTYPE => Error::EPROTOTYPE, + Errno::ENOPROTOOPT => Error::ENOPROTOOPT, + Errno::EPROTONOSUPPORT => Error::EPROTONOSUPPORT, + Errno::EAFNOSUPPORT => Error::EAFNOSUPPORT, + Errno::EADDRINUSE => Error::EADDRINUSE, + Errno::EADDRNOTAVAIL => Error::EADDRNOTAVAIL, + Errno::ENETDOWN => Error::ENETDOWN, + Errno::ENETUNREACH => Error::ENETUNREACH, + Errno::ENETRESET => Error::ENETRESET, + Errno::ECONNABORTED => Error::ECONNABORTED, + Errno::ECONNRESET => Error::ECONNRESET, + Errno::ENOBUFS => Error::ENOBUFS, + Errno::EISCONN => Error::EISCONN, + Errno::ENOTCONN => Error::ENOTCONN, + Errno::ETIMEDOUT => Error::ETIMEDOUT, + Errno::ECONNREFUSED => Error::ECONNREFUSED, + Errno::EHOSTUNREACH => Error::EHOSTUNREACH, + Errno::EALREADY => Error::EALREADY, + Errno::EINPROGRESS => Error::EINPROGRESS, + Errno::ESTALE => Error::ESTALE, + Errno::EDQUOT => Error::EDQUOT, + Errno::ECANCELED => Error::ECANCELED, + Errno::EOWNERDEAD => Error::EOWNERDEAD, + Errno::ENOTRECOVERABLE => Error::ENOTRECOVERABLE, } } -pub(crate) fn nix_from_fdflags(fdflags: wasi::__wasi_fdflags_t) -> nix::fcntl::OFlag { - use nix::fcntl::OFlag; +pub(crate) fn nix_from_fdflags(fdflags: wasi::__wasi_fdflags_t) -> OFlag { let mut nix_flags = OFlag::empty(); if fdflags & wasi::__WASI_FDFLAGS_APPEND != 0 { - nix_flags.insert(OFlag::O_APPEND); + nix_flags.insert(OFlag::APPEND); } if fdflags & wasi::__WASI_FDFLAGS_DSYNC != 0 { - nix_flags.insert(OFlag::O_DSYNC); + nix_flags.insert(OFlag::DSYNC); } if fdflags & wasi::__WASI_FDFLAGS_NONBLOCK != 0 { - nix_flags.insert(OFlag::O_NONBLOCK); + nix_flags.insert(OFlag::NONBLOCK); } if fdflags & wasi::__WASI_FDFLAGS_RSYNC != 0 { nix_flags.insert(O_RSYNC); } if fdflags & wasi::__WASI_FDFLAGS_SYNC != 0 { - nix_flags.insert(OFlag::O_SYNC); + nix_flags.insert(OFlag::SYNC); } nix_flags } -pub(crate) fn fdflags_from_nix(oflags: nix::fcntl::OFlag) -> wasi::__wasi_fdflags_t { - use nix::fcntl::OFlag; +pub(crate) fn fdflags_from_nix(oflags: OFlag) -> wasi::__wasi_fdflags_t { let mut fdflags = 0; - if oflags.contains(OFlag::O_APPEND) { + if oflags.contains(OFlag::APPEND) { fdflags |= wasi::__WASI_FDFLAGS_APPEND; } - if oflags.contains(OFlag::O_DSYNC) { + if oflags.contains(OFlag::DSYNC) { fdflags |= wasi::__WASI_FDFLAGS_DSYNC; } - if oflags.contains(OFlag::O_NONBLOCK) { + if oflags.contains(OFlag::NONBLOCK) { fdflags |= wasi::__WASI_FDFLAGS_NONBLOCK; } if oflags.contains(O_RSYNC) { fdflags |= wasi::__WASI_FDFLAGS_RSYNC; } - if oflags.contains(OFlag::O_SYNC) { + if oflags.contains(OFlag::SYNC) { fdflags |= wasi::__WASI_FDFLAGS_SYNC; } fdflags } -pub(crate) fn nix_from_oflags(oflags: wasi::__wasi_oflags_t) -> nix::fcntl::OFlag { - use nix::fcntl::OFlag; +pub(crate) fn nix_from_oflags(oflags: wasi::__wasi_oflags_t) -> OFlag { let mut nix_flags = OFlag::empty(); if oflags & wasi::__WASI_OFLAGS_CREAT != 0 { - nix_flags.insert(OFlag::O_CREAT); + nix_flags.insert(OFlag::CREAT); } if oflags & wasi::__WASI_OFLAGS_DIRECTORY != 0 { - nix_flags.insert(OFlag::O_DIRECTORY); + nix_flags.insert(OFlag::DIRECTORY); } if oflags & wasi::__WASI_OFLAGS_EXCL != 0 { - nix_flags.insert(OFlag::O_EXCL); + nix_flags.insert(OFlag::EXCL); } if oflags & wasi::__WASI_OFLAGS_TRUNC != 0 { - nix_flags.insert(OFlag::O_TRUNC); + nix_flags.insert(OFlag::TRUNC); } nix_flags } -pub(crate) fn filetype_from_nix(sflags: nix::sys::stat::SFlag) -> FileType { - use nix::sys::stat::SFlag; - if sflags.contains(SFlag::S_IFCHR) { +pub(crate) fn filetype_from_nix(sflags: SFlag) -> FileType { + if sflags.contains(SFlag::IFCHR) { FileType::CharacterDevice - } else if sflags.contains(SFlag::S_IFBLK) { + } else if sflags.contains(SFlag::IFBLK) { FileType::BlockDevice - } else if sflags.contains(SFlag::S_IFSOCK) { + } else if sflags.contains(SFlag::IFSOCK) { FileType::SocketStream - } else if sflags.contains(SFlag::S_IFDIR) { + } else if sflags.contains(SFlag::IFDIR) { FileType::Directory - } else if sflags.contains(SFlag::S_IFREG) { + } else if sflags.contains(SFlag::IFREG) { FileType::RegularFile - } else if sflags.contains(SFlag::S_IFLNK) { + } else if sflags.contains(SFlag::IFLNK) { FileType::Symlink } else { FileType::Unknown } } -pub(crate) fn filestat_from_nix( - filestat: nix::sys::stat::FileStat, -) -> Result { +pub(crate) fn filestat_from_nix(filestat: libc::stat) -> Result { fn filestat_to_timestamp(secs: u64, nsecs: u64) -> Result { secs.checked_mul(1_000_000_000) .and_then(|sec_nsec| sec_nsec.checked_add(nsecs)) .ok_or(Error::EOVERFLOW) } - let filetype = nix::sys::stat::SFlag::from_bits_truncate(filestat.st_mode); + let filetype = SFlag::from_bits_truncate(filestat.st_mode); let dev = stdev_from_nix(filestat.st_dev)?; let ino = stino_from_nix(filestat.st_ino)?; let atim = filestat_to_timestamp(filestat.st_atime as u64, filestat.st_atime_nsec as u64)?; @@ -214,7 +207,7 @@ pub(crate) fn filestat_from_nix( } pub(crate) fn dirent_filetype_from_host( - host_entry: &nix::libc::dirent, + host_entry: &libc::dirent, ) -> Result { match host_entry.d_type { libc::DT_FIFO => Ok(wasi::__WASI_FILETYPE_UNKNOWN), @@ -243,3 +236,17 @@ pub(crate) fn dirent_filetype_from_host( pub(crate) fn path_from_host>(s: S) -> Result { helpers::path_from_slice(s.as_ref().as_bytes()).map(String::from) } + +impl From for FileType { + fn from(ft: yanix::dir::FileType) -> Self { + use yanix::dir::FileType::*; + match ft { + RegularFile => Self::RegularFile, + Symlink => Self::Symlink, + Directory => Self::Directory, + BlockDevice => Self::BlockDevice, + CharacterDevice => Self::CharacterDevice, + /* Unknown | Socket | Fifo */ _ => Self::Unknown, + } + } +} diff --git a/crates/wasi-common/src/sys/unix/hostcalls_impl/fs.rs b/crates/wasi-common/src/sys/unix/hostcalls_impl/fs.rs index 8a853d510c..8e6baa7bf5 100644 --- a/crates/wasi-common/src/sys/unix/hostcalls_impl/fs.rs +++ b/crates/wasi-common/src/sys/unix/hostcalls_impl/fs.rs @@ -1,11 +1,10 @@ #![allow(non_camel_case_types)] #![allow(unused_unsafe)] use crate::helpers::systemtime_to_timestamp; -use crate::hostcalls_impl::{FileType, PathGet}; -use crate::sys::host_impl; -use crate::sys::unix::str_to_cstring; +use crate::host::{Dirent, FileType}; +use crate::hostcalls_impl::PathGet; +use crate::sys::{fdentry_impl::OsHandle, host_impl}; use crate::{wasi, Error, Result}; -use nix::libc; use std::convert::TryInto; use std::fs::{File, Metadata}; use std::os::unix::fs::FileExt; @@ -39,53 +38,61 @@ pub(crate) fn fd_pwrite(file: &File, buf: &[u8], offset: wasi::__wasi_filesize_t } pub(crate) fn fd_fdstat_get(fd: &File) -> Result { - use nix::fcntl::{fcntl, OFlag, F_GETFL}; - match fcntl(fd.as_raw_fd(), F_GETFL).map(OFlag::from_bits_truncate) { - Ok(flags) => Ok(host_impl::fdflags_from_nix(flags)), - Err(e) => Err(host_impl::errno_from_nix(e.as_errno().unwrap())), - } + unsafe { yanix::fcntl::get_status_flags(fd.as_raw_fd()) } + .map(host_impl::fdflags_from_nix) + .map_err(Into::into) } pub(crate) fn fd_fdstat_set_flags(fd: &File, fdflags: wasi::__wasi_fdflags_t) -> Result<()> { - use nix::fcntl::{fcntl, F_SETFL}; let nix_flags = host_impl::nix_from_fdflags(fdflags); - match fcntl(fd.as_raw_fd(), F_SETFL(nix_flags)) { - Ok(_) => Ok(()), - Err(e) => Err(host_impl::errno_from_nix(e.as_errno().unwrap())), - } + unsafe { yanix::fcntl::set_status_flags(fd.as_raw_fd(), nix_flags) }.map_err(Into::into) +} + +pub(crate) fn fd_advise( + file: &File, + advice: wasi::__wasi_advice_t, + offset: wasi::__wasi_filesize_t, + len: wasi::__wasi_filesize_t, +) -> Result<()> { + use yanix::fadvise::{posix_fadvise, PosixFadviseAdvice}; + let offset = offset.try_into()?; + let len = len.try_into()?; + let host_advice = match advice { + wasi::__WASI_ADVICE_DONTNEED => PosixFadviseAdvice::DontNeed, + wasi::__WASI_ADVICE_SEQUENTIAL => PosixFadviseAdvice::Sequential, + wasi::__WASI_ADVICE_WILLNEED => PosixFadviseAdvice::WillNeed, + wasi::__WASI_ADVICE_NOREUSE => PosixFadviseAdvice::NoReuse, + wasi::__WASI_ADVICE_RANDOM => PosixFadviseAdvice::Random, + wasi::__WASI_ADVICE_NORMAL => PosixFadviseAdvice::Normal, + _ => return Err(Error::EINVAL), + }; + unsafe { posix_fadvise(file.as_raw_fd(), offset, len, host_advice) }.map_err(Into::into) } pub(crate) fn path_create_directory(resolved: PathGet) -> Result<()> { - use nix::libc::mkdirat; - let path_cstr = str_to_cstring(resolved.path())?; - // nix doesn't expose mkdirat() yet - match unsafe { mkdirat(resolved.dirfd().as_raw_fd(), path_cstr.as_ptr(), 0o777) } { - 0 => Ok(()), - _ => Err(host_impl::errno_from_nix(nix::errno::Errno::last())), + use yanix::file::{mkdirat, Mode}; + unsafe { + mkdirat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + Mode::from_bits_truncate(0o777), + ) } + .map_err(Into::into) } pub(crate) fn path_link(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> { - use nix::libc::linkat; - let old_path_cstr = str_to_cstring(resolved_old.path())?; - let new_path_cstr = str_to_cstring(resolved_new.path())?; - - // Not setting AT_SYMLINK_FOLLOW fails on most filesystems - let atflags = libc::AT_SYMLINK_FOLLOW; - let res = unsafe { + use yanix::file::{linkat, AtFlag}; + unsafe { linkat( resolved_old.dirfd().as_raw_fd(), - old_path_cstr.as_ptr(), + resolved_old.path(), resolved_new.dirfd().as_raw_fd(), - new_path_cstr.as_ptr(), - atflags, + resolved_new.path(), + AtFlag::SYMLINK_FOLLOW, ) - }; - if res != 0 { - Err(host_impl::errno_from_nix(nix::errno::Errno::last())) - } else { - Ok(()) } + .map_err(Into::into) } pub(crate) fn path_open( @@ -95,20 +102,21 @@ pub(crate) fn path_open( oflags: wasi::__wasi_oflags_t, fs_flags: wasi::__wasi_fdflags_t, ) -> Result { - use nix::errno::Errno; - use nix::fcntl::{openat, AtFlags, OFlag}; - use nix::sys::stat::{fstatat, Mode, SFlag}; + use yanix::{ + file::{fstatat, openat, AtFlag, Mode, OFlag, SFlag}, + Errno, + }; let mut nix_all_oflags = if read && write { - OFlag::O_RDWR + OFlag::RDWR } else if write { - OFlag::O_WRONLY + OFlag::WRONLY } else { - OFlag::O_RDONLY + OFlag::RDONLY }; // on non-Capsicum systems, we always want nofollow - nix_all_oflags.insert(OFlag::O_NOFOLLOW); + nix_all_oflags.insert(OFlag::NOFOLLOW); // convert open flags nix_all_oflags.insert(host_impl::nix_from_oflags(oflags)); @@ -123,54 +131,63 @@ pub(crate) fn path_open( log::debug!("path_open resolved = {:?}", resolved); log::debug!("path_open oflags = {:?}", nix_all_oflags); - let new_fd = match openat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - nix_all_oflags, - Mode::from_bits_truncate(0o666), - ) { + let new_fd = match unsafe { + openat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + nix_all_oflags, + Mode::from_bits_truncate(0o666), + ) + } { Ok(fd) => fd, Err(e) => { - match e.as_errno() { - // Linux returns ENXIO instead of EOPNOTSUPP when opening a socket - Some(Errno::ENXIO) => { - if let Ok(stat) = fstatat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - AtFlags::AT_SYMLINK_NOFOLLOW, - ) { - if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFSOCK) { - return Err(Error::ENOTSUP); + if let yanix::YanixError::Errno(errno) = e { + match errno { + // Linux returns ENXIO instead of EOPNOTSUPP when opening a socket + Errno::ENXIO => { + if let Ok(stat) = unsafe { + fstatat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + AtFlag::SYMLINK_NOFOLLOW, + ) + } { + if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::IFSOCK) { + return Err(Error::ENOTSUP); + } else { + return Err(Error::ENXIO); + } } else { return Err(Error::ENXIO); } - } else { - return Err(Error::ENXIO); } - } - // Linux returns ENOTDIR instead of ELOOP when using O_NOFOLLOW|O_DIRECTORY - // on a symlink. - Some(Errno::ENOTDIR) - if !(nix_all_oflags & (OFlag::O_NOFOLLOW | OFlag::O_DIRECTORY)).is_empty() => - { - if let Ok(stat) = fstatat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - AtFlags::AT_SYMLINK_NOFOLLOW, - ) { - if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFLNK) { - return Err(Error::ELOOP); + // Linux returns ENOTDIR instead of ELOOP when using O_NOFOLLOW|O_DIRECTORY + // on a symlink. + Errno::ENOTDIR + if !(nix_all_oflags & (OFlag::NOFOLLOW | OFlag::DIRECTORY)).is_empty() => + { + if let Ok(stat) = unsafe { + fstatat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + AtFlag::SYMLINK_NOFOLLOW, + ) + } { + if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::IFLNK) { + return Err(Error::ELOOP); + } } + return Err(Error::ENOTDIR); } - return Err(Error::ENOTDIR); + // FreeBSD returns EMLINK instead of ELOOP when using O_NOFOLLOW on + // a symlink. + Errno::EMLINK if !(nix_all_oflags & OFlag::NOFOLLOW).is_empty() => { + return Err(Error::ELOOP); + } + errno => return Err(host_impl::errno_from_nix(errno)), } - // FreeBSD returns EMLINK instead of ELOOP when using O_NOFOLLOW on - // a symlink. - Some(Errno::EMLINK) if !(nix_all_oflags & OFlag::O_NOFOLLOW).is_empty() => { - return Err(Error::ELOOP); - } - Some(e) => return Err(host_impl::errno_from_nix(e)), - None => return Err(Error::ENOSYS), + } else { + return Err(e.into()); } } }; @@ -182,34 +199,16 @@ pub(crate) fn path_open( } pub(crate) fn path_readlink(resolved: PathGet, buf: &mut [u8]) -> Result { - use nix::errno::Errno; - let path_cstr = str_to_cstring(resolved.path())?; - - // Linux requires that the buffer size is positive, whereas POSIX does not. - // Use a fake buffer to store the results if the size is zero. - // TODO: instead of using raw libc::readlinkat call here, this should really - // be fixed in `nix` crate - let fakebuf: &mut [u8] = &mut [0]; - let buf_len = buf.len(); - let len = unsafe { - libc::readlinkat( - resolved.dirfd().as_raw_fd(), - path_cstr.as_ptr() as *const libc::c_char, - if buf_len == 0 { - fakebuf.as_mut_ptr() - } else { - buf.as_mut_ptr() - } as *mut libc::c_char, - if buf_len == 0 { fakebuf.len() } else { buf_len }, - ) - }; - - if len < 0 { - Err(host_impl::errno_from_nix(Errno::last())) - } else { - let len = len as usize; - Ok(if len < buf_len { len } else { buf_len }) + use std::cmp::min; + use yanix::file::readlinkat; + let read_link = unsafe { readlinkat(resolved.dirfd().as_raw_fd(), resolved.path()) } + .map_err(Into::into) + .and_then(host_impl::path_from_host)?; + let copy_len = min(read_link.len(), buf.len()); + if copy_len > 0 { + buf[..copy_len].copy_from_slice(&read_link.as_bytes()[..copy_len]); } + Ok(copy_len) } pub(crate) fn fd_filestat_get_impl(file: &std::fs::File) -> Result { @@ -229,8 +228,8 @@ pub(crate) fn fd_filestat_get_impl(file: &std::fs::File) -> Result Result { - use nix::sys::socket::{self, SockType}; use std::os::unix::fs::FileTypeExt; + use yanix::socket::{get_socket_type, SockType}; let ftype = metadata.file_type(); if ftype.is_file() { Ok(FileType::RegularFile) @@ -243,10 +242,7 @@ fn filetype(file: &File, metadata: &Metadata) -> Result { } else if ftype.is_block_device() { Ok(FileType::BlockDevice) } else if ftype.is_socket() { - match socket::getsockopt(file.as_raw_fd(), socket::sockopt::SockType) - .map_err(|err| err.as_errno().unwrap()) - .map_err(host_impl::errno_from_nix)? - { + match unsafe { get_socket_type(file.as_raw_fd())? } { SockType::Datagram => Ok(FileType::SocketDgram), SockType::Stream => Ok(FileType::SocketStream), _ => Ok(FileType::Unknown), @@ -260,17 +256,14 @@ pub(crate) fn path_filestat_get( resolved: PathGet, dirflags: wasi::__wasi_lookupflags_t, ) -> Result { - use nix::fcntl::AtFlags; - use nix::sys::stat::fstatat; - + use yanix::file::{fstatat, AtFlag}; let atflags = match dirflags { - 0 => AtFlags::empty(), - _ => AtFlags::AT_SYMLINK_NOFOLLOW, + 0 => AtFlag::empty(), + _ => AtFlag::SYMLINK_NOFOLLOW, }; - - let filestat = fstatat(resolved.dirfd().as_raw_fd(), resolved.path(), atflags) - .map_err(|err| host_impl::errno_from_nix(err.as_errno().unwrap()))?; - host_impl::filestat_from_nix(filestat) + unsafe { fstatat(resolved.dirfd().as_raw_fd(), resolved.path(), atflags) } + .map_err(Into::into) + .and_then(host_impl::filestat_from_nix) } pub(crate) fn path_filestat_set_times( @@ -321,20 +314,49 @@ pub(crate) fn path_filestat_set_times( } pub(crate) fn path_remove_directory(resolved: PathGet) -> Result<()> { - use nix::errno; - use nix::libc::{unlinkat, AT_REMOVEDIR}; - - let path_cstr = str_to_cstring(resolved.path())?; - - // nix doesn't expose unlinkat() yet - match unsafe { + use yanix::file::{unlinkat, AtFlag}; + unsafe { unlinkat( resolved.dirfd().as_raw_fd(), - path_cstr.as_ptr(), - AT_REMOVEDIR, + resolved.path(), + AtFlag::REMOVEDIR, ) - } { - 0 => Ok(()), - _ => Err(host_impl::errno_from_nix(errno::Errno::last())), } + .map_err(Into::into) +} + +pub(crate) fn fd_readdir<'a>( + os_handle: &'a mut OsHandle, + cookie: wasi::__wasi_dircookie_t, +) -> Result> + 'a> { + use yanix::dir::{DirIter, Entry, EntryExt, SeekLoc}; + + // Get an instance of `Dir`; this is host-specific due to intricasies + // of managing a dir stream between Linux and BSD *nixes + let mut dir = fd_readdir_impl::get_dir_from_os_handle(os_handle)?; + + // Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START, + // new items may not be returned to the caller. + if cookie == wasi::__WASI_DIRCOOKIE_START { + log::trace!(" | fd_readdir: doing rewinddir"); + dir.rewind(); + } else { + log::trace!(" | fd_readdir: doing seekdir to {}", cookie); + let loc = unsafe { SeekLoc::from_raw(cookie as i64) }; + dir.seek(loc); + } + + Ok(DirIter::new(dir).map(|entry| { + let entry: Entry = entry?; + Ok(Dirent { + name: entry + // TODO can we reuse path_from_host for CStr? + .file_name() + .to_str()? + .to_owned(), + ino: entry.ino(), + ftype: entry.file_type().into(), + cookie: entry.seek_loc().to_raw().try_into()?, + }) + })) } diff --git a/crates/wasi-common/src/sys/unix/hostcalls_impl/fs_helpers.rs b/crates/wasi-common/src/sys/unix/hostcalls_impl/fs_helpers.rs index c57cc3476f..785b553d96 100644 --- a/crates/wasi-common/src/sys/unix/hostcalls_impl/fs_helpers.rs +++ b/crates/wasi-common/src/sys/unix/hostcalls_impl/fs_helpers.rs @@ -3,6 +3,7 @@ use crate::sys::host_impl; use crate::{wasi, Result}; use std::fs::File; +use yanix::file::OFlag; pub(crate) fn path_open_rights( rights_base: wasi::__wasi_rights_t, @@ -10,27 +11,25 @@ pub(crate) fn path_open_rights( oflags: wasi::__wasi_oflags_t, fs_flags: wasi::__wasi_fdflags_t, ) -> (wasi::__wasi_rights_t, wasi::__wasi_rights_t) { - use nix::fcntl::OFlag; - // which rights are needed on the dirfd? let mut needed_base = wasi::__WASI_RIGHTS_PATH_OPEN; let mut needed_inheriting = rights_base | rights_inheriting; // convert open flags let oflags = host_impl::nix_from_oflags(oflags); - if oflags.contains(OFlag::O_CREAT) { + if oflags.contains(OFlag::CREAT) { needed_base |= wasi::__WASI_RIGHTS_PATH_CREATE_FILE; } - if oflags.contains(OFlag::O_TRUNC) { + if oflags.contains(OFlag::TRUNC) { needed_base |= wasi::__WASI_RIGHTS_PATH_FILESTAT_SET_SIZE; } // convert file descriptor flags let fdflags = host_impl::nix_from_fdflags(fs_flags); - if fdflags.contains(OFlag::O_DSYNC) { + if fdflags.contains(OFlag::DSYNC) { needed_inheriting |= wasi::__WASI_RIGHTS_FD_DATASYNC; } - if fdflags.intersects(host_impl::O_RSYNC | OFlag::O_SYNC) { + if fdflags.intersects(host_impl::O_RSYNC | OFlag::SYNC) { needed_inheriting |= wasi::__WASI_RIGHTS_FD_SYNC; } @@ -38,31 +37,30 @@ pub(crate) fn path_open_rights( } pub(crate) fn openat(dirfd: &File, path: &str) -> Result { - use nix::fcntl::{self, OFlag}; - use nix::sys::stat::Mode; use std::os::unix::prelude::{AsRawFd, FromRawFd}; + use yanix::file::{openat, Mode}; log::debug!("path_get openat path = {:?}", path); - fcntl::openat( - dirfd.as_raw_fd(), - path, - OFlag::O_RDONLY | OFlag::O_DIRECTORY | OFlag::O_NOFOLLOW, - Mode::empty(), - ) + unsafe { + openat( + dirfd.as_raw_fd(), + path, + OFlag::RDONLY | OFlag::DIRECTORY | OFlag::NOFOLLOW, + Mode::empty(), + ) + } .map(|new_fd| unsafe { File::from_raw_fd(new_fd) }) .map_err(Into::into) } pub(crate) fn readlinkat(dirfd: &File, path: &str) -> Result { - use nix::fcntl; use std::os::unix::prelude::AsRawFd; + use yanix::file::readlinkat; log::debug!("path_get readlinkat path = {:?}", path); - let readlink_buf = &mut [0u8; libc::PATH_MAX as usize + 1]; - - fcntl::readlinkat(dirfd.as_raw_fd(), path, readlink_buf) + unsafe { readlinkat(dirfd.as_raw_fd(), path) } .map_err(Into::into) .and_then(host_impl::path_from_host) } diff --git a/crates/wasi-common/src/sys/unix/hostcalls_impl/misc.rs b/crates/wasi-common/src/sys/unix/hostcalls_impl/misc.rs index 46c29c14e2..077adb3868 100644 --- a/crates/wasi-common/src/sys/unix/hostcalls_impl/misc.rs +++ b/crates/wasi-common/src/sys/unix/hostcalls_impl/misc.rs @@ -3,29 +3,22 @@ use crate::hostcalls_impl::{ClockEventData, FdEventData}; use crate::sys::host_impl; use crate::{wasi, Error, Result}; -use nix::libc::{self, c_int}; -use std::mem::MaybeUninit; +use yanix::clock::{clock_getres, ClockId}; -fn wasi_clock_id_to_unix(clock_id: wasi::__wasi_clockid_t) -> Result { - // convert the supported clocks to the libc types, or return EINVAL +fn wasi_clock_id_to_unix(clock_id: wasi::__wasi_clockid_t) -> Result { + // convert the supported clocks to libc types, or return EINVAL match clock_id { - wasi::__WASI_CLOCKID_REALTIME => Ok(libc::CLOCK_REALTIME), - wasi::__WASI_CLOCKID_MONOTONIC => Ok(libc::CLOCK_MONOTONIC), - wasi::__WASI_CLOCKID_PROCESS_CPUTIME_ID => Ok(libc::CLOCK_PROCESS_CPUTIME_ID), - wasi::__WASI_CLOCKID_THREAD_CPUTIME_ID => Ok(libc::CLOCK_THREAD_CPUTIME_ID), + wasi::__WASI_CLOCKID_REALTIME => Ok(ClockId::Realtime), + wasi::__WASI_CLOCKID_MONOTONIC => Ok(ClockId::Monotonic), + wasi::__WASI_CLOCKID_PROCESS_CPUTIME_ID => Ok(ClockId::ProcessCPUTime), + wasi::__WASI_CLOCKID_THREAD_CPUTIME_ID => Ok(ClockId::ThreadCPUTime), _ => Err(Error::EINVAL), } } pub(crate) fn clock_res_get(clock_id: wasi::__wasi_clockid_t) -> Result { let clock_id = wasi_clock_id_to_unix(clock_id)?; - // no `nix` wrapper for clock_getres, so we do it ourselves - let mut timespec = MaybeUninit::::uninit(); - let res = unsafe { libc::clock_getres(clock_id, timespec.as_mut_ptr()) }; - if res != 0 { - return Err(host_impl::errno_from_nix(nix::errno::Errno::last())); - } - let timespec = unsafe { timespec.assume_init() }; + let timespec = clock_getres(clock_id)?; // convert to nanoseconds, returning EOVERFLOW in case of overflow; // this is freelancing a bit from the spec but seems like it'll @@ -46,13 +39,7 @@ pub(crate) fn clock_res_get(clock_id: wasi::__wasi_clockid_t) -> Result Result { let clock_id = wasi_clock_id_to_unix(clock_id)?; - // no `nix` wrapper for clock_getres, so we do it ourselves - let mut timespec = MaybeUninit::::uninit(); - let res = unsafe { libc::clock_gettime(clock_id, timespec.as_mut_ptr()) }; - if res != 0 { - return Err(host_impl::errno_from_nix(nix::errno::Errno::last())); - } - let timespec = unsafe { timespec.assume_init() }; + let timespec = clock_getres(clock_id)?; // convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit // from the spec but seems like it'll be an unusual situation to hit @@ -67,11 +54,11 @@ pub(crate) fn poll_oneoff( fd_events: Vec, events: &mut Vec, ) -> Result<()> { - use nix::{ - errno::Errno, - poll::{poll, PollFd, PollFlags}, - }; use std::{convert::TryInto, os::unix::prelude::AsRawFd}; + use yanix::{ + poll::{poll, PollFd, PollFlags}, + Errno, + }; if fd_events.is_empty() && timeout.is_none() { return Ok(()); @@ -89,13 +76,13 @@ pub(crate) fn poll_oneoff( // events we filtered before. If we get something else here, the code has a serious bug. _ => unreachable!(), }; - PollFd::new(event.descriptor.as_raw_fd(), flags) + unsafe { PollFd::new(event.descriptor.as_raw_fd(), flags) } }) .collect(); let poll_timeout = timeout.map_or(-1, |timeout| { let delay = timeout.delay / 1_000_000; // poll syscall requires delay to expressed in milliseconds - delay.try_into().unwrap_or(c_int::max_value()) + delay.try_into().unwrap_or(libc::c_int::max_value()) }); log::debug!("poll_oneoff poll_timeout = {:?}", poll_timeout); @@ -107,7 +94,7 @@ pub(crate) fn poll_oneoff( } return Err(host_impl::errno_from_nix(Errno::last())); } - Ok(ready) => break ready as usize, + Ok(ready) => break ready, } }; @@ -119,9 +106,6 @@ pub(crate) fn poll_oneoff( }) } -// define the `fionread()` function, equivalent to `ioctl(fd, FIONREAD, *bytes)` -nix::ioctl_read_bad!(fionread, nix::libc::FIONREAD, c_int); - fn poll_oneoff_handle_timeout_event( timeout: ClockEventData, events: &mut Vec, @@ -140,11 +124,11 @@ fn poll_oneoff_handle_timeout_event( } fn poll_oneoff_handle_fd_event<'a>( - ready_events: impl Iterator, nix::poll::PollFd)>, + ready_events: impl Iterator, yanix::poll::PollFd)>, events: &mut Vec, ) -> Result<()> { - use nix::poll::PollFlags; use std::{convert::TryInto, os::unix::prelude::AsRawFd}; + use yanix::{file::fionread, poll::PollFlags}; for (fd_event, poll_fd) in ready_events { log::debug!("poll_oneoff_handle_fd_event fd_event = {:?}", fd_event); @@ -157,10 +141,11 @@ fn poll_oneoff_handle_fd_event<'a>( log::debug!("poll_oneoff_handle_fd_event revents = {:?}", revents); - let mut nbytes = 0; - if fd_event.r#type == wasi::__WASI_EVENTTYPE_FD_READ { - let _ = unsafe { fionread(fd_event.descriptor.as_raw_fd(), &mut nbytes) }; - } + let nbytes = if fd_event.r#type == wasi::__WASI_EVENTTYPE_FD_READ { + unsafe { fionread(fd_event.descriptor.as_raw_fd())? } + } else { + 0 + }; let output_event = if revents.contains(PollFlags::POLLNVAL) { wasi::__wasi_event_t { diff --git a/crates/wasi-common/src/sys/unix/linux/hostcalls_impl.rs b/crates/wasi-common/src/sys/unix/linux/hostcalls_impl.rs index 2279a15ae2..0ddac0f41a 100644 --- a/crates/wasi-common/src/sys/unix/linux/hostcalls_impl.rs +++ b/crates/wasi-common/src/sys/unix/linux/hostcalls_impl.rs @@ -1,176 +1,60 @@ -use super::super::dir::{Dir, Entry, SeekLoc}; -use crate::hostcalls_impl::{Dirent, PathGet}; -use crate::sys::host_impl; -use crate::sys::unix::str_to_cstring; -use crate::{wasi, Error, Result}; -use log::trace; -use std::convert::TryInto; -use std::fs::File; +use crate::hostcalls_impl::PathGet; +use crate::Result; use std::os::unix::prelude::AsRawFd; pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> { - use nix::errno; - use nix::libc::unlinkat; - - let path_cstr = str_to_cstring(resolved.path())?; - - // nix doesn't expose unlinkat() yet - let res = unsafe { unlinkat(resolved.dirfd().as_raw_fd(), path_cstr.as_ptr(), 0) }; - if res == 0 { - Ok(()) - } else { - Err(host_impl::errno_from_nix(errno::Errno::last())) + use yanix::file::{unlinkat, AtFlag}; + unsafe { + unlinkat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + AtFlag::empty(), + ) } + .map_err(Into::into) } pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> Result<()> { - use nix::{errno::Errno, libc::symlinkat}; - - let old_path_cstr = str_to_cstring(old_path)?; - let new_path_cstr = str_to_cstring(resolved.path())?; + use yanix::file::symlinkat; log::debug!("path_symlink old_path = {:?}", old_path); log::debug!("path_symlink resolved = {:?}", resolved); - let res = unsafe { - symlinkat( - old_path_cstr.as_ptr(), - resolved.dirfd().as_raw_fd(), - new_path_cstr.as_ptr(), - ) - }; - if res != 0 { - Err(host_impl::errno_from_nix(Errno::last())) - } else { - Ok(()) - } + unsafe { symlinkat(old_path, resolved.dirfd().as_raw_fd(), resolved.path()) } + .map_err(Into::into) } pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> { - use nix::libc::renameat; - let old_path_cstr = str_to_cstring(resolved_old.path())?; - let new_path_cstr = str_to_cstring(resolved_new.path())?; - - let res = unsafe { + use yanix::file::renameat; + unsafe { renameat( resolved_old.dirfd().as_raw_fd(), - old_path_cstr.as_ptr(), + resolved_old.path(), resolved_new.dirfd().as_raw_fd(), - new_path_cstr.as_ptr(), + resolved_new.path(), ) - }; - if res != 0 { - Err(host_impl::errno_from_nix(nix::errno::Errno::last())) - } else { - Ok(()) } + .map_err(Into::into) } -pub(crate) fn fd_readdir( - fd: &File, - cookie: wasi::__wasi_dircookie_t, -) -> Result>> { - // We need to duplicate the fd, because `opendir(3)`: - // After a successful call to fdopendir(), fd is used internally by the implementation, - // and should not otherwise be used by the application. - // `opendir(3p)` also says that it's undefined behavior to - // modify the state of the fd in a different way than by accessing DIR*. - // - // Still, rewinddir will be needed because the two file descriptors - // share progress. But we can safely execute closedir now. - let fd = fd.try_clone()?; - let mut dir = Dir::from(fd)?; +pub(crate) mod fd_readdir_impl { + use crate::sys::fdentry_impl::OsHandle; + use crate::Result; + use yanix::dir::Dir; - // Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START, - // new items may not be returned to the caller. - // - // According to `opendir(3p)`: - // If a file is removed from or added to the directory after the most recent call - // to opendir() or rewinddir(), whether a subsequent call to readdir() returns an entry - // for that file is unspecified. - if cookie == wasi::__WASI_DIRCOOKIE_START { - trace!(" | fd_readdir: doing rewinddir"); - dir.rewind(); - } else { - trace!(" | fd_readdir: doing seekdir to {}", cookie); - let loc = unsafe { SeekLoc::from_raw(cookie as i64) }; - dir.seek(loc); - } - - Ok(DirIter(dir).map(|entry| { - let entry: Entry = entry?; - Ok(Dirent { - name: entry // TODO can we reuse path_from_host for CStr? - .file_name() - .to_str()? - .to_owned(), - ino: entry.ino(), - ftype: entry.file_type().into(), - cookie: entry.seek_loc().to_raw().try_into()?, - }) - })) -} - -struct DirIter(Dir); - -impl Iterator for DirIter { - type Item = nix::Result; - - fn next(&mut self) -> Option { - use libc::{dirent64, readdir64_r}; - use nix::errno::Errno; - - unsafe { - // Note: POSIX specifies that portable applications should dynamically allocate a - // buffer with room for a `d_name` field of size `pathconf(..., _PC_NAME_MAX)` plus 1 - // for the NUL byte. It doesn't look like the std library does this; it just uses - // fixed-sized buffers (and libc's dirent seems to be sized so this is appropriate). - // Probably fine here too then. - // - // See `impl Iterator for ReadDir` [1] for more details. - // [1] https://github.com/rust-lang/rust/blob/master/src/libstd/sys/unix/fs.rs - let mut ent = std::mem::MaybeUninit::::uninit(); - let mut result = std::ptr::null_mut(); - if let Err(e) = Errno::result(readdir64_r( - (self.0).0.as_ptr(), - ent.as_mut_ptr(), - &mut result, - )) { - return Some(Err(e)); - } - if result.is_null() { - None - } else { - assert_eq!(result, ent.as_mut_ptr(), "readdir_r specification violated"); - Some(Ok(Entry(ent.assume_init()))) - } - } + pub(crate) fn get_dir_from_os_handle(os_handle: &mut OsHandle) -> Result> { + // We need to duplicate the fd, because `opendir(3)`: + // After a successful call to fdopendir(), fd is used internally by the implementation, + // and should not otherwise be used by the application. + // `opendir(3p)` also says that it's undefined behavior to + // modify the state of the fd in a different way than by accessing DIR*. + // + // Still, rewinddir will be needed because the two file descriptors + // share progress. But we can safely execute closedir now. + let fd = os_handle.try_clone()?; + // TODO This doesn't look very clean. Can we do something about it? + // Boxing is needed here in order to satisfy `yanix`'s trait requirement for the `DirIter` + // where `T: Deref`. + Ok(Box::new(Dir::from(fd)?)) } } - -pub(crate) fn fd_advise( - file: &File, - advice: wasi::__wasi_advice_t, - offset: wasi::__wasi_filesize_t, - len: wasi::__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 { - wasi::__WASI_ADVICE_DONTNEED => PosixFadviseAdvice::POSIX_FADV_DONTNEED, - wasi::__WASI_ADVICE_SEQUENTIAL => PosixFadviseAdvice::POSIX_FADV_SEQUENTIAL, - wasi::__WASI_ADVICE_WILLNEED => PosixFadviseAdvice::POSIX_FADV_WILLNEED, - wasi::__WASI_ADVICE_NOREUSE => PosixFadviseAdvice::POSIX_FADV_NOREUSE, - wasi::__WASI_ADVICE_RANDOM => PosixFadviseAdvice::POSIX_FADV_RANDOM, - wasi::__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/crates/wasi-common/src/sys/unix/linux/mod.rs b/crates/wasi-common/src/sys/unix/linux/mod.rs index 32955b18c7..a9fd0557d5 100644 --- a/crates/wasi-common/src/sys/unix/linux/mod.rs +++ b/crates/wasi-common/src/sys/unix/linux/mod.rs @@ -2,42 +2,16 @@ pub(crate) mod filetime; pub(crate) mod hostcalls_impl; pub(crate) mod oshandle; -pub(crate) mod fdentry_impl { - use crate::{sys::host_impl, Result}; - use std::os::unix::prelude::AsRawFd; - - pub(crate) unsafe fn isatty(fd: &impl AsRawFd) -> Result { - use nix::errno::Errno; - - let res = libc::isatty(fd.as_raw_fd()); - if res == 1 { - // isatty() returns 1 if fd is an open file descriptor referring to a terminal... - Ok(true) - } else { - // ... otherwise 0 is returned, and errno is set to indicate the error. - match Errno::last() { - // While POSIX specifies ENOTTY if the passed - // fd is *not* a tty, on Linux, some implementations - // may return EINVAL instead. - // - // https://linux.die.net/man/3/isatty - Errno::ENOTTY | Errno::EINVAL => Ok(false), - x => Err(host_impl::errno_from_nix(x)), - } - } - } -} - pub(crate) mod host_impl { use crate::{wasi, Result}; - pub(crate) const O_RSYNC: nix::fcntl::OFlag = nix::fcntl::OFlag::O_RSYNC; + pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::RSYNC; - pub(crate) fn stdev_from_nix(dev: nix::libc::dev_t) -> Result { + pub(crate) fn stdev_from_nix(dev: libc::dev_t) -> Result { Ok(wasi::__wasi_device_t::from(dev)) } - pub(crate) fn stino_from_nix(ino: nix::libc::ino_t) -> Result { + pub(crate) fn stino_from_nix(ino: libc::ino_t) -> Result { Ok(wasi::__wasi_device_t::from(ino)) } } diff --git a/crates/wasi-common/src/sys/unix/mod.rs b/crates/wasi-common/src/sys/unix/mod.rs index 52c6e3e4eb..7aa05706ff 100644 --- a/crates/wasi-common/src/sys/unix/mod.rs +++ b/crates/wasi-common/src/sys/unix/mod.rs @@ -2,7 +2,6 @@ pub(crate) mod fdentry_impl; pub(crate) mod host_impl; pub(crate) mod hostcalls_impl; -mod dir; mod filetime; #[cfg(any( @@ -17,8 +16,7 @@ mod bsd; #[cfg(target_os = "linux")] mod linux; -use crate::{Error, Result}; -use std::ffi::CString; +use crate::Result; use std::fs::{File, OpenOptions}; use std::path::Path; @@ -30,10 +28,6 @@ pub(crate) fn dev_null() -> Result { .map_err(Into::into) } -pub(crate) fn str_to_cstring(s: &str) -> Result { - CString::new(s.as_bytes()).map_err(|_| Error::EILSEQ) -} - pub fn preopen_dir>(path: P) -> Result { File::open(path).map_err(Into::into) } diff --git a/crates/wasi-common/src/sys/windows/hostcalls_impl/fs.rs b/crates/wasi-common/src/sys/windows/hostcalls_impl/fs.rs index 4c8cfb269f..4f5d3c8783 100644 --- a/crates/wasi-common/src/sys/windows/hostcalls_impl/fs.rs +++ b/crates/wasi-common/src/sys/windows/hostcalls_impl/fs.rs @@ -4,7 +4,8 @@ use super::fs_helpers::*; use crate::ctx::WasiCtx; use crate::fdentry::FdEntry; use crate::helpers::systemtime_to_timestamp; -use crate::hostcalls_impl::{fd_filestat_set_times_impl, Dirent, FileType, PathGet}; +use crate::host::{Dirent, FileType}; +use crate::hostcalls_impl::{fd_filestat_set_times_impl, PathGet}; use crate::sys::fdentry_impl::determine_type_rights; use crate::sys::host_impl::{self, path_from_host}; use crate::sys::hostcalls_impl::fs_helpers::PathGetExt; diff --git a/crates/wasi-common/yanix/Cargo.toml b/crates/wasi-common/yanix/Cargo.toml new file mode 100644 index 0000000000..61c52488cb --- /dev/null +++ b/crates/wasi-common/yanix/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "yanix" +version = "0.1.0" +authors = ["The Wasmtime Project Developers"] +description = "Yet Another Nix crate: a Unix API helper library" +license = "Apache-2.0 WITH LLVM-exception" +repository = "https://github.com/bytecodealliance/wasmtime" +edition = "2018" + +[dependencies] +log = "0.4" +libc = { version = "0.2", features = ["extra_traits"] } +thiserror = "1.0" +bitflags = "1.2" +cfg-if = "0.1.9" + +[badges] +maintenance = { status = "actively-developed" } diff --git a/crates/wasi-common/yanix/LICENSE b/crates/wasi-common/yanix/LICENSE new file mode 100644 index 0000000000..f9d81955f4 --- /dev/null +++ b/crates/wasi-common/yanix/LICENSE @@ -0,0 +1,220 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + diff --git a/crates/wasi-common/yanix/src/clock.rs b/crates/wasi-common/yanix/src/clock.rs new file mode 100644 index 0000000000..530272cc19 --- /dev/null +++ b/crates/wasi-common/yanix/src/clock.rs @@ -0,0 +1,29 @@ +use crate::{Errno, Result}; +use std::mem::MaybeUninit; + +#[derive(Debug, Copy, Clone)] +pub enum ClockId { + Realtime, + Monotonic, + ProcessCPUTime, + ThreadCPUTime, +} + +impl ClockId { + pub fn as_raw(&self) -> libc::clockid_t { + match self { + Self::Realtime => libc::CLOCK_REALTIME, + Self::Monotonic => libc::CLOCK_MONOTONIC, + Self::ProcessCPUTime => libc::CLOCK_PROCESS_CPUTIME_ID, + Self::ThreadCPUTime => libc::CLOCK_THREAD_CPUTIME_ID, + } + } +} + +pub fn clock_getres(clock_id: ClockId) -> Result { + let mut timespec = MaybeUninit::::uninit(); + Errno::from_success_code(unsafe { + libc::clock_getres(clock_id.as_raw(), timespec.as_mut_ptr()) + })?; + Ok(unsafe { timespec.assume_init() }) +} diff --git a/crates/wasi-common/yanix/src/dir.rs b/crates/wasi-common/yanix/src/dir.rs new file mode 100644 index 0000000000..e395491f4e --- /dev/null +++ b/crates/wasi-common/yanix/src/dir.rs @@ -0,0 +1,162 @@ +use crate::{ + sys::dir::{iter_impl, EntryImpl}, + Errno, Result, +}; +use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; +use std::{ffi::CStr, ops::Deref, ptr}; + +pub use crate::sys::EntryExt; + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct Dir(pub(crate) ptr::NonNull); + +impl Dir { + /// Takes the ownership of the passed-in descriptor-based object, + /// and creates a new instance of `Dir`. + #[inline] + pub fn from(fd: F) -> Result { + let fd = fd.into_raw_fd(); + unsafe { Self::from_fd(fd) } + } + + unsafe fn from_fd(fd: RawFd) -> Result { + let d = libc::fdopendir(fd); + if let Some(d) = ptr::NonNull::new(d) { + Ok(Self(d)) + } else { + let e = Errno::last(); + libc::close(fd); + Err(e.into()) + } + } + + /// Set the position of the directory stream, see `seekdir(3)`. + #[cfg(not(target_os = "android"))] + pub fn seek(&mut self, loc: SeekLoc) { + unsafe { libc::seekdir(self.0.as_ptr(), loc.0) } + } + + /// Reset directory stream, see `rewinddir(3)`. + pub fn rewind(&mut self) { + unsafe { libc::rewinddir(self.0.as_ptr()) } + } + + /// Get the current position in the directory stream. + /// + /// If this location is given to `Dir::seek`, the entries up to the previously returned + /// will be omitted and the iteration will start from the currently pending directory entry. + #[cfg(not(target_os = "android"))] + #[allow(dead_code)] + pub fn tell(&self) -> SeekLoc { + let loc = unsafe { libc::telldir(self.0.as_ptr()) }; + SeekLoc(loc) + } +} + +unsafe impl Send for Dir {} + +impl AsRawFd for Dir { + fn as_raw_fd(&self) -> RawFd { + unsafe { libc::dirfd(self.0.as_ptr()) } + } +} + +impl Drop for Dir { + fn drop(&mut self) { + unsafe { libc::closedir(self.0.as_ptr()) }; + } +} + +#[derive(Debug, Copy, Clone)] +pub struct Entry(pub(crate) EntryImpl); + +impl Entry { + /// Returns the file name of this directory entry. + pub fn file_name(&self) -> &CStr { + unsafe { CStr::from_ptr(self.0.d_name.as_ptr()) } + } + + /// Returns the type of this directory entry. + pub fn file_type(&self) -> FileType { + unsafe { FileType::from_raw(self.0.d_type) } + } +} + +#[cfg(not(target_os = "android"))] +#[derive(Clone, Copy, Debug)] +pub struct SeekLoc(libc::c_long); + +#[cfg(not(target_os = "android"))] +impl SeekLoc { + pub unsafe fn from_raw(loc: i64) -> Self { + Self(loc.into()) + } + + pub fn to_raw(&self) -> i64 { + self.0.into() + } +} + +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +pub enum FileType { + CharacterDevice = libc::DT_CHR, + Directory = libc::DT_DIR, + BlockDevice = libc::DT_BLK, + RegularFile = libc::DT_REG, + Symlink = libc::DT_LNK, + Fifo = libc::DT_FIFO, + Socket = libc::DT_SOCK, + Unknown = libc::DT_UNKNOWN, +} + +impl FileType { + pub unsafe fn from_raw(file_type: u8) -> Self { + match file_type { + libc::DT_CHR => Self::CharacterDevice, + libc::DT_DIR => Self::Directory, + libc::DT_BLK => Self::BlockDevice, + libc::DT_REG => Self::RegularFile, + libc::DT_LNK => Self::Symlink, + libc::DT_SOCK => Self::Socket, + libc::DT_FIFO => Self::Fifo, + /* libc::DT_UNKNOWN */ _ => Self::Unknown, + } + } + + pub fn to_raw(&self) -> u8 { + match self { + Self::CharacterDevice => libc::DT_CHR, + Self::Directory => libc::DT_DIR, + Self::BlockDevice => libc::DT_BLK, + Self::RegularFile => libc::DT_REG, + Self::Symlink => libc::DT_LNK, + Self::Socket => libc::DT_SOCK, + Self::Fifo => libc::DT_FIFO, + Self::Unknown => libc::DT_UNKNOWN, + } + } +} + +#[derive(Debug)] +pub struct DirIter>(T); + +impl DirIter +where + T: Deref, +{ + pub fn new(dir: T) -> Self { + Self(dir) + } +} + +impl Iterator for DirIter +where + T: Deref, +{ + type Item = Result; + + fn next(&mut self) -> Option { + unsafe { iter_impl(&self.0).map(|x| x.map(Entry)) } + } +} diff --git a/crates/wasi-common/yanix/src/errno.rs b/crates/wasi-common/yanix/src/errno.rs new file mode 100644 index 0000000000..8f8be64dc8 --- /dev/null +++ b/crates/wasi-common/yanix/src/errno.rs @@ -0,0 +1,227 @@ +//! Errno-specific for different Unix platforms +use crate::Result; +use std::{fmt, io}; +use thiserror::Error; + +#[derive(Debug, Copy, Clone, Error, PartialEq, Eq, Hash)] +#[repr(i32)] +pub enum Errno { + EPERM = libc::EPERM, + ENOENT = libc::ENOENT, + ESRCH = libc::ESRCH, + EINTR = libc::EINTR, + EIO = libc::EIO, + ENXIO = libc::ENXIO, + E2BIG = libc::E2BIG, + ENOEXEC = libc::ENOEXEC, + EBADF = libc::EBADF, + ECHILD = libc::ECHILD, + EAGAIN = libc::EAGAIN, + ENOMEM = libc::ENOMEM, + EACCES = libc::EACCES, + EFAULT = libc::EFAULT, + EBUSY = libc::EBUSY, + EEXIST = libc::EEXIST, + EXDEV = libc::EXDEV, + ENODEV = libc::ENODEV, + ENOTDIR = libc::ENOTDIR, + EISDIR = libc::EISDIR, + EINVAL = libc::EINVAL, + ENFILE = libc::ENFILE, + EMFILE = libc::EMFILE, + ENOTTY = libc::ENOTTY, + ETXTBSY = libc::ETXTBSY, + EFBIG = libc::EFBIG, + ENOSPC = libc::ENOSPC, + ESPIPE = libc::ESPIPE, + EROFS = libc::EROFS, + EMLINK = libc::EMLINK, + EPIPE = libc::EPIPE, + EDOM = libc::EDOM, + ERANGE = libc::ERANGE, + EDEADLK = libc::EDEADLK, + ENAMETOOLONG = libc::ENAMETOOLONG, + ENOLCK = libc::ENOLCK, + ENOSYS = libc::ENOSYS, + ENOTEMPTY = libc::ENOTEMPTY, + ELOOP = libc::ELOOP, + ENOMSG = libc::ENOMSG, + EIDRM = libc::EIDRM, + ENOLINK = libc::ENOLINK, + EPROTO = libc::EPROTO, + EMULTIHOP = libc::EMULTIHOP, + EBADMSG = libc::EBADMSG, + EOVERFLOW = libc::EOVERFLOW, + EILSEQ = libc::EILSEQ, + ENOTSOCK = libc::ENOTSOCK, + EDESTADDRREQ = libc::EDESTADDRREQ, + EMSGSIZE = libc::EMSGSIZE, + EPROTOTYPE = libc::EPROTOTYPE, + ENOPROTOOPT = libc::ENOPROTOOPT, + EPROTONOSUPPORT = libc::EPROTONOSUPPORT, + EAFNOSUPPORT = libc::EAFNOSUPPORT, + EADDRINUSE = libc::EADDRINUSE, + EADDRNOTAVAIL = libc::EADDRNOTAVAIL, + ENETDOWN = libc::ENETDOWN, + ENETUNREACH = libc::ENETUNREACH, + ENETRESET = libc::ENETRESET, + ECONNABORTED = libc::ECONNABORTED, + ECONNRESET = libc::ECONNRESET, + ENOBUFS = libc::ENOBUFS, + EISCONN = libc::EISCONN, + ENOTCONN = libc::ENOTCONN, + ETIMEDOUT = libc::ETIMEDOUT, + ECONNREFUSED = libc::ECONNREFUSED, + EHOSTUNREACH = libc::EHOSTUNREACH, + EALREADY = libc::EALREADY, + EINPROGRESS = libc::EINPROGRESS, + ESTALE = libc::ESTALE, + EDQUOT = libc::EDQUOT, + ECANCELED = libc::ECANCELED, + EOWNERDEAD = libc::EOWNERDEAD, + ENOTRECOVERABLE = libc::ENOTRECOVERABLE, +} + +impl Errno { + pub fn from_i32(err: i32) -> Self { + match err { + libc::EPERM => Self::EPERM, + libc::ENOENT => Self::ENOENT, + libc::ESRCH => Self::ESRCH, + libc::EINTR => Self::EINTR, + libc::EIO => Self::EIO, + libc::ENXIO => Self::ENXIO, + libc::E2BIG => Self::E2BIG, + libc::ENOEXEC => Self::ENOEXEC, + libc::EBADF => Self::EBADF, + libc::ECHILD => Self::ECHILD, + libc::EAGAIN => Self::EAGAIN, + libc::ENOMEM => Self::ENOMEM, + libc::EACCES => Self::EACCES, + libc::EFAULT => Self::EFAULT, + libc::EBUSY => Self::EBUSY, + libc::EEXIST => Self::EEXIST, + libc::EXDEV => Self::EXDEV, + libc::ENODEV => Self::ENODEV, + libc::ENOTDIR => Self::ENOTDIR, + libc::EISDIR => Self::EISDIR, + libc::EINVAL => Self::EINVAL, + libc::ENFILE => Self::ENFILE, + libc::EMFILE => Self::EMFILE, + libc::ENOTTY => Self::ENOTTY, + libc::ETXTBSY => Self::ETXTBSY, + libc::EFBIG => Self::EFBIG, + libc::ENOSPC => Self::ENOSPC, + libc::ESPIPE => Self::ESPIPE, + libc::EROFS => Self::EROFS, + libc::EMLINK => Self::EMLINK, + libc::EPIPE => Self::EPIPE, + libc::EDOM => Self::EDOM, + libc::ERANGE => Self::ERANGE, + libc::EDEADLK => Self::EDEADLK, + libc::ENAMETOOLONG => Self::ENAMETOOLONG, + libc::ENOLCK => Self::ENOLCK, + libc::ENOSYS => Self::ENOSYS, + libc::ENOTEMPTY => Self::ENOTEMPTY, + libc::ELOOP => Self::ELOOP, + libc::ENOMSG => Self::ENOMSG, + libc::EIDRM => Self::EIDRM, + libc::ENOLINK => Self::ENOLINK, + libc::EPROTO => Self::EPROTO, + libc::EMULTIHOP => Self::EMULTIHOP, + libc::EBADMSG => Self::EBADMSG, + libc::EOVERFLOW => Self::EOVERFLOW, + libc::EILSEQ => Self::EILSEQ, + libc::ENOTSOCK => Self::ENOTSOCK, + libc::EDESTADDRREQ => Self::EDESTADDRREQ, + libc::EMSGSIZE => Self::EMSGSIZE, + libc::EPROTOTYPE => Self::EPROTOTYPE, + libc::ENOPROTOOPT => Self::ENOPROTOOPT, + libc::EPROTONOSUPPORT => Self::EPROTONOSUPPORT, + libc::EAFNOSUPPORT => Self::EAFNOSUPPORT, + libc::EADDRINUSE => Self::EADDRINUSE, + libc::EADDRNOTAVAIL => Self::EADDRNOTAVAIL, + libc::ENETDOWN => Self::ENETDOWN, + libc::ENETUNREACH => Self::ENETUNREACH, + libc::ENETRESET => Self::ENETRESET, + libc::ECONNABORTED => Self::ECONNABORTED, + libc::ECONNRESET => Self::ECONNRESET, + libc::ENOBUFS => Self::ENOBUFS, + libc::EISCONN => Self::EISCONN, + libc::ENOTCONN => Self::ENOTCONN, + libc::ETIMEDOUT => Self::ETIMEDOUT, + libc::ECONNREFUSED => Self::ECONNREFUSED, + libc::EHOSTUNREACH => Self::EHOSTUNREACH, + libc::EALREADY => Self::EALREADY, + libc::EINPROGRESS => Self::EINPROGRESS, + libc::ESTALE => Self::ESTALE, + libc::EDQUOT => Self::EDQUOT, + libc::ECANCELED => Self::ECANCELED, + libc::EOWNERDEAD => Self::EOWNERDEAD, + libc::ENOTRECOVERABLE => Self::ENOTRECOVERABLE, + other => { + log::warn!("Unknown errno: {}", other); + Self::ENOSYS + } + } + } + + pub fn last() -> Self { + let errno = io::Error::last_os_error() + .raw_os_error() + .unwrap_or(libc::ENOSYS); + Self::from_i32(errno) + } + + pub fn from_success_code(t: T) -> Result<()> { + if t.is_zero() { + Ok(()) + } else { + Err(Self::last().into()) + } + } + + pub fn from_result(t: T) -> Result { + if t.is_minus_one() { + Err(Self::last().into()) + } else { + Ok(t) + } + } +} + +impl fmt::Display for Errno { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Errno code: {}", self) + } +} + +#[doc(hidden)] +pub trait IsZero { + fn is_zero(&self) -> bool; +} + +macro_rules! impl_is_zero { + ($($t:ident)*) => ($(impl IsZero for $t { + fn is_zero(&self) -> bool { + *self == 0 + } + })*) +} + +impl_is_zero! { i32 i64 isize } + +#[doc(hidden)] +pub trait IsMinusOne { + fn is_minus_one(&self) -> bool; +} + +macro_rules! impl_is_minus_one { + ($($t:ident)*) => ($(impl IsMinusOne for $t { + fn is_minus_one(&self) -> bool { + *self == -1 + } + })*) +} + +impl_is_minus_one! { i32 i64 isize } diff --git a/crates/wasi-common/yanix/src/fcntl.rs b/crates/wasi-common/yanix/src/fcntl.rs new file mode 100644 index 0000000000..87d9827af3 --- /dev/null +++ b/crates/wasi-common/yanix/src/fcntl.rs @@ -0,0 +1,33 @@ +use crate::{ + file::{FdFlag, OFlag}, + Errno, Result, +}; +use std::os::unix::prelude::*; + +pub unsafe fn dup_fd(fd: RawFd, close_on_exec: bool) -> Result { + // Both fcntl commands expect a RawFd arg which will specify + // the minimum duplicated RawFd number. In our case, I don't + // think we have to worry about this that much, so passing in + // the RawFd descriptor we want duplicated + Errno::from_result(if close_on_exec { + libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, fd) + } else { + libc::fcntl(fd, libc::F_DUPFD, fd) + }) +} + +pub unsafe fn get_fd_flags(fd: RawFd) -> Result { + Errno::from_result(libc::fcntl(fd, libc::F_GETFD)).map(FdFlag::from_bits_truncate) +} + +pub unsafe fn set_fd_flags(fd: RawFd, flags: FdFlag) -> Result<()> { + Errno::from_success_code(libc::fcntl(fd, libc::F_SETFD, flags.bits())) +} + +pub unsafe fn get_status_flags(fd: RawFd) -> Result { + Errno::from_result(libc::fcntl(fd, libc::F_GETFL)).map(OFlag::from_bits_truncate) +} + +pub unsafe fn set_status_flags(fd: RawFd, flags: OFlag) -> Result<()> { + Errno::from_success_code(libc::fcntl(fd, libc::F_SETFL, flags.bits())) +} diff --git a/crates/wasi-common/yanix/src/file.rs b/crates/wasi-common/yanix/src/file.rs new file mode 100644 index 0000000000..36f12141a1 --- /dev/null +++ b/crates/wasi-common/yanix/src/file.rs @@ -0,0 +1,198 @@ +use crate::{Errno, Result}; +use bitflags::bitflags; +use std::{ + convert::TryInto, + ffi::{CString, OsStr, OsString}, + os::unix::prelude::*, +}; + +pub use crate::sys::file::*; + +bitflags! { + pub struct FdFlag: libc::c_int { + const CLOEXEC = libc::FD_CLOEXEC; + } +} + +bitflags! { + pub struct AtFlag: libc::c_int { + const REMOVEDIR = libc::AT_REMOVEDIR; + const SYMLINK_FOLLOW = libc::AT_SYMLINK_FOLLOW; + const SYMLINK_NOFOLLOW = libc::AT_SYMLINK_NOFOLLOW; + } +} + +bitflags! { + pub struct Mode: libc::mode_t { + const IRWXU = libc::S_IRWXU; + const IRUSR = libc::S_IRUSR; + const IWUSR = libc::S_IWUSR; + const IXUSR = libc::S_IXUSR; + const IRWXG = libc::S_IRWXG; + const IRGRP = libc::S_IRGRP; + const IWGRP = libc::S_IWGRP; + const IXGRP = libc::S_IXGRP; + const IRWXO = libc::S_IRWXO; + const IROTH = libc::S_IROTH; + const IWOTH = libc::S_IWOTH; + const IXOTH = libc::S_IXOTH; + const ISUID = libc::S_ISUID as libc::mode_t; + const ISGID = libc::S_ISGID as libc::mode_t; + const ISVTX = libc::S_ISVTX as libc::mode_t; + } +} + +bitflags! { + pub struct OFlag: libc::c_int { + const ACCMODE = libc::O_ACCMODE; + const APPEND = libc::O_APPEND; + const CREAT = libc::O_CREAT; + const DIRECTORY = libc::O_DIRECTORY; + #[cfg(any(target_os = "android", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "emscripten"))] + const DSYNC = libc::O_DSYNC; + const EXCL = libc::O_EXCL; + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + all(target_os = "linux", not(target_env = "musl")), + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + const FSYNC = libc::O_FSYNC; + const NOFOLLOW = libc::O_NOFOLLOW; + const NONBLOCK = libc::O_NONBLOCK; + const RDONLY = libc::O_RDONLY; + const WRONLY = libc::O_WRONLY; + const RDWR = libc::O_RDWR; + #[cfg(any(target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + target_os = "emscripten"))] + const RSYNC = libc::O_RSYNC; + const SYNC = libc::O_SYNC; + const TRUNC = libc::O_TRUNC; + } +} + +bitflags! { + pub struct SFlag: libc::mode_t { + const IFIFO = libc::S_IFIFO; + const IFCHR = libc::S_IFCHR; + const IFDIR = libc::S_IFDIR; + const IFBLK = libc::S_IFBLK; + const IFREG = libc::S_IFREG; + const IFLNK = libc::S_IFLNK; + const IFSOCK = libc::S_IFSOCK; + const IFMT = libc::S_IFMT; + } +} + +pub unsafe fn openat>( + dirfd: RawFd, + path: P, + oflag: OFlag, + mode: Mode, +) -> Result { + let path = CString::new(path.as_ref().as_bytes())?; + Errno::from_result(libc::openat( + dirfd, + path.as_ptr(), + oflag.bits(), + libc::c_uint::from(mode.bits()), + )) +} + +pub unsafe fn readlinkat>(dirfd: RawFd, path: P) -> Result { + let path = CString::new(path.as_ref().as_bytes())?; + let buffer = &mut [0u8; libc::PATH_MAX as usize + 1]; + Errno::from_result(libc::readlinkat( + dirfd, + path.as_ptr(), + buffer.as_mut_ptr() as *mut _, + buffer.len(), + )) + .and_then(|nread| { + let link = OsStr::from_bytes(&buffer[0..nread.try_into()?]); + Ok(link.into()) + }) +} + +pub unsafe fn mkdirat>(dirfd: RawFd, path: P, mode: Mode) -> Result<()> { + let path = CString::new(path.as_ref().as_bytes())?; + Errno::from_success_code(libc::mkdirat(dirfd, path.as_ptr(), mode.bits())) +} + +pub unsafe fn linkat>( + old_dirfd: RawFd, + old_path: P, + new_dirfd: RawFd, + new_path: P, + flags: AtFlag, +) -> Result<()> { + let old_path = CString::new(old_path.as_ref().as_bytes())?; + let new_path = CString::new(new_path.as_ref().as_bytes())?; + Errno::from_success_code(libc::linkat( + old_dirfd, + old_path.as_ptr(), + new_dirfd, + new_path.as_ptr(), + flags.bits(), + )) +} + +pub unsafe fn unlinkat>(dirfd: RawFd, path: P, flags: AtFlag) -> Result<()> { + let path = CString::new(path.as_ref().as_bytes())?; + Errno::from_success_code(libc::unlinkat(dirfd, path.as_ptr(), flags.bits())) +} + +pub unsafe fn renameat>( + old_dirfd: RawFd, + old_path: P, + new_dirfd: RawFd, + new_path: P, +) -> Result<()> { + let old_path = CString::new(old_path.as_ref().as_bytes())?; + let new_path = CString::new(new_path.as_ref().as_bytes())?; + Errno::from_success_code(libc::renameat( + old_dirfd, + old_path.as_ptr(), + new_dirfd, + new_path.as_ptr(), + )) +} + +pub unsafe fn symlinkat>(old_path: P, new_dirfd: RawFd, new_path: P) -> Result<()> { + let old_path = CString::new(old_path.as_ref().as_bytes())?; + let new_path = CString::new(new_path.as_ref().as_bytes())?; + Errno::from_success_code(libc::symlinkat( + old_path.as_ptr(), + new_dirfd, + new_path.as_ptr(), + )) +} + +pub unsafe fn fstatat>(dirfd: RawFd, path: P, flags: AtFlag) -> Result { + use std::mem::MaybeUninit; + let path = CString::new(path.as_ref().as_bytes())?; + let mut filestat = MaybeUninit::::uninit(); + Errno::from_result(libc::fstatat( + dirfd, + path.as_ptr(), + filestat.as_mut_ptr(), + flags.bits(), + ))?; + Ok(filestat.assume_init()) +} + +/// `fionread()` function, equivalent to `ioctl(fd, FIONREAD, *bytes)`. +pub unsafe fn fionread(fd: RawFd) -> Result { + let mut nread: libc::c_int = 0; + Errno::from_result(libc::ioctl(fd, libc::FIONREAD, &mut nread as *mut _))?; + Ok(nread.try_into()?) +} diff --git a/crates/wasi-common/yanix/src/lib.rs b/crates/wasi-common/yanix/src/lib.rs new file mode 100644 index 0000000000..e97e4e0b0f --- /dev/null +++ b/crates/wasi-common/yanix/src/lib.rs @@ -0,0 +1,40 @@ +//! `yanix` stands for Yet Another Nix crate, and, well, it is simply +//! a yet another crate in the spirit of the [nix] crate. As such, +//! this crate is inspired by the original `nix` crate, however, +//! it takes a different approach, using lower-level interfaces with +//! less abstraction, so that it fits better with its main use case +//! which is our WASI implementation, [wasi-common]. +//! +//! [nix]: https://github.com/nix-rust/nix +//! [wasi-common]: https://github.com/bytecodealliance/wasmtime/tree/master/crates/wasi-common +#![cfg(unix)] + +pub mod clock; +pub mod dir; +pub mod fcntl; +pub mod file; +pub mod poll; +pub mod socket; + +mod errno; +mod sys; + +pub mod fadvise { + pub use super::sys::fadvise::*; +} + +pub use errno::Errno; +use std::{ffi, num}; +use thiserror::Error; + +pub type Result = std::result::Result; + +#[derive(Debug, Error)] +pub enum YanixError { + #[error("raw os error {0}")] + Errno(#[from] Errno), + #[error("a nul byte was not found in the expected position")] + NulError(#[from] ffi::NulError), + #[error("integral type conversion failed")] + TryFromIntError(#[from] num::TryFromIntError), +} diff --git a/crates/wasi-common/yanix/src/poll.rs b/crates/wasi-common/yanix/src/poll.rs new file mode 100644 index 0000000000..58cb4426cf --- /dev/null +++ b/crates/wasi-common/yanix/src/poll.rs @@ -0,0 +1,47 @@ +use crate::{Errno, Result}; +use bitflags::bitflags; +use std::{convert::TryInto, os::unix::prelude::*}; + +bitflags! { + pub struct PollFlags: libc::c_short { + const POLLIN = libc::POLLIN; + const POLLPRI = libc::POLLPRI; + const POLLOUT = libc::POLLOUT; + const POLLRDNORM = libc::POLLRDNORM; + const POLLWRNORM = libc::POLLWRNORM; + const POLLRDBAND = libc::POLLRDBAND; + const POLLWRBAND = libc::POLLWRBAND; + const POLLERR = libc::POLLERR; + const POLLHUP = libc::POLLHUP; + const POLLNVAL = libc::POLLNVAL; + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(C)] +pub struct PollFd(libc::pollfd); + +impl PollFd { + pub unsafe fn new(fd: RawFd, events: PollFlags) -> Self { + Self(libc::pollfd { + fd, + events: events.bits(), + revents: PollFlags::empty().bits(), + }) + } + + pub fn revents(self) -> Option { + PollFlags::from_bits(self.0.revents) + } +} + +pub fn poll(fds: &mut [PollFd], timeout: i32) -> Result { + Errno::from_result(unsafe { + libc::poll( + fds.as_mut_ptr() as *mut libc::pollfd, + fds.len() as libc::nfds_t, + timeout, + ) + }) + .and_then(|nready| nready.try_into().map_err(Into::into)) +} diff --git a/crates/wasi-common/yanix/src/socket.rs b/crates/wasi-common/yanix/src/socket.rs new file mode 100644 index 0000000000..f0f1364e13 --- /dev/null +++ b/crates/wasi-common/yanix/src/socket.rs @@ -0,0 +1,31 @@ +use crate::{Errno, Result}; +use std::os::unix::prelude::*; + +#[derive(Debug, Clone, Copy)] +#[repr(i32)] +pub enum SockType { + Stream = libc::SOCK_STREAM, + Datagram = libc::SOCK_DGRAM, + SeqPacket = libc::SOCK_SEQPACKET, + Raw = libc::SOCK_RAW, + Rdm = libc::SOCK_RDM, +} + +pub unsafe fn get_socket_type(fd: RawFd) -> Result { + use std::mem::{self, MaybeUninit}; + let mut buffer = MaybeUninit::::zeroed().assume_init(); + let mut out_len = mem::size_of::() as libc::socklen_t; + Errno::from_success_code(libc::getsockopt( + fd, + libc::SOL_SOCKET, + libc::SO_TYPE, + &mut buffer as *mut SockType as *mut _, + &mut out_len, + ))?; + assert_eq!( + out_len as usize, + mem::size_of::(), + "invalid SockType value" + ); + Ok(buffer) +} diff --git a/crates/wasi-common/yanix/src/sys/bsd/dir.rs b/crates/wasi-common/yanix/src/sys/bsd/dir.rs new file mode 100644 index 0000000000..fee320e3e5 --- /dev/null +++ b/crates/wasi-common/yanix/src/sys/bsd/dir.rs @@ -0,0 +1,52 @@ +use crate::{ + dir::{Dir, Entry, EntryExt, SeekLoc}, + Errno, Result, +}; +use std::ops::Deref; + +#[derive(Copy, Clone, Debug)] +pub(crate) struct EntryImpl { + dirent: libc::dirent, + loc: SeekLoc, +} + +impl Deref for EntryImpl { + type Target = libc::dirent; + + fn deref(&self) -> &Self::Target { + &self.dirent + } +} + +pub(crate) unsafe fn iter_impl(dir: &Dir) -> Option> { + let errno = Errno::last(); + let dirent = libc::readdir(dir.0.as_ptr()); + if dirent.is_null() { + if errno != Errno::last() { + // TODO This should be verified on different BSD-flavours. + // + // According to 4.3BSD/POSIX.1-2001 man pages, there was an error + // if the errno value has changed at some point during the sequence + // of readdir calls. + Some(Err(Errno::last().into())) + } else { + // Not an error. We've simply reached the end of the stream. + None + } + } else { + Some(Ok(EntryImpl { + dirent: *dirent, + loc: dir.tell(), + })) + } +} + +impl EntryExt for Entry { + fn ino(&self) -> u64 { + self.0.d_ino.into() + } + + fn seek_loc(&self) -> SeekLoc { + self.0.loc + } +} diff --git a/crates/wasi-common/yanix/src/sys/bsd/fadvise.rs b/crates/wasi-common/yanix/src/sys/bsd/fadvise.rs new file mode 100644 index 0000000000..74150f3f27 --- /dev/null +++ b/crates/wasi-common/yanix/src/sys/bsd/fadvise.rs @@ -0,0 +1,51 @@ +use crate::{Errno, Result}; +use std::{convert::TryInto, os::unix::prelude::*}; + +#[derive(Debug, Copy, Clone)] +#[repr(i32)] +pub enum PosixFadviseAdvice { + Normal, + Sequential, + Random, + NoReuse, + WillNeed, + DontNeed, +} + +// There's no posix_fadvise on macOS but we can use fcntl with F_RDADVISE +// command instead to achieve the same +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub unsafe fn posix_fadvise( + fd: RawFd, + offset: libc::off_t, + len: libc::off_t, + _advice: PosixFadviseAdvice, +) -> Result<()> { + // 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, + ra_count: len.try_into()?, + }; + Errno::from_success_code(libc::fcntl(fd, libc::F_RDADVISE, &advisory)) +} + +// TODO +// On non-macOS BSD's we leave it as no-op for now +#[cfg(not(any(target_os = "macos", target_os = "ios")))] +pub unsafe fn posix_fadvise( + _fd: RawFd, + _offset: libc::off_t, + _len: libc::off_t, + _advice: PosixFadviseAdvice, +) -> Result<()> { + Ok(()) +} diff --git a/crates/wasi-common/yanix/src/sys/bsd/file.rs b/crates/wasi-common/yanix/src/sys/bsd/file.rs new file mode 100644 index 0000000000..b367154812 --- /dev/null +++ b/crates/wasi-common/yanix/src/sys/bsd/file.rs @@ -0,0 +1,18 @@ +use crate::{Errno, Result}; +use std::os::unix::prelude::*; + +pub unsafe fn isatty(fd: RawFd) -> Result { + let res = libc::isatty(fd); + if res == 1 { + // isatty() returns 1 if fd is an open file descriptor referring to a terminal... + Ok(true) + } else { + // ... otherwise 0 is returned, and errno is set to indicate the error. + let errno = Errno::last(); + if errno == Errno::ENOTTY { + Ok(false) + } else { + Err(errno.into()) + } + } +} diff --git a/crates/wasi-common/yanix/src/sys/bsd/mod.rs b/crates/wasi-common/yanix/src/sys/bsd/mod.rs new file mode 100644 index 0000000000..3e6611f7b9 --- /dev/null +++ b/crates/wasi-common/yanix/src/sys/bsd/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod dir; +pub(crate) mod fadvise; +pub(crate) mod file; diff --git a/crates/wasi-common/yanix/src/sys/linux/dir.rs b/crates/wasi-common/yanix/src/sys/linux/dir.rs new file mode 100644 index 0000000000..72349e3cf4 --- /dev/null +++ b/crates/wasi-common/yanix/src/sys/linux/dir.rs @@ -0,0 +1,46 @@ +use crate::{ + dir::{Dir, Entry, EntryExt, SeekLoc}, + Errno, Result, +}; +use std::ops::Deref; + +#[derive(Copy, Clone, Debug)] +pub(crate) struct EntryImpl(libc::dirent64); + +impl Deref for EntryImpl { + type Target = libc::dirent64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl EntryExt for Entry { + fn ino(&self) -> u64 { + self.0.d_ino.into() + } + + fn seek_loc(&self) -> SeekLoc { + unsafe { SeekLoc::from_raw(self.0.d_off) } + } +} + +pub(crate) unsafe fn iter_impl(dir: &Dir) -> Option> { + let errno = Errno::last(); + let dirent = libc::readdir64(dir.0.as_ptr()); + if dirent.is_null() { + if errno != Errno::last() { + // TODO This should be verified on different BSD-flavours. + // + // According to 4.3BSD/POSIX.1-2001 man pages, there was an error + // if the errno value has changed at some point during the sequence + // of readdir calls. + Some(Err(Errno::last().into())) + } else { + // Not an error. We've simply reached the end of the stream. + None + } + } else { + Some(Ok(EntryImpl(*dirent))) + } +} diff --git a/crates/wasi-common/yanix/src/sys/linux/fadvise.rs b/crates/wasi-common/yanix/src/sys/linux/fadvise.rs new file mode 100644 index 0000000000..999525e1c7 --- /dev/null +++ b/crates/wasi-common/yanix/src/sys/linux/fadvise.rs @@ -0,0 +1,22 @@ +use crate::{Errno, Result}; +use std::os::unix::prelude::*; + +#[derive(Debug, Copy, Clone)] +#[repr(i32)] +pub enum PosixFadviseAdvice { + Normal = libc::POSIX_FADV_NORMAL, + Sequential = libc::POSIX_FADV_SEQUENTIAL, + Random = libc::POSIX_FADV_RANDOM, + NoReuse = libc::POSIX_FADV_NOREUSE, + WillNeed = libc::POSIX_FADV_WILLNEED, + DontNeed = libc::POSIX_FADV_DONTNEED, +} + +pub unsafe fn posix_fadvise( + fd: RawFd, + offset: libc::off_t, + len: libc::off_t, + advice: PosixFadviseAdvice, +) -> Result<()> { + Errno::from_success_code(libc::posix_fadvise(fd, offset, len, advice as libc::c_int)) +} diff --git a/crates/wasi-common/yanix/src/sys/linux/file.rs b/crates/wasi-common/yanix/src/sys/linux/file.rs new file mode 100644 index 0000000000..c5f88f457b --- /dev/null +++ b/crates/wasi-common/yanix/src/sys/linux/file.rs @@ -0,0 +1,23 @@ +use crate::{Errno, Result}; +use std::os::unix::prelude::*; + +pub unsafe fn isatty(fd: RawFd) -> Result { + let res = libc::isatty(fd); + if res == 1 { + // isatty() returns 1 if fd is an open file descriptor referring to a terminal... + Ok(true) + } else { + // ... otherwise 0 is returned, and errno is set to indicate the error. + let errno = Errno::last(); + // While POSIX specifies ENOTTY if the passed + // fd is *not* a tty, on Linux, some implementations + // may return EINVAL instead. + // + // https://linux.die.net/man/3/isatty + if errno == Errno::ENOTTY || errno == Errno::EINVAL { + Ok(false) + } else { + Err(errno.into()) + } + } +} diff --git a/crates/wasi-common/yanix/src/sys/linux/mod.rs b/crates/wasi-common/yanix/src/sys/linux/mod.rs new file mode 100644 index 0000000000..3e6611f7b9 --- /dev/null +++ b/crates/wasi-common/yanix/src/sys/linux/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod dir; +pub(crate) mod fadvise; +pub(crate) mod file; diff --git a/crates/wasi-common/yanix/src/sys/mod.rs b/crates/wasi-common/yanix/src/sys/mod.rs new file mode 100644 index 0000000000..523e9bf754 --- /dev/null +++ b/crates/wasi-common/yanix/src/sys/mod.rs @@ -0,0 +1,27 @@ +use crate::dir::SeekLoc; +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(any(target_os = "linux", + target_os = "android", + target_os = "emscripten"))] { + mod linux; + pub(crate) use self::linux::*; + } + else if #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "dragonfly"))] { + mod bsd; + pub(crate) use self::bsd::*; + } else { + compile_error!("yanix doesn't compile for this platform yet"); + } +} + +pub trait EntryExt { + fn ino(&self) -> u64; + fn seek_loc(&self) -> SeekLoc; +}