From cb4a430b8349d9216f2c58d921a1b3a5aa6a6652 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 10 Oct 2025 12:59:41 -0700 Subject: [PATCH] std: Use `libc` for filesystem ops on WASI targets This commit is a large change to the implementation of filesystem and other system-related operations on WASI targets. Previously the standard library explicitly used the `wasi` crate at the 0.11.x version track which means that it used WASIp1 APIs directly. This meant that `std` was hard-coded to use WASIp1 syscalls and there was no separate implementation for the WASIp{2,3} targets, for example. The high-level goal of this commit is to decouple this interaction and avoid the use of the `wasi` crate on the WASIp2 target. Historically when WASIp1 was originally added to Rust the wasi-libc library was in a much different position than it is today. Nowadays Rust already depends on wasi-libc on WASI targets for things like memory allocation and environment variable management. As a libc library it also has all the functions necessary to implement all filesystem operations Rust wants. Recently wasi-libc additionally was updated to use WASIp2 APIs directly on the `wasm32-wasip2` target instead of using `wasm32-wasip1` APIs. This commit is leveraging this work by enabling Rust to completely sever the dependence on WASIp1 APIs when compiling for `wasm32-wasip2`. This is also intended to make it easier to migrate to `wasm32-wasip3` internally in the future where now only libc need be updated and Rust doesn't need to explicitly change as well. This commit fairly heavily changes the internals of the implementation of WASI filesystem operations. The basis of implementation is now libc-style APIs as opposed to WASIp1-style-APIs which necessitated a number of changes throughout. Additionally the `std::os::wasi::fs` module has had a few API-breaking changes as well, but this module is also unstable. While this module has been "stable" since inception in the sense that it hasn't changed, this PR is what the unstable status was sort of trying to buy in terms of future flexibility to make changes. For users of stable `std::fs` interfaces this is not intended to be a breaking change but there nevertheless may be minor issues here and there. Concrete changes here are: * `std` for `wasm32-wasip2` no longer depends on `wasi 0.11.x` * The implementation of `std::os::wasi::fs`, which was previously unstable and still is, is now only available on `wasm32-wasip1` and is implemented largely directly in terms of WASIp1 APIs by directly using the `wasi 0.11.x` crate. * `std::os::wasi::fs::MetadataExt`, which was previously unstable and still is, has removed methods that duplicate functionality in `std::fs::Metadata` as there's no point in having them. * `std::os::wasi::fs::FileTypeExt`, which was previously unstable and still is, lost the ability to distinguish between dgram/stream sockets. WASIp1 sockets were never really supported, though, so this isn't much of a loss. * The internal `WasiFd` type has been "gutted" to have far fewer methods internally. This still represents a file descriptor owned by `wasi-libc`, however. * The `std::sys::fs::wasi` implementation is overhauled to largely use the `libc` crate and its APIs directly (which go to wasi-libc). This drastically changes the `ReadDir` implementation, for example. * The `std::sys::fs::wasi::OpenOptions` API now has two ways of opening files. By default `libc::open` is used but of `std::os::wasi::fs::OpenOptionsExt` is used then a WASIp1-specific API is used to open a file. * Types like `FileAttr` and `FileType` now use libc representations at-rest instead of WASIp1 internals. * Various transmutations for iovec-style APIs are consolidated into a WASI-specific module close to the definition of the `IoSlice{,Mut}` types to make it easier to audit unsafe guarantees. * The `wasip2` OS module gained a custom implementation of the `helpers` module as the WASIp1 version of this module is no longer applicable. I'd like to do follow-up PRs after this one to reorganize `std::os::wasi*` a bit to be more amenable for WASIp{2,3} targets as well, but that'll come in a future PR. For now this is tasked with the high-level goal of removing the dependency on WASIp1 APIs in the standard library by relying on wasi-libc to natively use WASIp2 APIs. --- library/std/Cargo.toml | 2 +- library/std/src/os/wasi/fs.rs | 142 ++-- library/std/src/os/wasi/mod.rs | 3 + library/std/src/os/wasi/net/mod.rs | 5 +- library/std/src/sys/fd/wasi.rs | 273 +------- library/std/src/sys/fs/wasi.rs | 777 ++++++++++++++-------- library/std/src/sys/io/io_slice/wasi.rs | 83 ++- library/std/src/sys/pal/wasip1/time.rs | 20 +- library/std/src/sys/pal/wasip2/helpers.rs | 57 ++ library/std/src/sys/pal/wasip2/mod.rs | 3 +- library/std/src/sys/pal/wasip2/time.rs | 20 +- tests/rustdoc-js-std/unbox-type-result.js | 1 - 12 files changed, 751 insertions(+), 635 deletions(-) create mode 100644 library/std/src/sys/pal/wasip2/helpers.rs diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 779b07ce240a6..09e228dd62c74 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -75,7 +75,7 @@ hermit-abi = { version = "0.5.0", features = [ 'rustc-dep-of-std', ], public = true } -[target.'cfg(target_os = "wasi")'.dependencies] +[target.'cfg(all(target_os = "wasi", target_env = "p1"))'.dependencies] wasi = { version = "0.11.0", features = [ 'rustc-dep-of-std', ], default-features = false } diff --git a/library/std/src/os/wasi/fs.rs b/library/std/src/os/wasi/fs.rs index 5ea91dd6521ad..981b29751409c 100644 --- a/library/std/src/os/wasi/fs.rs +++ b/library/std/src/os/wasi/fs.rs @@ -1,4 +1,4 @@ -//! WASI-specific extensions to primitives in the [`std::fs`] module. +//! WASIp1-specific extensions to primitives in the [`std::fs`] module. //! //! [`std::fs`]: crate::fs @@ -9,9 +9,11 @@ use io::{Read, Write}; use crate::ffi::OsStr; -use crate::fs::{self, File, Metadata, OpenOptions}; +use crate::fs::{self, File, OpenOptions}; use crate::io::{self, IoSlice, IoSliceMut}; +use crate::os::fd::AsRawFd; use crate::path::{Path, PathBuf}; +use crate::sys::err2io; use crate::sys_common::{AsInner, AsInnerMut, FromInner}; /// WASI-specific extensions to [`File`]. @@ -200,7 +202,7 @@ pub trait FileExt { /// /// This corresponds to the `path_filestat_get` syscall. #[doc(alias = "path_filestat_get")] - fn metadata_at>(&self, lookup_flags: u32, path: P) -> io::Result; + fn metadata_at>(&self, lookup_flags: i32, path: P) -> io::Result; /// Unlinks a file. /// @@ -224,19 +226,24 @@ pub trait FileExt { impl FileExt for fs::File { fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result { - self.as_inner().as_inner().pread(bufs, offset) + let bufs = crate::sys::io::IoSliceMut::as_wasip1_slice(bufs); + unsafe { wasi::fd_pread(self.as_raw_fd() as wasi::Fd, bufs, offset).map_err(err2io) } } fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result { - self.as_inner().as_inner().pwrite(bufs, offset) + let bufs = crate::sys::io::IoSlice::as_wasip1_slice(bufs); + unsafe { wasi::fd_pwrite(self.as_raw_fd() as wasi::Fd, bufs, offset).map_err(err2io) } } fn fdstat_set_flags(&self, flags: u16) -> io::Result<()> { - self.as_inner().as_inner().set_flags(flags) + unsafe { wasi::fd_fdstat_set_flags(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) } } fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()> { - self.as_inner().as_inner().set_rights(rights, inheriting) + unsafe { + wasi::fd_fdstat_set_rights(self.as_raw_fd() as wasi::Fd, rights, inheriting) + .map_err(err2io) + } } fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()> { @@ -255,32 +262,37 @@ impl FileExt for fs::File { } }; - self.as_inner().as_inner().advise(offset, len, advice) + unsafe { + wasi::fd_advise(self.as_raw_fd() as wasi::Fd, offset, len, advice).map_err(err2io) + } } fn allocate(&self, offset: u64, len: u64) -> io::Result<()> { - self.as_inner().as_inner().allocate(offset, len) + unsafe { wasi::fd_allocate(self.as_raw_fd() as wasi::Fd, offset, len).map_err(err2io) } } fn create_directory>(&self, dir: P) -> io::Result<()> { - self.as_inner().as_inner().create_directory(osstr2str(dir.as_ref().as_ref())?) + let path = osstr2str(dir.as_ref().as_ref())?; + unsafe { wasi::path_create_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } } fn read_link>(&self, path: P) -> io::Result { - self.as_inner().read_link(path.as_ref()) + self.as_inner().readlink_at(path.as_ref()) } - fn metadata_at>(&self, lookup_flags: u32, path: P) -> io::Result { + fn metadata_at>(&self, lookup_flags: i32, path: P) -> io::Result { let m = self.as_inner().metadata_at(lookup_flags, path.as_ref())?; Ok(FromInner::from_inner(m)) } fn remove_file>(&self, path: P) -> io::Result<()> { - self.as_inner().as_inner().unlink_file(osstr2str(path.as_ref().as_ref())?) + let path = osstr2str(path.as_ref().as_ref())?; + unsafe { wasi::path_unlink_file(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } } fn remove_directory>(&self, path: P) -> io::Result<()> { - self.as_inner().as_inner().remove_directory(osstr2str(path.as_ref().as_ref())?) + let path = osstr2str(path.as_ref().as_ref())?; + unsafe { wasi::path_remove_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } } } @@ -355,42 +367,42 @@ pub trait OpenOptionsExt { impl OpenOptionsExt for OpenOptions { fn lookup_flags(&mut self, flags: u32) -> &mut OpenOptions { - self.as_inner_mut().lookup_flags(flags); + self.as_inner_mut().wasip1_lookup_flags(flags); self } fn directory(&mut self, dir: bool) -> &mut OpenOptions { - self.as_inner_mut().directory(dir); + self.as_inner_mut().wasip1_directory(dir); self } fn dsync(&mut self, enabled: bool) -> &mut OpenOptions { - self.as_inner_mut().dsync(enabled); + self.as_inner_mut().wasip1_dsync(enabled); self } fn nonblock(&mut self, enabled: bool) -> &mut OpenOptions { - self.as_inner_mut().nonblock(enabled); + self.as_inner_mut().wasip1_nonblock(enabled); self } fn rsync(&mut self, enabled: bool) -> &mut OpenOptions { - self.as_inner_mut().rsync(enabled); + self.as_inner_mut().wasip1_rsync(enabled); self } fn sync(&mut self, enabled: bool) -> &mut OpenOptions { - self.as_inner_mut().sync(enabled); + self.as_inner_mut().wasip1_sync(enabled); self } fn fs_rights_base(&mut self, rights: u64) -> &mut OpenOptions { - self.as_inner_mut().fs_rights_base(rights); + self.as_inner_mut().wasip1_fs_rights_base(rights); self } fn fs_rights_inheriting(&mut self, rights: u64) -> &mut OpenOptions { - self.as_inner_mut().fs_rights_inheriting(rights); + self.as_inner_mut().wasip1_fs_rights_inheriting(rights); self } @@ -408,37 +420,17 @@ pub trait MetadataExt { fn ino(&self) -> u64; /// Returns the `st_nlink` field of the internal `filestat_t` fn nlink(&self) -> u64; - /// Returns the `st_size` field of the internal `filestat_t` - fn size(&self) -> u64; - /// Returns the `st_atim` field of the internal `filestat_t` - fn atim(&self) -> u64; - /// Returns the `st_mtim` field of the internal `filestat_t` - fn mtim(&self) -> u64; - /// Returns the `st_ctim` field of the internal `filestat_t` - fn ctim(&self) -> u64; } impl MetadataExt for fs::Metadata { fn dev(&self) -> u64 { - self.as_inner().as_wasi().dev + self.as_inner().as_libc().st_dev } fn ino(&self) -> u64 { - self.as_inner().as_wasi().ino + self.as_inner().as_libc().st_ino } fn nlink(&self) -> u64 { - self.as_inner().as_wasi().nlink - } - fn size(&self) -> u64 { - self.as_inner().as_wasi().size - } - fn atim(&self) -> u64 { - self.as_inner().as_wasi().atim - } - fn mtim(&self) -> u64 { - self.as_inner().as_wasi().mtim - } - fn ctim(&self) -> u64 { - self.as_inner().as_wasi().ctim + self.as_inner().as_libc().st_nlink } } @@ -451,28 +443,19 @@ pub trait FileTypeExt { fn is_block_device(&self) -> bool; /// Returns `true` if this file type is a character device. fn is_char_device(&self) -> bool; - /// Returns `true` if this file type is a socket datagram. - fn is_socket_dgram(&self) -> bool; - /// Returns `true` if this file type is a socket stream. - fn is_socket_stream(&self) -> bool; /// Returns `true` if this file type is any type of socket. - fn is_socket(&self) -> bool { - self.is_socket_stream() || self.is_socket_dgram() - } + fn is_socket(&self) -> bool; } impl FileTypeExt for fs::FileType { fn is_block_device(&self) -> bool { - self.as_inner().bits() == wasi::FILETYPE_BLOCK_DEVICE + self.as_inner().mode() == libc::S_IFBLK } fn is_char_device(&self) -> bool { - self.as_inner().bits() == wasi::FILETYPE_CHARACTER_DEVICE - } - fn is_socket_dgram(&self) -> bool { - self.as_inner().bits() == wasi::FILETYPE_SOCKET_DGRAM + self.as_inner().mode() == libc::S_IFCHR } - fn is_socket_stream(&self) -> bool { - self.as_inner().bits() == wasi::FILETYPE_SOCKET_STREAM + fn is_socket(&self) -> bool { + self.as_inner().mode() == libc::S_IFSOCK } } @@ -499,12 +482,16 @@ pub fn link, U: AsRef>( new_fd: &File, new_path: U, ) -> io::Result<()> { - old_fd.as_inner().as_inner().link( - old_flags, - osstr2str(old_path.as_ref().as_ref())?, - new_fd.as_inner().as_inner(), - osstr2str(new_path.as_ref().as_ref())?, - ) + unsafe { + wasi::path_link( + old_fd.as_raw_fd() as wasi::Fd, + old_flags, + osstr2str(old_path.as_ref().as_ref())?, + new_fd.as_raw_fd() as wasi::Fd, + osstr2str(new_path.as_ref().as_ref())?, + ) + .map_err(err2io) + } } /// Renames a file or directory. @@ -517,11 +504,15 @@ pub fn rename, U: AsRef>( new_fd: &File, new_path: U, ) -> io::Result<()> { - old_fd.as_inner().as_inner().rename( - osstr2str(old_path.as_ref().as_ref())?, - new_fd.as_inner().as_inner(), - osstr2str(new_path.as_ref().as_ref())?, - ) + unsafe { + wasi::path_rename( + old_fd.as_raw_fd() as wasi::Fd, + osstr2str(old_path.as_ref().as_ref())?, + new_fd.as_raw_fd() as wasi::Fd, + osstr2str(new_path.as_ref().as_ref())?, + ) + .map_err(err2io) + } } /// Creates a symbolic link. @@ -533,9 +524,14 @@ pub fn symlink, U: AsRef>( fd: &File, new_path: U, ) -> io::Result<()> { - fd.as_inner() - .as_inner() - .symlink(osstr2str(old_path.as_ref().as_ref())?, osstr2str(new_path.as_ref().as_ref())?) + unsafe { + wasi::path_symlink( + osstr2str(old_path.as_ref().as_ref())?, + fd.as_raw_fd() as wasi::Fd, + osstr2str(new_path.as_ref().as_ref())?, + ) + .map_err(err2io) + } } /// Creates a symbolic link. diff --git a/library/std/src/os/wasi/mod.rs b/library/std/src/os/wasi/mod.rs index 2ee6aa4660094..a922c4cadef89 100644 --- a/library/std/src/os/wasi/mod.rs +++ b/library/std/src/os/wasi/mod.rs @@ -34,6 +34,7 @@ #![doc(cfg(target_os = "wasi"))] pub mod ffi; +#[cfg(target_env = "p1")] pub mod fs; pub mod io; @@ -50,9 +51,11 @@ pub mod prelude { pub use super::ffi::{OsStrExt, OsStringExt}; #[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")] + #[cfg(target_env = "p1")] pub use super::fs::FileTypeExt; #[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")] + #[cfg(target_env = "p1")] pub use super::fs::{DirEntryExt, FileExt, MetadataExt, OpenOptionsExt}; #[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")] diff --git a/library/std/src/os/wasi/net/mod.rs b/library/std/src/os/wasi/net/mod.rs index 4704dd574517a..9430cd3b05eee 100644 --- a/library/std/src/os/wasi/net/mod.rs +++ b/library/std/src/os/wasi/net/mod.rs @@ -2,7 +2,8 @@ #![unstable(feature = "wasi_ext", issue = "71213")] -use crate::sys_common::AsInner; +use crate::os::fd::AsRawFd; +use crate::sys::err2io; use crate::{io, net}; /// WASI-specific extensions to [`std::net::TcpListener`]. @@ -17,6 +18,6 @@ pub trait TcpListenerExt { impl TcpListenerExt for net::TcpListener { fn sock_accept(&self, flags: u16) -> io::Result { - self.as_inner().as_inner().as_inner().sock_accept(flags) + unsafe { wasi::sock_accept(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) } } } diff --git a/library/std/src/sys/fd/wasi.rs b/library/std/src/sys/fd/wasi.rs index 80a5143ff0b00..febc8de83605b 100644 --- a/library/std/src/sys/fd/wasi.rs +++ b/library/std/src/sys/fd/wasi.rs @@ -1,10 +1,6 @@ -#![expect(dead_code)] - -use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; -use crate::mem; -use crate::net::Shutdown; +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; -use crate::sys::pal::err2io; +use crate::sys::os::cvt; use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; #[derive(Debug)] @@ -12,271 +8,34 @@ pub struct WasiFd { fd: OwnedFd, } -fn iovec<'a>(a: &'a mut [IoSliceMut<'_>]) -> &'a [wasi::Iovec] { - assert_eq!(size_of::>(), size_of::()); - assert_eq!(align_of::>(), align_of::()); - // SAFETY: `IoSliceMut` and `IoVec` have exactly the same memory layout. - // We decorate our `IoSliceMut` with `repr(transparent)` (see `io.rs`), and - // `crate::io::IoSliceMut` is a `repr(transparent)` wrapper around our type, so this is - // guaranteed. - unsafe { mem::transmute(a) } -} - -fn ciovec<'a>(a: &'a [IoSlice<'_>]) -> &'a [wasi::Ciovec] { - assert_eq!(size_of::>(), size_of::()); - assert_eq!(align_of::>(), align_of::()); - // SAFETY: `IoSlice` and `CIoVec` have exactly the same memory layout. - // We decorate our `IoSlice` with `repr(transparent)` (see `io.rs`), and - // `crate::io::IoSlice` is a `repr(transparent)` wrapper around our type, so this is - // guaranteed. - unsafe { mem::transmute(a) } -} - impl WasiFd { - pub fn datasync(&self) -> io::Result<()> { - unsafe { wasi::fd_datasync(self.as_raw_fd() as wasi::Fd).map_err(err2io) } - } - - pub fn pread(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result { - unsafe { wasi::fd_pread(self.as_raw_fd() as wasi::Fd, iovec(bufs), offset).map_err(err2io) } - } - - pub fn pwrite(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result { + pub fn read(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + let bufs = crate::sys::io::IoSliceMut::as_libc_slice(bufs); unsafe { - wasi::fd_pwrite(self.as_raw_fd() as wasi::Fd, ciovec(bufs), offset).map_err(err2io) + let n = cvt(libc::readv(self.as_raw_fd(), bufs.as_ptr(), bufs.len() as libc::c_int))?; + Ok(n as usize) } } - pub fn read(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { - unsafe { wasi::fd_read(self.as_raw_fd() as wasi::Fd, iovec(bufs)).map_err(err2io) } - } - pub fn read_buf(&self, mut buf: BorrowedCursor<'_>) -> io::Result<()> { unsafe { - let bufs = [wasi::Iovec { - buf: buf.as_mut().as_mut_ptr() as *mut u8, - buf_len: buf.capacity(), - }]; - match wasi::fd_read(self.as_raw_fd() as wasi::Fd, &bufs) { - Ok(n) => { - buf.advance_unchecked(n); - Ok(()) - } - Err(e) => Err(err2io(e)), - } + let amt = cvt(libc::read( + self.as_raw_fd(), + buf.as_mut().as_mut_ptr().cast(), + buf.as_mut().len(), + ))?; + buf.advance_unchecked(amt as usize); + Ok(()) } } pub fn write(&self, bufs: &[IoSlice<'_>]) -> io::Result { - unsafe { wasi::fd_write(self.as_raw_fd() as wasi::Fd, ciovec(bufs)).map_err(err2io) } - } - - pub fn seek(&self, pos: SeekFrom) -> io::Result { - let (whence, offset) = match pos { - SeekFrom::Start(pos) => (wasi::WHENCE_SET, pos as i64), - SeekFrom::End(pos) => (wasi::WHENCE_END, pos), - SeekFrom::Current(pos) => (wasi::WHENCE_CUR, pos), - }; - unsafe { wasi::fd_seek(self.as_raw_fd() as wasi::Fd, offset, whence).map_err(err2io) } - } - - pub fn tell(&self) -> io::Result { - unsafe { wasi::fd_tell(self.as_raw_fd() as wasi::Fd).map_err(err2io) } - } - - // FIXME: __wasi_fd_fdstat_get - - pub fn set_flags(&self, flags: wasi::Fdflags) -> io::Result<()> { - unsafe { wasi::fd_fdstat_set_flags(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) } - } - - pub fn set_rights(&self, base: wasi::Rights, inheriting: wasi::Rights) -> io::Result<()> { - unsafe { - wasi::fd_fdstat_set_rights(self.as_raw_fd() as wasi::Fd, base, inheriting) - .map_err(err2io) - } - } - - pub fn sync(&self) -> io::Result<()> { - unsafe { wasi::fd_sync(self.as_raw_fd() as wasi::Fd).map_err(err2io) } - } - - pub(crate) fn advise(&self, offset: u64, len: u64, advice: wasi::Advice) -> io::Result<()> { - unsafe { - wasi::fd_advise(self.as_raw_fd() as wasi::Fd, offset, len, advice).map_err(err2io) - } - } - - pub fn allocate(&self, offset: u64, len: u64) -> io::Result<()> { - unsafe { wasi::fd_allocate(self.as_raw_fd() as wasi::Fd, offset, len).map_err(err2io) } - } - - pub fn create_directory(&self, path: &str) -> io::Result<()> { - unsafe { wasi::path_create_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } - } - - pub fn link( - &self, - old_flags: wasi::Lookupflags, - old_path: &str, - new_fd: &WasiFd, - new_path: &str, - ) -> io::Result<()> { - unsafe { - wasi::path_link( - self.as_raw_fd() as wasi::Fd, - old_flags, - old_path, - new_fd.as_raw_fd() as wasi::Fd, - new_path, - ) - .map_err(err2io) - } - } - - pub fn open( - &self, - dirflags: wasi::Lookupflags, - path: &str, - oflags: wasi::Oflags, - fs_rights_base: wasi::Rights, - fs_rights_inheriting: wasi::Rights, - fs_flags: wasi::Fdflags, - ) -> io::Result { - unsafe { - wasi::path_open( - self.as_raw_fd() as wasi::Fd, - dirflags, - path, - oflags, - fs_rights_base, - fs_rights_inheriting, - fs_flags, - ) - .map(|fd| WasiFd::from_raw_fd(fd as RawFd)) - .map_err(err2io) - } - } - - pub fn readdir(&self, buf: &mut [u8], cookie: wasi::Dircookie) -> io::Result { + let bufs = crate::sys::io::IoSlice::as_libc_slice(bufs); unsafe { - wasi::fd_readdir(self.as_raw_fd() as wasi::Fd, buf.as_mut_ptr(), buf.len(), cookie) - .map_err(err2io) + let n = cvt(libc::writev(self.as_raw_fd(), bufs.as_ptr(), bufs.len() as libc::c_int))?; + Ok(n as usize) } } - - pub fn readlink(&self, path: &str, buf: &mut [u8]) -> io::Result { - unsafe { - wasi::path_readlink(self.as_raw_fd() as wasi::Fd, path, buf.as_mut_ptr(), buf.len()) - .map_err(err2io) - } - } - - pub fn rename(&self, old_path: &str, new_fd: &WasiFd, new_path: &str) -> io::Result<()> { - unsafe { - wasi::path_rename( - self.as_raw_fd() as wasi::Fd, - old_path, - new_fd.as_raw_fd() as wasi::Fd, - new_path, - ) - .map_err(err2io) - } - } - - pub(crate) fn filestat_get(&self) -> io::Result { - unsafe { wasi::fd_filestat_get(self.as_raw_fd() as wasi::Fd).map_err(err2io) } - } - - pub fn filestat_set_times( - &self, - atim: wasi::Timestamp, - mtim: wasi::Timestamp, - fstflags: wasi::Fstflags, - ) -> io::Result<()> { - unsafe { - wasi::fd_filestat_set_times(self.as_raw_fd() as wasi::Fd, atim, mtim, fstflags) - .map_err(err2io) - } - } - - pub fn filestat_set_size(&self, size: u64) -> io::Result<()> { - unsafe { wasi::fd_filestat_set_size(self.as_raw_fd() as wasi::Fd, size).map_err(err2io) } - } - - pub(crate) fn path_filestat_get( - &self, - flags: wasi::Lookupflags, - path: &str, - ) -> io::Result { - unsafe { - wasi::path_filestat_get(self.as_raw_fd() as wasi::Fd, flags, path).map_err(err2io) - } - } - - pub fn path_filestat_set_times( - &self, - flags: wasi::Lookupflags, - path: &str, - atim: wasi::Timestamp, - mtim: wasi::Timestamp, - fstflags: wasi::Fstflags, - ) -> io::Result<()> { - unsafe { - wasi::path_filestat_set_times( - self.as_raw_fd() as wasi::Fd, - flags, - path, - atim, - mtim, - fstflags, - ) - .map_err(err2io) - } - } - - pub fn symlink(&self, old_path: &str, new_path: &str) -> io::Result<()> { - unsafe { - wasi::path_symlink(old_path, self.as_raw_fd() as wasi::Fd, new_path).map_err(err2io) - } - } - - pub fn unlink_file(&self, path: &str) -> io::Result<()> { - unsafe { wasi::path_unlink_file(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } - } - - pub fn remove_directory(&self, path: &str) -> io::Result<()> { - unsafe { wasi::path_remove_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } - } - - pub fn sock_accept(&self, flags: wasi::Fdflags) -> io::Result { - unsafe { wasi::sock_accept(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) } - } - - pub fn sock_recv( - &self, - ri_data: &mut [IoSliceMut<'_>], - ri_flags: wasi::Riflags, - ) -> io::Result<(usize, wasi::Roflags)> { - unsafe { - wasi::sock_recv(self.as_raw_fd() as wasi::Fd, iovec(ri_data), ri_flags).map_err(err2io) - } - } - - pub fn sock_send(&self, si_data: &[IoSlice<'_>], si_flags: wasi::Siflags) -> io::Result { - unsafe { - wasi::sock_send(self.as_raw_fd() as wasi::Fd, ciovec(si_data), si_flags).map_err(err2io) - } - } - - pub fn sock_shutdown(&self, how: Shutdown) -> io::Result<()> { - let how = match how { - Shutdown::Read => wasi::SDFLAGS_RD, - Shutdown::Write => wasi::SDFLAGS_WR, - Shutdown::Both => wasi::SDFLAGS_WR | wasi::SDFLAGS_RD, - }; - unsafe { wasi::sock_shutdown(self.as_raw_fd() as wasi::Fd, how).map_err(err2io) } - } } impl AsInner for WasiFd { diff --git a/library/std/src/sys/fs/wasi.rs b/library/std/src/sys/fs/wasi.rs index 0b65b9cb389df..37201b0cf44a6 100644 --- a/library/std/src/sys/fs/wasi.rs +++ b/library/std/src/sys/fs/wasi.rs @@ -1,7 +1,7 @@ -use crate::ffi::{CStr, OsStr, OsString}; +use crate::ffi::{CStr, CString, OsStr, OsString}; use crate::fs::TryLockError; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; -use crate::mem::{self, ManuallyDrop}; +use crate::mem::ManuallyDrop; use crate::os::raw::c_int; use crate::os::wasi::ffi::{OsStrExt, OsStringExt}; use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; @@ -21,43 +21,25 @@ pub struct File { #[derive(Clone)] pub struct FileAttr { - meta: wasi::Filestat, + stat: libc::stat, } pub struct ReadDir { inner: Arc, - state: ReadDirState, -} - -enum ReadDirState { - /// Fill `buf` with `buf.len()` bytes starting from `next_read_offset`. - FillBuffer { - next_read_offset: wasi::Dircookie, - buf: Vec, - }, - ProcessEntry { - buf: Vec, - next_read_offset: Option, - offset: usize, - }, - /// There is no more data to get in [`Self::FillBuffer`]; keep returning - /// entries via ProcessEntry until `buf` is exhausted. - RunUntilExhaustion { - buf: Vec, - offset: usize, - }, - Done, + done: bool, } struct ReadDirInner { root: PathBuf, - dir: File, + dirp: c::Dirp, } pub struct DirEntry { - meta: wasi::Dirent, - name: Vec, inner: Arc, + #[cfg(target_env = "p1")] + d_ino: libc::ino_t, + d_type: libc::c_uchar, + name: CString, } #[derive(Clone, Debug, Default)] @@ -65,11 +47,21 @@ pub struct OpenOptions { read: bool, write: bool, append: bool, - dirflags: wasi::Lookupflags, - fdflags: wasi::Fdflags, - oflags: wasi::Oflags, - rights_base: Option, - rights_inheriting: Option, + truncate: bool, + create: bool, + create_new: bool, + custom_flags: libc::c_int, + + #[cfg(target_env = "p1")] + use_wasip1: bool, + #[cfg(target_env = "p1")] + wasip1_dirflags: Option, + #[cfg(target_env = "p1")] + wasip1_fdflags: wasi::Fdflags, + #[cfg(target_env = "p1")] + wasip1_rights_base: Option, + #[cfg(target_env = "p1")] + wasip1_rights_inheriting: Option, } #[derive(Clone, PartialEq, Eq, Debug)] @@ -85,7 +77,7 @@ pub struct FileTimes { #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] pub struct FileType { - bits: wasi::Filetype, + mode: libc::mode_t, } #[derive(Debug)] @@ -93,7 +85,7 @@ pub struct DirBuilder {} impl FileAttr { pub fn size(&self) -> u64 { - self.meta.size + self.stat.st_size as u64 } pub fn perm(&self) -> FilePermissions { @@ -102,23 +94,24 @@ impl FileAttr { } pub fn file_type(&self) -> FileType { - FileType { bits: self.meta.filetype } + FileType { mode: self.stat.st_mode } } pub fn modified(&self) -> io::Result { - Ok(SystemTime::from_wasi_timestamp(self.meta.mtim)) + Ok(SystemTime::from_timespec(self.stat.st_mtim)) } pub fn accessed(&self) -> io::Result { - Ok(SystemTime::from_wasi_timestamp(self.meta.atim)) + Ok(SystemTime::from_timespec(self.stat.st_atim)) } pub fn created(&self) -> io::Result { - Ok(SystemTime::from_wasi_timestamp(self.meta.ctim)) + Ok(SystemTime::from_timespec(self.stat.st_ctim)) } - pub(crate) fn as_wasi(&self) -> &wasi::Filestat { - &self.meta + #[cfg(target_env = "p1")] + pub(crate) fn as_libc(&self) -> &libc::stat { + &self.stat } } @@ -144,28 +137,27 @@ impl FileTimes { impl FileType { pub fn is_dir(&self) -> bool { - self.bits == wasi::FILETYPE_DIRECTORY + self.mode == libc::S_IFDIR } pub fn is_file(&self) -> bool { - self.bits == wasi::FILETYPE_REGULAR_FILE + self.mode == libc::S_IFREG } pub fn is_symlink(&self) -> bool { - self.bits == wasi::FILETYPE_SYMBOLIC_LINK + self.mode == libc::S_IFLNK } - pub(crate) fn bits(&self) -> wasi::Filetype { - self.bits + #[cfg(target_env = "p1")] + pub(crate) fn mode(&self) -> libc::mode_t { + self.mode } } impl ReadDir { - fn new(dir: File, root: PathBuf) -> ReadDir { - ReadDir { - inner: Arc::new(ReadDirInner { dir, root }), - state: ReadDirState::FillBuffer { next_read_offset: 0, buf: vec![0; 128] }, - } + fn new(dir: File, root: PathBuf) -> io::Result { + let dirp = c::Dirp::new(dir)?; + Ok(ReadDir { inner: Arc::new(ReadDirInner { dirp, root }), done: false }) } } @@ -181,125 +173,83 @@ impl Iterator for ReadDir { type Item = io::Result; fn next(&mut self) -> Option> { - match &mut self.state { - ReadDirState::FillBuffer { next_read_offset, buf } => { - let result = self.inner.dir.fd.readdir(buf, *next_read_offset); - match result { - Ok(read_bytes) => { - if read_bytes < buf.len() { - buf.truncate(read_bytes); - self.state = - ReadDirState::RunUntilExhaustion { buf: mem::take(buf), offset: 0 }; - } else { - debug_assert_eq!(read_bytes, buf.len()); - self.state = ReadDirState::ProcessEntry { - buf: mem::take(buf), - offset: 0, - next_read_offset: Some(*next_read_offset), - }; - } - self.next() - } - Err(e) => { - self.state = ReadDirState::Done; - return Some(Err(e)); - } - } - } - ReadDirState::ProcessEntry { buf, next_read_offset, offset } => { - let contents = &buf[*offset..]; - const DIRENT_SIZE: usize = size_of::(); - if contents.len() >= DIRENT_SIZE { - let (dirent, data) = contents.split_at(DIRENT_SIZE); - let dirent = - unsafe { ptr::read_unaligned(dirent.as_ptr() as *const wasi::Dirent) }; - // If the file name was truncated, then we need to reinvoke - // `readdir` so we truncate our buffer to start over and reread this - // descriptor. - if data.len() < dirent.d_namlen as usize { - if buf.len() < dirent.d_namlen as usize + DIRENT_SIZE { - buf.resize(dirent.d_namlen as usize + DIRENT_SIZE, 0); - } - if let Some(next_read_offset) = *next_read_offset { - self.state = - ReadDirState::FillBuffer { next_read_offset, buf: mem::take(buf) }; - } else { - self.state = ReadDirState::Done; - } - - return self.next(); - } - next_read_offset.as_mut().map(|cookie| { - *cookie = dirent.d_next; - }); - *offset = *offset + DIRENT_SIZE + dirent.d_namlen as usize; - - let name = &data[..(dirent.d_namlen as usize)]; - - // These names are skipped on all other platforms, so let's skip - // them here too - if name == b"." || name == b".." { - return self.next(); - } + if self.done { + return None; + } - return Some(Ok(DirEntry { - meta: dirent, - name: name.to_vec(), - inner: self.inner.clone(), - })); - } else if let Some(next_read_offset) = *next_read_offset { - self.state = ReadDirState::FillBuffer { next_read_offset, buf: mem::take(buf) }; - } else { - self.state = ReadDirState::Done; + loop { + let entry_ptr = match self.inner.dirp.readdir() { + Ok(Some(ptr)) => ptr, + Ok(None) => { + self.done = true; + return None; } - self.next() - } - ReadDirState::RunUntilExhaustion { buf, offset } => { - if *offset >= buf.len() { - self.state = ReadDirState::Done; - } else { - self.state = ReadDirState::ProcessEntry { - buf: mem::take(buf), - offset: *offset, - next_read_offset: None, - }; + Err(e) => { + self.done = true; + return Some(Err(e)); + } + }; + + unsafe { + let name = CStr::from_ptr((&raw const (*entry_ptr).d_name).cast()); + let name_bytes = name.to_bytes(); + if name_bytes == b"." || name_bytes == b".." { + continue; } - self.next() + return Some(Ok(DirEntry { + d_type: (*entry_ptr).d_type, + #[cfg(target_env = "p1")] + d_ino: (*entry_ptr).d_ino, + name: name.to_owned(), + inner: Arc::clone(&self.inner), + })); } - ReadDirState::Done => None, } } } impl DirEntry { pub fn path(&self) -> PathBuf { - let name = OsStr::from_bytes(&self.name); - self.inner.root.join(name) + self.inner.root.join(self.file_name_os_str()) } pub fn file_name(&self) -> OsString { - OsString::from_vec(self.name.clone()) + self.file_name_os_str().to_owned() + } + + fn file_name_os_str(&self) -> &OsStr { + OsStr::from_bytes(self.name.to_bytes()) } pub fn metadata(&self) -> io::Result { - metadata_at(&self.inner.dir.fd, 0, OsStr::from_bytes(&self.name).as_ref()) + c::fstatat(self.dir_fd(), &self.name, libc::AT_SYMLINK_NOFOLLOW) } pub fn file_type(&self) -> io::Result { - Ok(FileType { bits: self.meta.d_type }) + match self.d_type { + libc::DT_CHR => Ok(FileType { mode: libc::S_IFCHR }), + libc::DT_LNK => Ok(FileType { mode: libc::S_IFLNK }), + libc::DT_REG => Ok(FileType { mode: libc::S_IFREG }), + libc::DT_DIR => Ok(FileType { mode: libc::S_IFDIR }), + libc::DT_BLK => Ok(FileType { mode: libc::S_IFBLK }), + _ => self.metadata().map(|m| m.file_type()), + } } + #[cfg(target_env = "p1")] pub fn ino(&self) -> wasi::Inode { - self.meta.d_ino + self.d_ino + } + + fn dir_fd(&self) -> BorrowedFd<'_> { + self.inner.dirp.as_fd() } } impl OpenOptions { pub fn new() -> OpenOptions { - let mut base = OpenOptions::default(); - base.dirflags = wasi::LOOKUPFLAGS_SYMLINK_FOLLOW; - base + OpenOptions::default() } pub fn read(&mut self, read: bool) { @@ -311,69 +261,68 @@ impl OpenOptions { } pub fn truncate(&mut self, truncate: bool) { - self.oflag(wasi::OFLAGS_TRUNC, truncate); + self.truncate = truncate; } pub fn create(&mut self, create: bool) { - self.oflag(wasi::OFLAGS_CREAT, create); + self.create = create; } pub fn create_new(&mut self, create_new: bool) { - self.oflag(wasi::OFLAGS_EXCL, create_new); - self.oflag(wasi::OFLAGS_CREAT, create_new); - } - - pub fn directory(&mut self, directory: bool) { - self.oflag(wasi::OFLAGS_DIRECTORY, directory); - } - - fn oflag(&mut self, bit: wasi::Oflags, set: bool) { - if set { - self.oflags |= bit; - } else { - self.oflags &= !bit; - } + self.create_new = create_new; } pub fn append(&mut self, append: bool) { self.append = append; - self.fdflag(wasi::FDFLAGS_APPEND, append); + #[cfg(target_env = "p1")] + self.wasip1_fdflag(wasi::FDFLAGS_APPEND, append); } - pub fn dsync(&mut self, set: bool) { - self.fdflag(wasi::FDFLAGS_DSYNC, set); + #[cfg(target_env = "p1")] + pub fn wasip1_dsync(&mut self, set: bool) { + self.wasip1_fdflag(wasi::FDFLAGS_DSYNC, set); } - pub fn nonblock(&mut self, set: bool) { - self.fdflag(wasi::FDFLAGS_NONBLOCK, set); + #[cfg(target_env = "p1")] + pub fn wasip1_nonblock(&mut self, set: bool) { + self.wasip1_fdflag(wasi::FDFLAGS_NONBLOCK, set); } - pub fn rsync(&mut self, set: bool) { - self.fdflag(wasi::FDFLAGS_RSYNC, set); + #[cfg(target_env = "p1")] + pub fn wasip1_rsync(&mut self, set: bool) { + self.wasip1_fdflag(wasi::FDFLAGS_RSYNC, set); } - pub fn sync(&mut self, set: bool) { - self.fdflag(wasi::FDFLAGS_SYNC, set); + #[cfg(target_env = "p1")] + pub fn wasip1_sync(&mut self, set: bool) { + self.wasip1_fdflag(wasi::FDFLAGS_SYNC, set); } - fn fdflag(&mut self, bit: wasi::Fdflags, set: bool) { + #[cfg(target_env = "p1")] + fn wasip1_fdflag(&mut self, bit: wasi::Fdflags, set: bool) { + self.use_wasip1 = true; if set { - self.fdflags |= bit; + self.wasip1_fdflags |= bit; } else { - self.fdflags &= !bit; + self.wasip1_fdflags &= !bit; } } - pub fn fs_rights_base(&mut self, rights: wasi::Rights) { - self.rights_base = Some(rights); + #[cfg(target_env = "p1")] + pub fn wasip1_fs_rights_base(&mut self, rights: wasi::Rights) { + self.use_wasip1 = true; + self.wasip1_rights_base = Some(rights); } - pub fn fs_rights_inheriting(&mut self, rights: wasi::Rights) { - self.rights_inheriting = Some(rights); + #[cfg(target_env = "p1")] + pub fn wasip1_fs_rights_inheriting(&mut self, rights: wasi::Rights) { + self.use_wasip1 = true; + self.wasip1_rights_inheriting = Some(rights); } - fn rights_base(&self) -> wasi::Rights { - if let Some(rights) = self.rights_base { + #[cfg(target_env = "p1")] + fn wasip1_rights_base(&self) -> wasi::Rights { + if let Some(rights) = self.wasip1_rights_base { return rights; } @@ -419,39 +368,150 @@ impl OpenOptions { base } - fn rights_inheriting(&self) -> wasi::Rights { - self.rights_inheriting.unwrap_or_else(|| self.rights_base()) + #[cfg(target_env = "p1")] + fn wasip1_rights_inheriting(&self) -> wasi::Rights { + self.wasip1_rights_inheriting.unwrap_or_else(|| self.wasip1_rights_base()) } - pub fn lookup_flags(&mut self, flags: wasi::Lookupflags) { - self.dirflags = flags; + #[cfg(target_env = "p1")] + pub fn wasip1_lookup_flags(&mut self, flags: wasi::Lookupflags) { + self.use_wasip1 = true; + self.wasip1_dirflags = Some(flags); + } + + #[cfg(target_env = "p1")] + pub fn wasip1_directory(&mut self, enable: bool) { + if enable { + self.custom_flags |= libc::O_DIRECTORY; + } else { + self.custom_flags &= !libc::O_DIRECTORY; + } + self.use_wasip1 = true; + } + + pub fn custom_flags(&mut self, flags: libc::c_int) { + self.custom_flags = flags; + } + + fn open_flags(&self) -> io::Result { + Ok(self.get_access_mode()? | self.get_creation_mode()? | self.custom_flags) + } + + fn get_access_mode(&self) -> io::Result { + match (self.read, self.write, self.append) { + (true, false, false) => Ok(libc::O_RDONLY), + (false, true, false) => Ok(libc::O_WRONLY), + (true, true, false) => Ok(libc::O_RDWR), + (false, _, true) => Ok(libc::O_WRONLY | libc::O_APPEND), + (true, _, true) => Ok(libc::O_RDWR | libc::O_APPEND), + (false, false, false) => { + // If no access mode is set, check if any creation flags are set + // to provide a more descriptive error message + if self.create || self.create_new || self.truncate { + Err(io::Error::new( + io::ErrorKind::InvalidInput, + "creating or truncating a file requires write or append access", + )) + } else { + Err(io::Error::new( + io::ErrorKind::InvalidInput, + "must specify at least one of read, write, or append access", + )) + } + } + } + } + + fn get_creation_mode(&self) -> io::Result { + match (self.write, self.append) { + (true, false) => {} + (false, false) => { + if self.truncate || self.create || self.create_new { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "creating or truncating a file requires write or append access", + )); + } + } + (_, true) => { + if self.truncate && !self.create_new { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "creating or truncating a file requires write or append access", + )); + } + } + } + + Ok(match (self.create, self.truncate, self.create_new) { + (false, false, false) => 0, + (true, false, false) => libc::O_CREAT, + (false, true, false) => libc::O_TRUNC, + (true, true, false) => libc::O_CREAT | libc::O_TRUNC, + (_, _, true) => libc::O_CREAT | libc::O_EXCL, + }) + } + + fn open_mode(&self) -> libc::c_int { + 0o666 + } + + #[cfg(target_env = "p1")] + pub fn wasip1_oflags(&self) -> wasi::Oflags { + let mut flags = 0; + if self.create { + flags |= wasi::OFLAGS_CREAT; + } + if self.create_new { + flags |= wasi::OFLAGS_CREAT | wasi::OFLAGS_EXCL; + } + if self.truncate { + flags |= wasi::OFLAGS_TRUNC; + } + if self.custom_flags == libc::O_DIRECTORY { + flags |= wasi::OFLAGS_DIRECTORY; + } + flags } } impl File { pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { let (dir, file) = open_parent(path)?; - open_at(&dir, &file, opts) - } - - pub fn open_at(&self, path: &Path, opts: &OpenOptions) -> io::Result { - open_at(&self.fd, path, opts) + run_path_with_cstr(&file, &|file| File::open_c(dir.as_fd(), file, opts)) + } + + pub fn open_c(fd: BorrowedFd<'_>, path: &CStr, opts: &OpenOptions) -> io::Result { + #[cfg(target_env = "p1")] + if opts.use_wasip1 { + let fd = unsafe { + let fd = wasi::path_open( + fd.as_raw_fd() as wasi::Fd, + opts.wasip1_dirflags.unwrap_or(wasi::LOOKUPFLAGS_SYMLINK_FOLLOW), + path.to_str().map_err(|_| io::ErrorKind::InvalidInput)?, + opts.wasip1_oflags(), + opts.wasip1_rights_base(), + opts.wasip1_rights_inheriting(), + opts.wasip1_fdflags, + ) + .map_err(crate::sys::err2io)?; + WasiFd::from_raw_fd(fd as libc::c_int) + }; + return Ok(File { fd }); + } + c::openat(fd, path, opts.open_flags()?, Some(opts.open_mode())) } pub fn file_attr(&self) -> io::Result { - self.fd.filestat_get().map(|meta| FileAttr { meta }) - } - - pub fn metadata_at(&self, flags: wasi::Lookupflags, path: &Path) -> io::Result { - metadata_at(&self.fd, flags, path) + c::fstat(self.as_fd()) } pub fn fsync(&self) -> io::Result<()> { - self.fd.sync() + c::fsync(self.as_fd()) } pub fn datasync(&self) -> io::Result<()> { - self.fd.datasync() + c::fdatasync(self.as_fd()) } pub fn lock(&self) -> io::Result<()> { @@ -475,7 +535,8 @@ impl File { } pub fn truncate(&self, size: u64) -> io::Result<()> { - self.fd.filestat_set_size(size) + let size = size.try_into().map_err(|_| io::ErrorKind::InvalidInput)?; + c::ftruncate(self.as_fd(), size) } pub fn read(&self, buf: &mut [u8]) -> io::Result { @@ -513,7 +574,14 @@ impl File { } pub fn seek(&self, pos: SeekFrom) -> io::Result { - self.fd.seek(pos) + let (whence, pos) = match pos { + // Casting to `i64` is fine, too large values will end up as + // negative which will cause an error in `lseek64`. + SeekFrom::Start(off) => (libc::SEEK_SET, off as i64), + SeekFrom::End(off) => (libc::SEEK_END, off), + SeekFrom::Current(off) => (libc::SEEK_CUR, off), + }; + c::lseek(self.as_fd(), pos, whence) } pub fn size(&self) -> Option> { @@ -521,7 +589,7 @@ impl File { } pub fn tell(&self) -> io::Result { - self.fd.tell() + self.seek(SeekFrom::Current(0)) } pub fn duplicate(&self) -> io::Result { @@ -536,24 +604,27 @@ impl File { } pub fn set_times(&self, times: FileTimes) -> io::Result<()> { - let to_timestamp = |time: Option| match time { - Some(time) if let Some(ts) = time.to_wasi_timestamp() => Ok(ts), - Some(_) => Err(io::const_error!( - io::ErrorKind::InvalidInput, - "timestamp is too large to set as a file time", - )), - None => Ok(0), - }; - self.fd.filestat_set_times( - to_timestamp(times.accessed)?, - to_timestamp(times.modified)?, - times.accessed.map_or(0, |_| wasi::FSTFLAGS_ATIM) - | times.modified.map_or(0, |_| wasi::FSTFLAGS_MTIM), - ) + let omit = libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT }; + let times = [ + times.accessed.map(|t| t.to_timespec()).transpose()?.unwrap_or(omit), + times.modified.map(|t| t.to_timespec()).transpose()?.unwrap_or(omit), + ]; + c::futimens(self.as_fd(), ×) + } + + #[cfg(target_env = "p1")] + pub fn metadata_at(&self, flags: i32, path: &Path) -> io::Result { + run_path_with_cstr(path, &|path| c::fstatat(self.as_fd(), path, flags)) } - pub fn read_link(&self, file: &Path) -> io::Result { - read_link(&self.fd, file) + #[cfg(target_env = "p1")] + pub fn readlink_at(&self, path: &Path) -> io::Result { + run_path_with_cstr(path, &|path| read_link(self.as_fd(), path)) + } + + #[cfg(target_env = "p1")] + pub fn open_at(&self, path: &Path, opts: &OpenOptions) -> io::Result { + run_path_with_cstr(path, &|path| File::open_c(self.as_fd(), path, opts)) } } @@ -607,8 +678,8 @@ impl DirBuilder { } pub fn mkdir(&self, p: &Path) -> io::Result<()> { - let (dir, file) = open_parent(p)?; - dir.create_directory(osstr2str(file.as_ref())?) + let (dir, path) = open_parent(p)?; + run_path_with_cstr(&path, &|path| c::mkdirat(dir.as_fd(), path)) } } @@ -620,21 +691,26 @@ impl fmt::Debug for File { pub fn readdir(p: &Path) -> io::Result { let mut opts = OpenOptions::new(); - opts.directory(true); + opts.custom_flags(libc::O_DIRECTORY); opts.read(true); let dir = File::open(p, &opts)?; - Ok(ReadDir::new(dir, p.to_path_buf())) + ReadDir::new(dir, p.to_path_buf()) } pub fn unlink(p: &Path) -> io::Result<()> { let (dir, file) = open_parent(p)?; - dir.unlink_file(osstr2str(file.as_ref())?) + run_path_with_cstr(&file, &|file| c::unlinkat(dir.as_fd(), file, 0)) } pub fn rename(old: &Path, new: &Path) -> io::Result<()> { let (old, old_file) = open_parent(old)?; let (new, new_file) = open_parent(new)?; - old.rename(osstr2str(old_file.as_ref())?, &new, osstr2str(new_file.as_ref())?) + run_path_with_cstr(&old_file, &|old_file| { + run_path_with_cstr(&new_file, &|new_file| { + c::renameat(old.as_fd(), old_file, new.as_fd(), new_file) + }) + })?; + Ok(()) } pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { @@ -644,23 +720,23 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { } pub fn rmdir(p: &Path) -> io::Result<()> { - let (dir, file) = open_parent(p)?; - dir.remove_directory(osstr2str(file.as_ref())?) + let (dir, path) = open_parent(p)?; + run_path_with_cstr(&path, &|path| c::unlinkat(dir.as_fd(), path, libc::AT_REMOVEDIR)) } pub fn readlink(p: &Path) -> io::Result { let (dir, file) = open_parent(p)?; - read_link(&dir, &file) + run_path_with_cstr(&file, &|file| read_link(dir.as_fd(), file)) } -fn read_link(fd: &WasiFd, file: &Path) -> io::Result { +fn read_link(fd: BorrowedFd<'_>, path: &CStr) -> io::Result { // Try to get a best effort initial capacity for the vector we're going to // fill. Note that if it's not a symlink we don't use a file to avoid // allocating gigabytes if you read_link a huge movie file by accident. // Additionally we add 1 to the initial size so if it doesn't change until // when we call `readlink` the returned length will be less than the // capacity, guaranteeing that we got all the data. - let meta = metadata_at(fd, 0, file)?; + let meta = c::fstatat(fd, path, libc::AT_SYMLINK_NOFOLLOW)?; let initial_size = if meta.file_type().is_symlink() { (meta.size() as usize).saturating_add(1) } else { @@ -670,10 +746,9 @@ fn read_link(fd: &WasiFd, file: &Path) -> io::Result { // Now that we have an initial guess of how big to make our buffer, call // `readlink` in a loop until it fails or reports it filled fewer bytes than // we asked for, indicating we got everything. - let file = osstr2str(file.as_ref())?; let mut destination = vec![0u8; initial_size]; loop { - let len = fd.readlink(file, &mut destination)?; + let len = c::readlinkat(fd, path, &mut destination)?; if len < destination.len() { destination.truncate(len); destination.shrink_to_fit(); @@ -686,29 +761,40 @@ fn read_link(fd: &WasiFd, file: &Path) -> io::Result { pub fn symlink(original: &Path, link: &Path) -> io::Result<()> { let (link, link_file) = open_parent(link)?; - link.symlink(osstr2str(original.as_ref())?, osstr2str(link_file.as_ref())?) + run_path_with_cstr(&original, &|original| { + run_path_with_cstr(&link_file, &|link_file| c::symlinkat(original, link.as_fd(), link_file)) + })?; + Ok(()) } pub fn link(original: &Path, link: &Path) -> io::Result<()> { let (original, original_file) = open_parent(original)?; let (link, link_file) = open_parent(link)?; - // Pass 0 as the flags argument, meaning don't follow symlinks. - original.link(0, osstr2str(original_file.as_ref())?, &link, osstr2str(link_file.as_ref())?) + + run_path_with_cstr(&original_file, &|original_file| { + run_path_with_cstr(&link_file, &|link_file| { + c::linkat( + original.as_fd(), + original_file, + link.as_fd(), + link_file, + // Pass 0 as the flags argument, meaning don't follow + // symlinks. + 0, + ) + }) + })?; + Ok(()) } pub fn stat(p: &Path) -> io::Result { let (dir, file) = open_parent(p)?; - metadata_at(&dir, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, &file) + run_path_with_cstr(&file, &|file| c::fstatat(dir.as_fd(), file, libc::AT_SYMLINK_FOLLOW)) } pub fn lstat(p: &Path) -> io::Result { let (dir, file) = open_parent(p)?; - metadata_at(&dir, 0, &file) -} - -fn metadata_at(fd: &WasiFd, flags: wasi::Lookupflags, path: &Path) -> io::Result { - let meta = fd.path_filestat_get(flags, osstr2str(path.as_ref())?)?; - Ok(FileAttr { meta }) + run_path_with_cstr(&file, &|file| c::fstatat(dir.as_fd(), file, libc::AT_SYMLINK_NOFOLLOW)) } pub fn canonicalize(_p: &Path) -> io::Result { @@ -717,18 +803,6 @@ pub fn canonicalize(_p: &Path) -> io::Result { unsupported() } -fn open_at(fd: &WasiFd, path: &Path, opts: &OpenOptions) -> io::Result { - let fd = fd.open( - opts.dirflags, - osstr2str(path.as_ref())?, - opts.oflags, - opts.rights_base(), - opts.rights_inheriting(), - opts.fdflags, - )?; - Ok(File { fd }) -} - /// Attempts to open a bare path `p`. /// /// WASI has no fundamental capability to do this. All syscalls and operations @@ -761,9 +835,9 @@ fn open_parent(p: &Path) -> io::Result<(ManuallyDrop, PathBuf)> { let mut buf = Vec::::with_capacity(512); loop { unsafe { - let mut relative_path = buf.as_ptr().cast(); + let mut relative_path = buf.as_mut_ptr().cast(); let mut abs_prefix = ptr::null(); - let fd = __wasilibc_find_relpath( + let fd = libc::__wasilibc_find_relpath( p.as_ptr(), &mut abs_prefix, &mut relative_path, @@ -792,22 +866,9 @@ fn open_parent(p: &Path) -> io::Result<(ManuallyDrop, PathBuf)> { )); } } - - unsafe extern "C" { - pub fn __wasilibc_find_relpath( - path: *const libc::c_char, - abs_prefix: *mut *const libc::c_char, - relative_path: *mut *const libc::c_char, - relative_path_len: libc::size_t, - ) -> libc::c_int; - } }) } -pub fn osstr2str(f: &OsStr) -> io::Result<&str> { - f.to_str().ok_or_else(|| io::const_error!(io::ErrorKind::Uncategorized, "input must be utf-8")) -} - pub fn copy(from: &Path, to: &Path) -> io::Result { use crate::fs::File; @@ -819,10 +880,11 @@ pub fn copy(from: &Path, to: &Path) -> io::Result { pub fn remove_dir_all(path: &Path) -> io::Result<()> { let (parent, path) = open_parent(path)?; - remove_dir_all_recursive(&parent, &path) + + run_path_with_cstr(&path, &|path| remove_dir_all_recursive(parent.as_fd(), path)) } -fn remove_dir_all_recursive(parent: &WasiFd, path: &Path) -> io::Result<()> { +fn remove_dir_all_recursive(parent: BorrowedFd<'_>, path: &CStr) -> io::Result<()> { // Open up a file descriptor for the directory itself. Note that we don't // follow symlinks here and we specifically open directories. // @@ -833,13 +895,9 @@ fn remove_dir_all_recursive(parent: &WasiFd, path: &Path) -> io::Result<()> { // // If the opened file was actually a symlink then the symlink is deleted, // not the directory recursively. - let mut opts = OpenOptions::new(); - opts.lookup_flags(0); - opts.directory(true); - opts.read(true); - let fd = open_at(parent, path, &opts)?; + let fd = c::openat(parent, path, libc::O_RDONLY | libc::O_DIRECTORY | libc::O_NOFOLLOW, None)?; if fd.file_attr()?.file_type().is_symlink() { - return parent.unlink_file(osstr2str(path.as_ref())?); + return c::unlinkat(parent.as_fd(), path, 0); } // this "root" is only used by `DirEntry::path` which we don't use below so @@ -855,17 +913,13 @@ fn remove_dir_all_recursive(parent: &WasiFd, path: &Path) -> io::Result<()> { // invocations of reading a directory. By reading all the entries at once // this ensures that, at least without concurrent modifications, it should // be possible to delete everything. - for entry in ReadDir::new(fd, dummy_root).collect::>() { + for entry in ReadDir::new(fd, dummy_root)?.collect::>() { let entry = entry?; - let path = crate::str::from_utf8(&entry.name).map_err(|_| { - io::const_error!(io::ErrorKind::Uncategorized, "invalid utf-8 file name found") - })?; - let result: io::Result<()> = try { if entry.file_type()?.is_dir() { - remove_dir_all_recursive(&entry.inner.dir.fd, path.as_ref())?; + remove_dir_all_recursive(entry.dir_fd(), &entry.name)?; } else { - entry.inner.dir.fd.unlink_file(path)?; + c::unlinkat(entry.dir_fd(), &entry.name, 0)?; } }; // ignore internal NotFound errors @@ -878,5 +932,176 @@ fn remove_dir_all_recursive(parent: &WasiFd, path: &Path) -> io::Result<()> { // Once all this directory's contents are deleted it should be safe to // delete the directory tiself. - ignore_notfound(parent.remove_directory(osstr2str(path.as_ref())?)) + ignore_notfound(c::unlinkat(parent, path, libc::AT_REMOVEDIR)) +} + +mod c { + use super::{File, FileAttr}; + use crate::ffi::CStr; + use crate::io; + use crate::mem::MaybeUninit; + use crate::os::fd::{FromRawFd, IntoRawFd}; + use crate::os::wasi::io::{AsRawFd, BorrowedFd}; + use crate::sys::os::{cvt, errno}; + + pub fn ftruncate(fd: BorrowedFd<'_>, size: libc::off_t) -> io::Result<()> { + unsafe { + cvt(libc::ftruncate(fd.as_raw_fd(), size))?; + } + Ok(()) + } + + pub fn linkat( + oldfd: BorrowedFd<'_>, + oldpath: &CStr, + newfd: BorrowedFd<'_>, + newpath: &CStr, + flags: libc::c_int, + ) -> io::Result<()> { + cvt(unsafe { + libc::linkat( + oldfd.as_raw_fd(), + oldpath.as_ptr(), + newfd.as_raw_fd(), + newpath.as_ptr(), + flags, + ) + })?; + Ok(()) + } + + pub fn symlinkat(original: &CStr, newfd: BorrowedFd<'_>, newpath: &CStr) -> io::Result<()> { + cvt(unsafe { libc::symlinkat(original.as_ptr(), newfd.as_raw_fd(), newpath.as_ptr()) })?; + Ok(()) + } + + pub fn renameat( + oldfd: BorrowedFd<'_>, + oldpath: &CStr, + newfd: BorrowedFd<'_>, + newpath: &CStr, + ) -> io::Result<()> { + cvt(unsafe { + libc::renameat(oldfd.as_raw_fd(), oldpath.as_ptr(), newfd.as_raw_fd(), newpath.as_ptr()) + })?; + Ok(()) + } + + pub fn mkdirat(fd: BorrowedFd<'_>, path: &CStr) -> io::Result<()> { + cvt(unsafe { libc::mkdirat(fd.as_raw_fd(), path.as_ptr(), 0o777) })?; + Ok(()) + } + + pub fn futimens(fd: BorrowedFd<'_>, times: &[libc::timespec; 2]) -> io::Result<()> { + unsafe { + cvt(libc::futimens(fd.as_raw_fd(), times.as_ptr()))?; + } + Ok(()) + } + + pub fn lseek(fd: BorrowedFd<'_>, pos: i64, whence: libc::c_int) -> io::Result { + let n = cvt(unsafe { libc::lseek(fd.as_raw_fd(), pos, whence) })?; + Ok(n as u64) + } + + pub fn fsync(fd: BorrowedFd<'_>) -> io::Result<()> { + unsafe { + cvt(libc::fsync(fd.as_raw_fd()))?; + } + Ok(()) + } + + pub fn fdatasync(fd: BorrowedFd<'_>) -> io::Result<()> { + unsafe { + cvt(libc::fdatasync(fd.as_raw_fd()))?; + } + Ok(()) + } + + pub fn fstat(fd: BorrowedFd<'_>) -> io::Result { + let mut stat = MaybeUninit::uninit(); + unsafe { + cvt(libc::fstat(fd.as_raw_fd(), stat.as_mut_ptr()))?; + Ok(FileAttr { stat: stat.assume_init() }) + } + } + + pub fn fstatat(fd: BorrowedFd<'_>, path: &CStr, flags: libc::c_int) -> io::Result { + let mut stat = MaybeUninit::uninit(); + unsafe { + cvt(libc::fstatat(fd.as_raw_fd(), path.as_ptr(), stat.as_mut_ptr(), flags))?; + Ok(FileAttr { stat: stat.assume_init() }) + } + } + + pub fn readlinkat(fd: BorrowedFd<'_>, path: &CStr, buf: &mut [u8]) -> io::Result { + let len = cvt(unsafe { + libc::readlinkat(fd.as_raw_fd(), path.as_ptr(), buf.as_mut_ptr().cast(), buf.len()) + })?; + Ok(len as usize) + } + + pub fn unlinkat(fd: BorrowedFd<'_>, path: &CStr, flags: libc::c_int) -> io::Result<()> { + cvt(unsafe { libc::unlinkat(fd.as_raw_fd(), path.as_ptr(), flags) })?; + Ok(()) + } + + pub fn openat( + fd: BorrowedFd<'_>, + path: &CStr, + flags: libc::c_int, + mode: Option, + ) -> io::Result { + unsafe { + let fd = match mode { + Some(mode) => cvt(libc::openat(fd.as_raw_fd(), path.as_ptr(), flags, mode))?, + None => cvt(libc::openat(fd.as_raw_fd(), path.as_ptr(), flags))?, + }; + Ok(File::from_raw_fd(fd)) + } + } + + pub struct Dirp { + ptr: *mut libc::DIR, + } + + impl Dirp { + pub fn new(dir: File) -> io::Result { + unsafe { + let ptr = libc::fdopendir(dir.as_raw_fd()); + if ptr.is_null() { + return Err(io::Error::last_os_error()); + } + let _ = dir.into_raw_fd(); // `ptr` now owns the fd + Ok(Dirp { ptr }) + } + } + + pub fn readdir(&self) -> io::Result> { + unsafe { + let entry = libc::readdir(self.ptr); + if entry.is_null() { + let e = errno(); + if e == 0 { Ok(None) } else { Err(io::Error::from_raw_os_error(e)) } + } else { + Ok(Some(entry)) + } + } + } + + pub fn as_fd(&self) -> BorrowedFd<'_> { + unsafe { BorrowedFd::borrow_raw(libc::dirfd(self.ptr)) } + } + } + + unsafe impl Send for Dirp {} + unsafe impl Sync for Dirp {} + + impl Drop for Dirp { + fn drop(&mut self) { + unsafe { + libc::closedir(self.ptr); + } + } + } } diff --git a/library/std/src/sys/io/io_slice/wasi.rs b/library/std/src/sys/io/io_slice/wasi.rs index 87acbbd924e56..0887682133b7a 100644 --- a/library/std/src/sys/io/io_slice/wasi.rs +++ b/library/std/src/sys/io/io_slice/wasi.rs @@ -1,40 +1,68 @@ use crate::marker::PhantomData; -use crate::slice; +use crate::{mem, slice}; #[derive(Copy, Clone)] #[repr(transparent)] pub struct IoSlice<'a> { - vec: wasi::Ciovec, + vec: libc::iovec, _p: PhantomData<&'a [u8]>, } impl<'a> IoSlice<'a> { #[inline] pub fn new(buf: &'a [u8]) -> IoSlice<'a> { - IoSlice { vec: wasi::Ciovec { buf: buf.as_ptr(), buf_len: buf.len() }, _p: PhantomData } + IoSlice { + vec: libc::iovec { iov_base: buf.as_ptr().cast_mut().cast(), iov_len: buf.len() }, + _p: PhantomData, + } } #[inline] pub fn advance(&mut self, n: usize) { - if self.vec.buf_len < n { + if self.vec.iov_len < n { panic!("advancing IoSlice beyond its length"); } unsafe { - self.vec.buf_len -= n; - self.vec.buf = self.vec.buf.add(n); + self.vec.iov_len -= n; + self.vec.iov_base = self.vec.iov_base.add(n); } } #[inline] pub const fn as_slice(&self) -> &'a [u8] { - unsafe { slice::from_raw_parts(self.vec.buf as *const u8, self.vec.buf_len) } + unsafe { slice::from_raw_parts(self.vec.iov_base as *const u8, self.vec.iov_len) } + } + + #[cfg(target_env = "p1")] + pub(crate) fn as_wasip1_slice<'b>(a: &'b [crate::io::IoSlice<'_>]) -> &'b [wasi::Ciovec] { + let a = Self::as_libc_slice(a); + + assert_eq!(size_of::(), size_of::()); + assert_eq!(align_of::(), align_of::()); + assert_eq!(mem::offset_of!(wasi::Ciovec, buf), mem::offset_of!(libc::iovec, iov_base)); + assert_eq!(mem::offset_of!(wasi::Ciovec, buf_len), mem::offset_of!(libc::iovec, iov_len)); + + // SAFETY: `wasi::Ciovec` and `libc::iovec` have different definitions + // but have the same layout by definition, so it should be safe to + // transmute between the two. + unsafe { mem::transmute(a) } + } + + pub(crate) fn as_libc_slice<'b>(a: &'b [crate::io::IoSlice<'_>]) -> &'b [libc::iovec] { + assert_eq!(size_of::>(), size_of::()); + assert_eq!(align_of::>(), align_of::()); + + // SAFETY: the `crate::io::IoSlice` type is a `repr(transparent)` + // wrapper around `Self`, and `Self` is a `repr(transparent)` wrapper + // aruond `libc::iovec`, thus an slice of one is a slice of the other. + unsafe { mem::transmute(a) } } } #[repr(transparent)] pub struct IoSliceMut<'a> { - vec: wasi::Iovec, + vec: libc::iovec, _p: PhantomData<&'a mut [u8]>, } @@ -42,35 +70,60 @@ impl<'a> IoSliceMut<'a> { #[inline] pub fn new(buf: &'a mut [u8]) -> IoSliceMut<'a> { IoSliceMut { - vec: wasi::Iovec { buf: buf.as_mut_ptr(), buf_len: buf.len() }, + vec: libc::iovec { iov_base: buf.as_mut_ptr().cast(), iov_len: buf.len() }, _p: PhantomData, } } #[inline] pub fn advance(&mut self, n: usize) { - if self.vec.buf_len < n { + if self.vec.iov_len < n { panic!("advancing IoSlice beyond its length"); } unsafe { - self.vec.buf_len -= n; - self.vec.buf = self.vec.buf.add(n); + self.vec.iov_len -= n; + self.vec.iov_base = self.vec.iov_base.add(n); } } #[inline] pub fn as_slice(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.vec.buf as *const u8, self.vec.buf_len) } + unsafe { slice::from_raw_parts(self.vec.iov_base as *const u8, self.vec.iov_len) } } #[inline] pub const fn into_slice(self) -> &'a mut [u8] { - unsafe { slice::from_raw_parts_mut(self.vec.buf as *mut u8, self.vec.buf_len) } + unsafe { slice::from_raw_parts_mut(self.vec.iov_base as *mut u8, self.vec.iov_len) } } #[inline] pub fn as_mut_slice(&mut self) -> &mut [u8] { - unsafe { slice::from_raw_parts_mut(self.vec.buf as *mut u8, self.vec.buf_len) } + unsafe { slice::from_raw_parts_mut(self.vec.iov_base as *mut u8, self.vec.iov_len) } + } + + #[cfg(target_env = "p1")] + pub(crate) fn as_wasip1_slice<'b>(a: &'b mut [crate::io::IoSliceMut<'_>]) -> &'b [wasi::Iovec] { + let a = Self::as_libc_slice(a); + + assert_eq!(size_of::(), size_of::()); + assert_eq!(align_of::(), align_of::()); + assert_eq!(mem::offset_of!(wasi::Iovec, buf), mem::offset_of!(libc::iovec, iov_base)); + assert_eq!(mem::offset_of!(wasi::Iovec, buf_len), mem::offset_of!(libc::iovec, iov_len)); + + // SAFETY: `wasi::Iovec` and `libc::iovec` have different definitions + // but have the same layout by definition, so it should be safe to + // transmute between the two. + unsafe { mem::transmute(a) } + } + + pub(crate) fn as_libc_slice<'b>(a: &'b mut [crate::io::IoSliceMut<'_>]) -> &'b [libc::iovec] { + assert_eq!(size_of::>(), size_of::()); + assert_eq!(align_of::>(), align_of::()); + + // SAFETY: the `crate::io::IoSliceMut` type is a `repr(transparent)` + // wrapper around `Self`, and `Self` is a `repr(transparent)` wrapper + // aruond `libc::iovec`, thus an slice of one is a slice of the other. + unsafe { mem::transmute(a) } } } diff --git a/library/std/src/sys/pal/wasip1/time.rs b/library/std/src/sys/pal/wasip1/time.rs index 0d8d0b59ac14a..79b953fb2bddb 100644 --- a/library/std/src/sys/pal/wasip1/time.rs +++ b/library/std/src/sys/pal/wasip1/time.rs @@ -1,5 +1,6 @@ #![forbid(unsafe_op_in_unsafe_fn)] +use crate::io; use crate::time::Duration; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] @@ -43,12 +44,23 @@ impl SystemTime { SystemTime(current_time(wasi::CLOCKID_REALTIME)) } - pub fn from_wasi_timestamp(ts: wasi::Timestamp) -> SystemTime { - SystemTime(Duration::from_nanos(ts)) + pub fn from_timespec(ts: libc::timespec) -> SystemTime { + SystemTime(Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32)) } - pub fn to_wasi_timestamp(&self) -> Option { - self.0.as_nanos().try_into().ok() + pub fn to_timespec(&self) -> io::Result { + Ok(libc::timespec { + tv_sec: self + .0 + .as_secs() + .try_into() + .map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?, + tv_nsec: self + .0 + .subsec_nanos() + .try_into() + .map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?, + }) } pub fn sub_time(&self, other: &SystemTime) -> Result { diff --git a/library/std/src/sys/pal/wasip2/helpers.rs b/library/std/src/sys/pal/wasip2/helpers.rs new file mode 100644 index 0000000000000..61f150a998af2 --- /dev/null +++ b/library/std/src/sys/pal/wasip2/helpers.rs @@ -0,0 +1,57 @@ +#![forbid(unsafe_op_in_unsafe_fn)] + +use crate::io as std_io; + +#[inline] +pub fn is_interrupted(errno: i32) -> bool { + errno == libc::EINTR +} + +pub fn decode_error_kind(errno: i32) -> std_io::ErrorKind { + use std_io::ErrorKind::*; + match errno as libc::c_int { + libc::E2BIG => ArgumentListTooLong, + libc::EADDRINUSE => AddrInUse, + libc::EADDRNOTAVAIL => AddrNotAvailable, + libc::EBUSY => ResourceBusy, + libc::ECONNABORTED => ConnectionAborted, + libc::ECONNREFUSED => ConnectionRefused, + libc::ECONNRESET => ConnectionReset, + libc::EDEADLK => Deadlock, + libc::EDQUOT => QuotaExceeded, + libc::EEXIST => AlreadyExists, + libc::EFBIG => FileTooLarge, + libc::EHOSTUNREACH => HostUnreachable, + libc::EINTR => Interrupted, + libc::EINVAL => InvalidInput, + libc::EISDIR => IsADirectory, + libc::ELOOP => FilesystemLoop, + libc::ENOENT => NotFound, + libc::ENOMEM => OutOfMemory, + libc::ENOSPC => StorageFull, + libc::ENOSYS => Unsupported, + libc::EMLINK => TooManyLinks, + libc::ENAMETOOLONG => InvalidFilename, + libc::ENETDOWN => NetworkDown, + libc::ENETUNREACH => NetworkUnreachable, + libc::ENOTCONN => NotConnected, + libc::ENOTDIR => NotADirectory, + libc::EPIPE => BrokenPipe, + libc::EROFS => ReadOnlyFilesystem, + libc::ESPIPE => NotSeekable, + libc::ESTALE => StaleNetworkFileHandle, + libc::ETIMEDOUT => TimedOut, + libc::ETXTBSY => ExecutableFileBusy, + libc::EXDEV => CrossesDevices, + libc::EINPROGRESS => InProgress, + libc::EOPNOTSUPP => Unsupported, + libc::EACCES | libc::EPERM => PermissionDenied, + libc::EWOULDBLOCK => WouldBlock, + + _ => Uncategorized, + } +} + +pub fn abort_internal() -> ! { + unsafe { libc::abort() } +} diff --git a/library/std/src/sys/pal/wasip2/mod.rs b/library/std/src/sys/pal/wasip2/mod.rs index c1d89da2677c9..b321806564eaa 100644 --- a/library/std/src/sys/pal/wasip2/mod.rs +++ b/library/std/src/sys/pal/wasip2/mod.rs @@ -23,13 +23,12 @@ mod common; pub use common::*; -#[path = "../wasip1/helpers.rs"] mod helpers; // The following exports are listed individually to work around Rust's glob // import conflict rules. If we glob export `helpers` and `common` together, // then the compiler complains about conflicts. -pub(crate) use helpers::{abort_internal, decode_error_kind, err2io, is_interrupted}; +pub(crate) use helpers::{abort_internal, decode_error_kind, is_interrupted}; mod cabi_realloc; diff --git a/library/std/src/sys/pal/wasip2/time.rs b/library/std/src/sys/pal/wasip2/time.rs index 434891839944d..cbe1be82d1204 100644 --- a/library/std/src/sys/pal/wasip2/time.rs +++ b/library/std/src/sys/pal/wasip2/time.rs @@ -1,3 +1,4 @@ +use crate::io; use crate::time::Duration; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] @@ -36,12 +37,23 @@ impl SystemTime { SystemTime(Duration::new(now.seconds, now.nanoseconds)) } - pub fn from_wasi_timestamp(ts: wasi::Timestamp) -> SystemTime { - SystemTime(Duration::from_nanos(ts)) + pub fn from_timespec(ts: libc::timespec) -> SystemTime { + SystemTime(Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32)) } - pub fn to_wasi_timestamp(&self) -> Option { - self.0.as_nanos().try_into().ok() + pub fn to_timespec(&self) -> io::Result { + Ok(libc::timespec { + tv_sec: self + .0 + .as_secs() + .try_into() + .map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?, + tv_nsec: self + .0 + .subsec_nanos() + .try_into() + .map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?, + }) } pub fn sub_time(&self, other: &SystemTime) -> Result { diff --git a/tests/rustdoc-js-std/unbox-type-result.js b/tests/rustdoc-js-std/unbox-type-result.js index 1f5cba58adf6d..e4765d87637c3 100644 --- a/tests/rustdoc-js-std/unbox-type-result.js +++ b/tests/rustdoc-js-std/unbox-type-result.js @@ -8,7 +8,6 @@ const EXPECTED = [ query: "File -> Metadata", others: [ { path: "std::fs::File", name: "metadata" }, - { path: "std::fs::File", name: "metadata_at" }, ] }, {