From a35ea0c8c37134e6b700b1da1ab2526ec8b4a6d3 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 12 Jul 2026 19:40:27 -0700 Subject: [PATCH 1/2] Reduce dependencies on `cap-std`-family crates This commit is an attempt to reduce the number of layers of abstraction that we have for the `wasmtime-wasi` crate and implementation, in particular around the dependencies that we have. Historically `wasmtime-wasi` has primarily depended on `cap-std` which provides a `std`-like API for using the native filesystem and other features (e.g. networking). Over time networking has largely moved away from `cap-std`, however. I've additionally historically found it difficult to trace through the tree of dependencies for figuring out what a WASI call does. This requires following code in `wasmtime-wasi`, to `cap-std`, to `cap-*-ext` sometimes, to `cap-primitives`, then to `rustix`. The goal of this commit is to shorten this chain and instead primarily rely on the "meat" of `cap-std`, the `cap-primitives` crate. The `cap-std` crate additionally serves a bit of a dual purpose of providing filesystem sandboxing and abstracting the underlying platform. This abstraction though is a bit leaky where the API is different enough across platforms that it can come at a performance cost on some platforms while also still needing `#[cfg]`s on others. This commit additionally is intended to reduce the reliance on this portability layer since the API that we're implementing, WASI, is the definition of what we want portability-wise which doesn't always match what `cap-std` does. Overall this commit removes the `cap-std` and `io-lifetimes` dependencies from the Wasmtime workspace itself. A dependency is left on `cap-fs-ext` out of necessity but it's only to access an otherwise-private API located in the `cap-primitives` crate. Implementations throughout `wasmtime-wasi` have shifted to using their `std` counterparts (e.g. clocks), encapsulating more platform-specific logic (e.g. unix-vs-windows filesystem behavior), and refactoring to avoid dependencies (e.g. networking). This commit is not intended to introduce any new behavior nor change any preexisting behavior. The goal of this commit is to reduce the dependency tree of Wasmtime to the bare minimum necessary through the `cap-primitives` crate. --- Cargo.lock | 6 +- Cargo.toml | 8 +- crates/bench-api/Cargo.toml | 1 - crates/c-api/Cargo.toml | 3 +- crates/wasi-nn/Cargo.toml | 1 - crates/wasi/Cargo.toml | 5 +- crates/wasi/src/clocks.rs | 44 +-- crates/wasi/src/ctx.rs | 4 +- crates/wasi/src/filesystem.rs | 286 +++++++++---------- crates/wasi/src/filesystem/unix.rs | 85 +++++- crates/wasi/src/filesystem/windows.rs | 170 ++++++++++- crates/wasi/src/lib.rs | 8 +- crates/wasi/src/p1.rs | 2 +- crates/wasi/src/p2/filesystem.rs | 4 +- crates/wasi/src/p2/host/clocks.rs | 2 +- crates/wasi/src/p2/host/filesystem.rs | 20 +- crates/wasi/src/p2/mod.rs | 4 +- crates/wasi/src/p2/tcp.rs | 5 +- crates/wasi/src/p3/filesystem/host.rs | 12 +- crates/wasi/src/p3/filesystem/mod.rs | 19 +- crates/wasi/src/p3/sockets/host/types/tcp.rs | 11 +- crates/wasi/src/sockets/tcp.rs | 53 ++-- crates/wasi/src/sockets/udp.rs | 16 +- crates/wasi/src/sockets/util.rs | 11 + 24 files changed, 467 insertions(+), 313 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d94a0f81d174..e503536b76d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4625,7 +4625,6 @@ dependencies = [ name = "wasmtime-bench-api" version = "48.0.0" dependencies = [ - "cap-std", "clap", "shuffling-allocator", "target-lexicon", @@ -4649,7 +4648,6 @@ version = "48.0.0" dependencies = [ "async-trait", "bytes", - "cap-std", "env_logger 0.11.5", "futures", "log", @@ -5133,11 +5131,10 @@ dependencies = [ "bitflags 2.11.1", "bytes", "cap-fs-ext", - "cap-std", + "cap-primitives", "cfg-if", "env_logger 0.11.5", "futures", - "io-lifetimes 3.0.1", "rand 0.10.1", "rustix 1.1.4", "tempfile", @@ -5223,7 +5220,6 @@ dependencies = [ name = "wasmtime-wasi-nn" version = "48.0.0" dependencies = [ - "cap-std", "libtest-mimic", "openvino", "ort", diff --git a/Cargo.toml b/Cargo.toml index bc68721d5a49..9fec9b6e5194 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -348,10 +348,12 @@ regalloc2 = "0.15.1" wasip1 = { version = "1.0.0", default-features = false } # cap-std family: +# +# Note that `cap-fs-ext` should be avoided where possible to use +# `cap-primitives` instead. target-lexicon = "0.13.5" -cap-std = "4.0.2" -cap-fs-ext = "4.0.2" -io-lifetimes = { version = "3.0.1", default-features = false } +cap-primitives = "4.0.2" +cap-fs-ext-avoid-using-this = { version = "4.0.2", package = 'cap-fs-ext' } rustix = "1.1.4" # wit-bindgen: wit-bindgen = { version = "0.59.0", default-features = false } diff --git a/crates/bench-api/Cargo.toml b/crates/bench-api/Cargo.toml index 2509958c2d55..7256e895a345 100644 --- a/crates/bench-api/Cargo.toml +++ b/crates/bench-api/Cargo.toml @@ -29,7 +29,6 @@ wasmtime-cli-flags = { workspace = true, default-features = true, features = [ ] } wasmtime-wasi = { workspace = true, features = ['p1', 'p2'] } wasmtime-wasi-nn = { workspace = true, optional = true } -cap-std = { workspace = true } clap = { workspace = true } [dev-dependencies] diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml index 221537f662c7..5cbcae521d67 100644 --- a/crates/c-api/Cargo.toml +++ b/crates/c-api/Cargo.toml @@ -30,7 +30,6 @@ tracing = { workspace = true } wat = { workspace = true, optional = true } # Optional dependencies for the `wasi` feature -cap-std = { workspace = true, optional = true } tokio = { workspace = true, optional = true, features = ["fs"] } wasmtime-wasi = { workspace = true, optional = true, features = ["p1"] } wasmtime-wasi-http = { workspace = true, optional = true, features = ["p2", "default-send-request"] } @@ -47,7 +46,7 @@ async = ['wasmtime/async', 'futures'] profiling = ["wasmtime/profiling"] cache = ["wasmtime/cache"] parallel-compilation = ['wasmtime/parallel-compilation'] -wasi = ['cap-std', 'wasmtime-wasi', 'wasmtime-wasi-io', 'tokio', 'async-trait', 'bytes'] +wasi = ['wasmtime-wasi', 'wasmtime-wasi-io', 'tokio', 'async-trait', 'bytes'] wasi-http = ['wasi', 'wasmtime-wasi-http'] logging = ['dep:env_logger'] disable-logging = ["log/max_level_off", "tracing/max_level_off"] diff --git a/crates/wasi-nn/Cargo.toml b/crates/wasi-nn/Cargo.toml index 724ea330a996..17b397dc7c57 100644 --- a/crates/wasi-nn/Cargo.toml +++ b/crates/wasi-nn/Cargo.toml @@ -55,7 +55,6 @@ optional = true walkdir = { workspace = true } [dev-dependencies] -cap-std = { workspace = true } libtest-mimic = { workspace = true } test-programs-artifacts = { workspace = true } wasmtime-wasi = { workspace = true, features = ["p1"] } diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index 819a9f5db38a..f76fc4fd85e9 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -26,9 +26,8 @@ tokio = { workspace = true, features = ["time", "sync", "io-std", "io-util", "r bytes = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true, features = ["std", "attributes"] } -cap-std = { workspace = true } -cap-fs-ext = { workspace = true } -io-lifetimes = { workspace = true } +cap-primitives = { workspace = true } +cap-fs-ext-avoid-using-this = { workspace = true } bitflags = { workspace = true } async-trait = { workspace = true } futures = { workspace = true } diff --git a/crates/wasi/src/clocks.rs b/crates/wasi/src/clocks.rs index 0d85792c62d7..4c213ae24184 100644 --- a/crates/wasi/src/clocks.rs +++ b/crates/wasi/src/clocks.rs @@ -1,7 +1,6 @@ -use cap_std::time::{Duration, Instant, SystemClock, SystemTime}; -use cap_std::{AmbientAuthority, ambient_authority}; use std::error::Error; use std::fmt; +use std::time::{Duration, Instant, SystemTime}; use wasmtime::component::{HasData, ResourceTable}; /// A helper struct which implements [`HasData`] for the `wasi:clocks` APIs. @@ -81,22 +80,12 @@ pub trait HostMonotonicClock: Send { fn now(&self) -> u64; } -pub struct WallClock { - /// The underlying system clock. - clock: cap_std::time::SystemClock, -} - -impl Default for WallClock { - fn default() -> Self { - Self::new(ambient_authority()) - } -} +#[derive(Default)] +pub struct WallClock; impl WallClock { - pub fn new(ambient_authority: AmbientAuthority) -> Self { - Self { - clock: cap_std::time::SystemClock::new(ambient_authority), - } + pub fn new() -> Self { + Self } } @@ -122,17 +111,13 @@ impl HostWallClock for WallClock { fn now(&self) -> Duration { // WASI defines wall clocks to return "Unix time". - self.clock - .now() - .duration_since(SystemClock::UNIX_EPOCH) + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) .unwrap() } } pub struct MonotonicClock { - /// The underlying system clock. - clock: cap_std::time::MonotonicClock, - /// The `Instant` this clock was created. All returned times are /// durations since that time. initial: Instant, @@ -140,15 +125,15 @@ pub struct MonotonicClock { impl Default for MonotonicClock { fn default() -> Self { - Self::new(ambient_authority()) + Self::new() } } impl MonotonicClock { - pub fn new(ambient_authority: AmbientAuthority) -> Self { - let clock = cap_std::time::MonotonicClock::new(ambient_authority); - let initial = clock.now(); - Self { clock, initial } + pub fn new() -> Self { + Self { + initial: Instant::now(), + } } } @@ -179,8 +164,7 @@ impl HostMonotonicClock for MonotonicClock { fn now(&self) -> u64 { // Unwrap here and in `resolution` above; a `u64` is wide enough to // hold over 584 years of nanoseconds. - self.clock - .now() + Instant::now() .duration_since(self.initial) .as_nanos() .try_into() @@ -205,7 +189,7 @@ impl TryFrom for Datetime { type Error = DatetimeError; fn try_from(time: SystemTime) -> Result { - let epoch = SystemTime::from_std(std::time::SystemTime::UNIX_EPOCH); + let epoch = SystemTime::UNIX_EPOCH; if time >= epoch { let duration = time.duration_since(epoch)?; diff --git a/crates/wasi/src/ctx.rs b/crates/wasi/src/ctx.rs index 7279122d0248..99c78ab33444 100644 --- a/crates/wasi/src/ctx.rs +++ b/crates/wasi/src/ctx.rs @@ -4,7 +4,7 @@ use crate::filesystem::{Dir, WasiFilesystemCtx}; use crate::random::WasiRandomCtx; use crate::sockets::{SocketAddrCheck, SocketAddrUse, WasiSocketsCtx}; use crate::{DirPerms, FilePerms, OpenMode}; -use cap_std::ambient_authority; +use cap_primitives::ambient_authority; use rand::Rng; use std::future::Future; use std::mem; @@ -304,7 +304,7 @@ impl WasiCtxBuilder { dir_perms: DirPerms, file_perms: FilePerms, ) -> Result<&mut Self> { - let dir = cap_std::fs::Dir::open_ambient_dir(host_path.as_ref(), ambient_authority())?; + let dir = cap_primitives::fs::open_ambient_dir(host_path.as_ref(), ambient_authority())?; let mut open_mode = OpenMode::empty(); if dir_perms.contains(DirPerms::READ) { open_mode |= OpenMode::READ; diff --git a/crates/wasi/src/filesystem.rs b/crates/wasi/src/filesystem.rs index 3f6b657cbcba..c6e89fef7895 100644 --- a/crates/wasi/src/filesystem.rs +++ b/crates/wasi/src/filesystem.rs @@ -1,7 +1,6 @@ use crate::clocks::Datetime; use crate::runtime::{AbortOnDropJoinHandle, spawn_blocking}; -use cap_fs_ext::{FileTypeExt as _, MetadataExt as _, SystemTimeSpec}; -use io_lifetimes::AsFilelike; +use cap_primitives::fs::{DirOptions, FollowSymlinks, Metadata, OpenOptions, SystemTimeSpec}; use std::collections::hash_map; use std::sync::Arc; use std::time::SystemTime; @@ -242,11 +241,6 @@ pub(crate) enum ErrorCode { InvalidSeek, } -fn datetime_from(t: std::time::SystemTime) -> Datetime { - // FIXME make this infallible or handle errors properly - Datetime::try_from(cap_std::time::SystemTime::from_std(t)).unwrap() -} - /// The type of a filesystem object referenced by a descriptor. /// /// Note: This was called `filetype` in earlier versions of WASI. @@ -255,6 +249,10 @@ pub(crate) enum DescriptorType { /// any of the other types specified. Unknown, /// The descriptor refers to a block device inode. + #[cfg_attr( + windows, + expect(dead_code, reason = "windows has no notion of block devices") + )] BlockDevice, /// The descriptor refers to a character device inode. CharacterDevice, @@ -266,20 +264,16 @@ pub(crate) enum DescriptorType { RegularFile, } -impl From for DescriptorType { - fn from(ft: cap_std::fs::FileType) -> Self { +impl From for DescriptorType { + fn from(ft: cap_primitives::fs::FileType) -> Self { if ft.is_dir() { DescriptorType::Directory } else if ft.is_symlink() { DescriptorType::SymbolicLink - } else if ft.is_block_device() { - DescriptorType::BlockDevice - } else if ft.is_char_device() { - DescriptorType::CharacterDevice } else if ft.is_file() { DescriptorType::RegularFile } else { - DescriptorType::Unknown + sys::descriptor_type(ft) } } } @@ -312,11 +306,18 @@ pub(crate) struct DescriptorStat { pub status_change_timestamp: Option, } -impl From for DescriptorStat { - fn from(meta: cap_std::fs::Metadata) -> Self { +impl DescriptorStat { + /// Creates a `DescriptorStat` from a `Metadata` plus the hard link + /// count. + fn new(meta: &Metadata, link_count: u64) -> Self { + fn datetime_from(t: std::time::SystemTime) -> Datetime { + // FIXME make this infallible or handle errors properly + Datetime::try_from(t).unwrap() + } + Self { type_: meta.file_type().into(), - link_count: meta.nlink(), + link_count, size: meta.len(), data_access_timestamp: meta.accessed().map(|t| datetime_from(t.into_std())).ok(), data_modification_timestamp: meta.modified().map(|t| datetime_from(t.into_std())).ok(), @@ -334,17 +335,17 @@ pub(crate) struct MetadataHashValue { pub upper: u64, } -impl From<&cap_std::fs::Metadata> for MetadataHashValue { - fn from(meta: &cap_std::fs::Metadata) -> Self { - use cap_fs_ext::MetadataExt; +impl MetadataHashValue { + /// Creates a hash value from a file's unique identity, e.g. a + /// device/inode number pair. + fn new(identity: impl std::hash::Hash) -> Self { // Without incurring any deps, std provides us with a 64 bit hash // function: - use std::hash::Hasher; + use std::hash::Hasher as _; // Note that this means that the metadata hash (which becomes a preview1 ino) may // change when a different rustc release is used to build this host implementation: let mut hasher = hash_map::DefaultHasher::new(); - hasher.write_u64(meta.dev()); - hasher.write_u64(meta.ino()); + identity.hash(&mut hasher); let lower = hasher.finish(); // MetadataHashValue has a pair of 64-bit members for representing a // single 128-bit number. However, we only have 64 bits of entropy. To @@ -486,19 +487,6 @@ impl Descriptor { } } - async fn get_metadata(&self) -> std::io::Result { - match self { - Self::File(f) => { - // No permissions check on metadata: if opened, allowed to stat it - f.run_blocking(|f| f.metadata()).await - } - Self::Dir(d) => { - // No permissions check on metadata: if opened, allowed to stat it - d.run_blocking(|d| d.dir_metadata()).await - } - } - } - pub(crate) async fn sync_data(&self) -> Result<(), ErrorCode> { match self { Self::File(f) => { @@ -519,7 +507,11 @@ impl Descriptor { } Self::Dir(d) => { d.run_blocking(|d| { - let d = d.open(std::path::Component::CurDir)?; + let d = cap_primitives::fs::open( + d, + std::path::Component::CurDir.as_ref(), + OpenOptions::new().read(true), + )?; d.sync_data()?; Ok(()) }) @@ -556,7 +548,7 @@ impl Descriptor { pub(crate) async fn get_type(&self) -> Result { match self { Self::File(f) => { - let meta = f.run_blocking(|f| f.metadata()).await?; + let meta = f.run_blocking(|f| Metadata::from_file(f)).await?; Ok(meta.file_type().into()) } Self::Dir(_) => Ok(DescriptorType::Directory), @@ -580,16 +572,14 @@ impl Descriptor { if !f.perms.contains(FilePerms::WRITE) { return Err(ErrorCode::NotPermitted); } - f.run_blocking(move |f| f.as_filelike_view::().set_times(times)) - .await?; + f.run_blocking(move |f| f.set_times(times)).await?; Ok(()) } Self::Dir(d) => { if !d.perms.contains(DirPerms::MUTATE) { return Err(ErrorCode::NotPermitted); } - d.run_blocking(move |d| d.as_filelike_view::().set_times(times)) - .await?; + d.run_blocking(move |d| d.set_times(times)).await?; Ok(()) } } @@ -615,7 +605,11 @@ impl Descriptor { } Self::Dir(d) => { d.run_blocking(|d| { - let d = d.open(std::path::Component::CurDir)?; + let d = cap_primitives::fs::open( + d, + std::path::Component::CurDir.as_ref(), + OpenOptions::new().read(true), + )?; d.sync_all()?; Ok(()) }) @@ -626,44 +620,34 @@ impl Descriptor { pub(crate) async fn stat(&self) -> Result { match self { - Self::File(f) => { - // No permissions check on stat: if opened, allowed to stat it - let meta = f.run_blocking(|f| f.metadata()).await?; - Ok(meta.into()) - } - Self::Dir(d) => { - // No permissions check on stat: if opened, allowed to stat it - let meta = d.run_blocking(|d| d.dir_metadata()).await?; - Ok(meta.into()) - } + Self::File(f) => Ok(f.run_blocking(|f| sys::stat(f)).await?), + Self::Dir(d) => Ok(d.run_blocking(|f| sys::stat(f)).await?), } } pub(crate) async fn is_same_object(&self, other: &Self) -> wasmtime::Result { - use cap_fs_ext::MetadataExt; - let meta_a = self.get_metadata().await?; - let meta_b = other.get_metadata().await?; - if meta_a.dev() == meta_b.dev() && meta_a.ino() == meta_b.ino() { - // MetadataHashValue does not derive eq, so use a pair of - // comparisons to check equality: - debug_assert_eq!( - MetadataHashValue::from(&meta_a).upper, - MetadataHashValue::from(&meta_b).upper, - ); - debug_assert_eq!( - MetadataHashValue::from(&meta_a).lower, - MetadataHashValue::from(&meta_b).lower, - ); - Ok(true) - } else { - // Hash collisions are possible, so don't assert the negative here - Ok(false) - } + // No permissions check on metadata: if opened, allowed to stat it + let other = match other { + Self::File(f) => Arc::clone(&f.file), + Self::Dir(d) => Arc::clone(&d.dir), + }; + Ok(match self { + Self::File(f) => { + f.run_blocking(move |f| sys::is_same_file(f, &other)) + .await? + } + Self::Dir(d) => { + d.run_blocking(move |d| sys::is_same_file(d, &other)) + .await? + } + }) } pub(crate) async fn metadata_hash(&self) -> Result { - let meta = self.get_metadata().await?; - Ok(MetadataHashValue::from(&meta)) + match self { + Self::File(f) => Ok(f.run_blocking(|f| sys::metadata_hash(f)).await?), + Self::Dir(d) => Ok(d.run_blocking(|d| sys::metadata_hash(d)).await?), + } } } @@ -674,15 +658,15 @@ pub struct File { /// Wrapped in an Arc because the same underlying file is used for /// implementing the stream types. A copy is also needed for /// `spawn_blocking`. - pub file: Arc, + pub file: Arc, /// Permissions to enforce on access to the file. These permissions are /// specified by a user of the `crate::WasiCtxBuilder`, and are /// enforced prior to any enforced by the underlying operating system. pub perms: FilePerms, /// The mode the file was opened under: bits for reading, and writing. - /// Required to correctly report the DescriptorFlags, because cap-std - /// doesn't presently provide a cross-platform equivalent of reading the - /// oflags back out using fcntl. + /// Required to correctly report the DescriptorFlags, because + /// cap-primitives doesn't presently provide a cross-platform equivalent + /// of reading the oflags back out using fcntl. pub open_mode: OpenMode, allow_blocking_current_thread: bool, @@ -690,7 +674,7 @@ pub struct File { impl File { pub fn new( - file: cap_std::fs::File, + file: std::fs::File, perms: FilePerms, open_mode: OpenMode, allow_blocking_current_thread: bool, @@ -719,7 +703,7 @@ impl File { /// - [Implement opt-in for enabling WASI to block the current thread](https://github.com/bytecodealliance/wasmtime/pull/8190) pub(crate) async fn run_blocking(&self, body: F) -> R where - F: FnOnce(&cap_std::fs::File) -> R + Send + 'static, + F: FnOnce(&std::fs::File) -> R + Send + 'static, R: Send + 'static, { match self.as_blocking_file() { @@ -730,7 +714,7 @@ impl File { pub(crate) fn spawn_blocking(&self, body: F) -> AbortOnDropJoinHandle where - F: FnOnce(&cap_std::fs::File) -> R + Send + 'static, + F: FnOnce(&std::fs::File) -> R + Send + 'static, R: Send + 'static, { let f = self.file.clone(); @@ -740,7 +724,7 @@ impl File { /// Returns `Some` when the current thread is allowed to block in filesystem /// operations, and otherwise returns `None` to indicate that /// `spawn_blocking` must be used. - pub(crate) fn as_blocking_file(&self) -> Option<&cap_std::fs::File> { + pub(crate) fn as_blocking_file(&self) -> Option<&std::fs::File> { if self.allow_blocking_current_thread { Some(&self.file) } else { @@ -748,9 +732,9 @@ impl File { } } - /// Returns reference to the underlying [`cap_std::fs::File`] + /// Returns reference to the underlying [`std::fs::File`] #[cfg(feature = "p3")] - pub(crate) fn as_file(&self) -> &Arc { + pub(crate) fn as_file(&self) -> &Arc { &self.file } @@ -779,8 +763,11 @@ pub struct Dir { /// The operating system file descriptor this struct is mediating access /// to. /// + /// This is a handle to a directory, and all paths accessed through this + /// struct are sandboxed to be within this directory via `cap-primitives`. + /// /// Wrapped in an Arc because a copy is needed for `run_blocking`. - pub dir: Arc, + pub dir: Arc, /// Permissions to enforce on access to this directory. These permissions /// are specified by a user of the `crate::WasiCtxBuilder`, and /// are enforced prior to any enforced by the underlying operating system. @@ -791,9 +778,9 @@ pub struct Dir { /// Permissions to enforce on any files opened under this directory. pub file_perms: FilePerms, /// The mode the directory was opened under: bits for reading, and writing. - /// Required to correctly report the DescriptorFlags, because cap-std - /// doesn't presently provide a cross-platform equivalent of reading the - /// oflags back out using fcntl. + /// Required to correctly report the DescriptorFlags, because + /// cap-primitives doesn't presently provide a cross-platform equivalent + /// of reading the oflags back out using fcntl. pub open_mode: OpenMode, pub(crate) allow_blocking_current_thread: bool, @@ -801,7 +788,7 @@ pub struct Dir { impl Dir { pub fn new( - dir: cap_std::fs::Dir, + dir: std::fs::File, perms: DirPerms, file_perms: FilePerms, open_mode: OpenMode, @@ -832,7 +819,7 @@ impl Dir { /// - [Implement opt-in for enabling WASI to block the current thread](https://github.com/bytecodealliance/wasmtime/pull/8190) pub(crate) async fn run_blocking(&self, body: F) -> R where - F: FnOnce(&cap_std::fs::Dir) -> R + Send + 'static, + F: FnOnce(&std::fs::File) -> R + Send + 'static, R: Send + 'static, { if self.allow_blocking_current_thread { @@ -843,9 +830,9 @@ impl Dir { } } - /// Returns reference to the underlying [`cap_std::fs::Dir`] + /// Returns reference to the underlying directory handle. #[cfg(feature = "p3")] - pub(crate) fn as_dir(&self) -> &Arc { + pub(crate) fn as_dir(&self) -> &Arc { &self.dir } @@ -853,7 +840,10 @@ impl Dir { if !self.perms.contains(DirPerms::MUTATE) { return Err(ErrorCode::NotPermitted); } - self.run_blocking(move |d| d.create_dir(&path)).await?; + self.run_blocking(move |d| { + cap_primitives::fs::create_dir(d, path.as_ref(), &DirOptions::new()) + }) + .await?; Ok(()) } @@ -866,13 +856,15 @@ impl Dir { return Err(ErrorCode::NotPermitted); } - let meta = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { - self.run_blocking(move |d| d.metadata(&path)).await? + let follow = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { + FollowSymlinks::Yes } else { - self.run_blocking(move |d| d.symlink_metadata(&path)) - .await? + FollowSymlinks::No }; - Ok(meta.into()) + let ret = self + .run_blocking(move |d| sys::stat_at(d, path.as_ref(), follow)) + .await?; + Ok(ret) } pub(crate) async fn set_times_at( @@ -882,19 +874,21 @@ impl Dir { atim: Option, mtim: Option, ) -> Result<(), ErrorCode> { - use cap_fs_ext::DirExt as _; - if !self.perms.contains(DirPerms::MUTATE) { return Err(ErrorCode::NotPermitted); } - let atim = atim.map(|t| SystemTimeSpec::Absolute(cap_std::time::SystemTime::from_std(t))); - let mtim = mtim.map(|t| SystemTimeSpec::Absolute(cap_std::time::SystemTime::from_std(t))); + let atim = + atim.map(|t| SystemTimeSpec::Absolute(cap_primitives::time::SystemTime::from_std(t))); + let mtim = + mtim.map(|t| SystemTimeSpec::Absolute(cap_primitives::time::SystemTime::from_std(t))); if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { - self.run_blocking(move |d| d.set_times(&path, atim, mtim)) + self.run_blocking(move |d| cap_primitives::fs::set_times(d, path.as_ref(), atim, mtim)) .await?; } else { - self.run_blocking(move |d| d.set_symlink_times(&path, atim, mtim)) - .await?; + self.run_blocking(move |d| { + cap_primitives::fs::set_times_nofollow(d, path.as_ref(), atim, mtim) + }) + .await?; } Ok(()) } @@ -919,8 +913,10 @@ impl Dir { return Err(ErrorCode::NotPermitted); } let new_dir_handle = Arc::clone(&new_dir.dir); - self.run_blocking(move |d| d.hard_link(&old_path, &new_dir_handle, &new_path)) - .await?; + self.run_blocking(move |d| { + cap_primitives::fs::hard_link(d, old_path.as_ref(), &new_dir_handle, new_path.as_ref()) + }) + .await?; Ok(()) } @@ -932,8 +928,6 @@ impl Dir { flags: DescriptorFlags, allow_blocking_current_thread: bool, ) -> Result { - use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt, OpenOptionsMaybeDirExt}; - if !self.perms.contains(DirPerms::READ) { return Err(ErrorCode::NotPermitted); } @@ -952,8 +946,8 @@ impl Dir { // Track open mode, for permission check and recording in created descriptor: let mut open_mode = OpenMode::empty(); // Construct the OpenOptions to give the OS: - let mut opts = cap_std::fs::OpenOptions::new(); - opts.maybe_dir(true); + let mut opts = OpenOptions::new(); + sys::maybe_dir(&mut opts); if oflags.contains(OpenFlags::CREATE) { if oflags.contains(OpenFlags::EXCLUSIVE) { @@ -983,13 +977,21 @@ impl Dir { opts.read(true); open_mode |= OpenMode::READ; } - if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { - opts.follow(FollowSymlinks::Yes); - } else { - opts.follow(FollowSymlinks::No); + + // Note that this is intentionally scoped to a separate block to + // minimize the surface area that is depended on by cap-fs-ext. Ideally + // the underlying functionality in `cap-primitives` would get exposed, + // but that'll require an upstream PR. + { + use cap_fs_ext_avoid_using_this::OpenOptionsFollowExt; + if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { + opts.follow(FollowSymlinks::Yes); + } else { + opts.follow(FollowSymlinks::No); + } } - // These flags are not yet supported in cap-std: + // These flags are not yet supported in cap-primitives: if flags.contains(DescriptorFlags::FILE_INTEGRITY_SYNC) || flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC) || flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC) @@ -1019,18 +1021,16 @@ impl Dir { // This makes sure we don't have to give spawn_blocking any way to // manipulate the table. enum OpenResult { - Dir(cap_std::fs::Dir), - File(cap_std::fs::File), + Dir(std::fs::File), + File(std::fs::File), NotDir, } let opened = self .run_blocking::<_, std::io::Result>(move |d| { - let opened = d.open_with(&path, &opts)?; - if opened.metadata()?.is_dir() { - Ok(OpenResult::Dir(cap_std::fs::Dir::from_std_file( - opened.into_std(), - ))) + let opened = cap_primitives::fs::open(d, path.as_ref(), &opts)?; + if Metadata::from_file(&opened)?.is_dir() { + Ok(OpenResult::Dir(opened)) } else if oflags.contains(OpenFlags::DIRECTORY) { Ok(OpenResult::NotDir) } else { @@ -1071,7 +1071,9 @@ impl Dir { if !self.perms.contains(DirPerms::READ) { return Err(ErrorCode::NotPermitted); } - let link = self.run_blocking(move |d| d.read_link(&path)).await?; + let link = self + .run_blocking(move |d| cap_primitives::fs::read_link(d, path.as_ref())) + .await?; link.into_os_string() .into_string() .or(Err(ErrorCode::IllegalByteSequence)) @@ -1081,7 +1083,8 @@ impl Dir { if !self.perms.contains(DirPerms::MUTATE) { return Err(ErrorCode::NotPermitted); } - self.run_blocking(move |d| d.remove_dir(&path)).await?; + self.run_blocking(move |d| cap_primitives::fs::remove_dir(d, path.as_ref())) + .await?; Ok(()) } @@ -1101,8 +1104,10 @@ impl Dir { return Err(ErrorCode::NotPermitted); } let new_dir_handle = Arc::clone(&new_dir.dir); - self.run_blocking(move |d| d.rename(&old_path, &new_dir_handle, &new_path)) - .await?; + self.run_blocking(move |d| { + cap_primitives::fs::rename(d, old_path.as_ref(), &new_dir_handle, new_path.as_ref()) + }) + .await?; Ok(()) } @@ -1111,25 +1116,19 @@ impl Dir { src_path: String, dest_path: String, ) -> Result<(), ErrorCode> { - // On windows, Dir.symlink is provided by DirExt - #[cfg(windows)] - use cap_fs_ext::DirExt; - if !self.perms.contains(DirPerms::MUTATE) { return Err(ErrorCode::NotPermitted); } - self.run_blocking(move |d| d.symlink(&src_path, &dest_path)) + self.run_blocking(move |d| sys::symlink(src_path.as_ref(), d, dest_path.as_ref())) .await?; Ok(()) } pub(crate) async fn unlink_file_at(&self, path: String) -> Result<(), ErrorCode> { - use cap_fs_ext::DirExt; - if !self.perms.contains(DirPerms::MUTATE) { return Err(ErrorCode::NotPermitted); } - self.run_blocking(move |d| d.remove_file_or_symlink(&path)) + self.run_blocking(move |d| sys::remove_file_or_symlink(d, path.as_ref())) .await?; Ok(()) } @@ -1140,16 +1139,15 @@ impl Dir { path: String, ) -> Result { // No permissions check on metadata: if dir opened, allowed to stat it - let meta = self - .run_blocking(move |d| { - if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { - d.metadata(path) - } else { - d.symlink_metadata(path) - } - }) + let follow = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { + FollowSymlinks::Yes + } else { + FollowSymlinks::No + }; + let hash = self + .run_blocking(move |d| sys::metadata_hash_at(d, path.as_ref(), follow)) .await?; - Ok(MetadataHashValue::from(&meta)) + Ok(hash) } } diff --git a/crates/wasi/src/filesystem/unix.rs b/crates/wasi/src/filesystem/unix.rs index 12826d854dd9..d93ab09495b1 100644 --- a/crates/wasi/src/filesystem/unix.rs +++ b/crates/wasi/src/filesystem/unix.rs @@ -1,13 +1,20 @@ -use crate::filesystem::{Advice, DescriptorFlags}; -use io_lifetimes::AsFilelike; +use crate::filesystem::{ + Advice, DescriptorFlags, DescriptorStat, DescriptorType, MetadataHashValue, +}; +use cap_primitives::fs::{ + FileType, FileTypeExt, FollowSymlinks, Metadata, MetadataExt, OpenOptions, +}; use rustix::fs::{OFlags, fcntl_getfl, fcntl_setfl}; use rustix::io::write; use std::fs::File; use std::io; -use std::os::fd::AsFd; use std::os::unix::fs::FileExt; +use std::path::Path; -pub(crate) fn get_flags(file: impl AsFd) -> io::Result { +pub use cap_primitives::fs::remove_file as remove_file_or_symlink; +pub use cap_primitives::fs::symlink; + +pub(crate) fn get_flags(file: &File) -> io::Result { let flags = fcntl_getfl(file)?; let mut ret = DescriptorFlags::empty(); ret.set( @@ -26,7 +33,7 @@ pub(crate) fn get_flags(file: impl AsFd) -> io::Result { Ok(ret) } -pub(crate) fn advise(file: impl AsFd, offset: u64, len: u64, advice: Advice) -> io::Result<()> { +pub(crate) fn advise(file: &File, offset: u64, len: u64, advice: Advice) -> io::Result<()> { cfg_if::cfg_if! { if #[cfg(target_vendor = "apple")] { match advice { @@ -58,7 +65,7 @@ pub(crate) fn advise(file: impl AsFd, offset: u64, len: u64, advice: Advice) -> Ok(()) } -pub(crate) fn append_cursor_unspecified(file: impl AsFd, data: &[u8]) -> io::Result { +pub(crate) fn append_cursor_unspecified(file: &File, data: &[u8]) -> io::Result { // On Linux, use `pwritev2`. #[cfg(target_os = "linux")] { @@ -83,18 +90,68 @@ pub(crate) fn append_cursor_unspecified(file: impl AsFd, data: &[u8]) -> io::Res Ok(result?) } -pub(crate) fn write_at_cursor_unspecified( - file: impl AsFd, - data: &[u8], - pos: u64, -) -> io::Result { - file.as_filelike_view::().write_at(data, pos) +pub(crate) fn write_at_cursor_unspecified(file: &File, data: &[u8], pos: u64) -> io::Result { + file.write_at(data, pos) } pub(crate) fn read_at_cursor_unspecified( - file: impl AsFd, + file: &File, buf: &mut [u8], pos: u64, ) -> io::Result { - file.as_filelike_view::().read_at(buf, pos) + file.read_at(buf, pos) +} + +fn meta_identity(meta: &Metadata) -> (u64, u64) { + (meta.dev(), meta.ino()) +} + +fn file_identity(file: &File) -> io::Result<(u64, u64)> { + let meta = Metadata::from_file(file)?; + Ok(meta_identity(&meta)) +} + +pub(crate) fn metadata_hash(file: &File) -> io::Result { + Ok(MetadataHashValue::new(file_identity(file)?)) +} + +pub(crate) fn metadata_hash_at( + start: &File, + path: &Path, + follow: FollowSymlinks, +) -> io::Result { + let meta = cap_primitives::fs::stat(start, path, follow)?; + Ok(MetadataHashValue::new(meta_identity(&meta))) +} + +pub(crate) fn is_same_file(a: &File, b: &File) -> io::Result { + Ok(file_identity(a)? == file_identity(b)?) +} + +pub(crate) fn stat(f: &std::fs::File) -> io::Result { + let meta = Metadata::from_file(f)?; + Ok(DescriptorStat::new(&meta, meta.nlink())) +} + +pub(crate) fn stat_at( + start: &File, + path: &Path, + follow: FollowSymlinks, +) -> io::Result { + let meta = cap_primitives::fs::stat(start, path, follow)?; + Ok(DescriptorStat::new(&meta, meta.nlink())) +} + +pub(crate) fn maybe_dir(opts: &mut OpenOptions) { + let _ = opts; +} + +pub(crate) fn descriptor_type(ft: FileType) -> DescriptorType { + if ft.is_block_device() { + DescriptorType::BlockDevice + } else if ft.is_char_device() { + DescriptorType::CharacterDevice + } else { + DescriptorType::Unknown + } } diff --git a/crates/wasi/src/filesystem/windows.rs b/crates/wasi/src/filesystem/windows.rs index 9c1952bfcaea..f0760d85c9a0 100644 --- a/crates/wasi/src/filesystem/windows.rs +++ b/crates/wasi/src/filesystem/windows.rs @@ -1,16 +1,20 @@ -use crate::filesystem::{Advice, DescriptorFlags}; -use io_lifetimes::AsFilelike; +use crate::filesystem::{ + Advice, DescriptorFlags, DescriptorStat, DescriptorType, MetadataHashValue, +}; +use cap_primitives::fs::{FileType, FollowSymlinks, Metadata, OpenOptions, OpenOptionsExt}; use std::fs::File; use std::io::{self, Write}; use std::mem::{self, MaybeUninit}; use std::os::windows::fs::FileExt; use std::os::windows::io::*; +use std::path::Path; +use std::sync::OnceLock; use windows_sys::Wdk::Storage::FileSystem::*; use windows_sys::Win32::Foundation::*; use windows_sys::Win32::Storage::FileSystem::*; use windows_sys::Win32::System::IO::*; -pub(crate) fn get_flags(file: impl AsHandle) -> io::Result { +pub(crate) fn get_flags(file: &File) -> io::Result { let file = file.as_handle(); let mode = query_mode_information(file)?; let mut ret = DescriptorFlags::empty(); @@ -21,7 +25,7 @@ pub(crate) fn get_flags(file: impl AsHandle) -> io::Result { Ok(ret) } -pub(crate) fn advise(file: impl AsHandle, offset: u64, len: u64, advice: Advice) -> io::Result<()> { +pub(crate) fn advise(file: &File, offset: u64, len: u64, advice: Advice) -> io::Result<()> { let _ = (file, offset, len, advice); // ... noop for now ... @@ -29,7 +33,7 @@ pub(crate) fn advise(file: impl AsHandle, offset: u64, len: u64, advice: Advice) Ok(()) } -pub(crate) fn append_cursor_unspecified(file: impl AsHandle, data: &[u8]) -> io::Result { +pub(crate) fn append_cursor_unspecified(file: &File, data: &[u8]) -> io::Result { let file = file.as_handle(); let access = query_access_information(file)?; @@ -54,20 +58,160 @@ pub(crate) fn append_cursor_unspecified(file: impl AsHandle, data: &[u8]) -> io: .write(data) } -pub(crate) fn write_at_cursor_unspecified( - file: impl AsHandle, - data: &[u8], - pos: u64, -) -> io::Result { - file.as_filelike_view::().seek_write(data, pos) +pub(crate) fn write_at_cursor_unspecified(file: &File, data: &[u8], pos: u64) -> io::Result { + file.seek_write(data, pos) } pub(crate) fn read_at_cursor_unspecified( - file: impl AsHandle, + file: &File, buf: &mut [u8], pos: u64, ) -> io::Result { - file.as_filelike_view::().seek_read(buf, pos) + file.seek_read(buf, pos) +} + +fn by_handle_info(file: &File) -> io::Result { + unsafe { + let mut info = mem::zeroed::(); + if GetFileInformationByHandle(file.as_raw_handle(), &mut info) == 0 { + return Err(io::Error::last_os_error()); + } + Ok(info) + } +} + +fn file_identity(file: &File) -> io::Result<(u64, u64)> { + let info = by_handle_info(file)?; + Ok(( + u64::from(info.dwVolumeSerialNumber), + (u64::from(info.nFileIndexHigh) << 32) | u64::from(info.nFileIndexLow), + )) +} + +pub(crate) fn metadata_hash(file: &File) -> io::Result { + Ok(MetadataHashValue::new(file_identity(file)?)) +} + +pub(crate) fn metadata_hash_at( + start: &File, + path: &Path, + follow: FollowSymlinks, +) -> io::Result { + let file = open_metadata_handle(start, path, follow)?; + metadata_hash(&file) +} + +pub(crate) fn is_same_file(a: &File, b: &File) -> io::Result { + Ok(file_identity(a)? == file_identity(b)?) +} + +/// Opens `path` relative to `start` for metadata queries only, mirroring +/// what `cap-primitives`' Windows `stat` does internally: no access rights +/// are requested, `FILE_FLAG_BACKUP_SEMANTICS` permits opening directories, +/// and for `FollowSymlinks::No` the trailing symlink itself is opened via +/// `FILE_FLAG_OPEN_REPARSE_POINT` (which `cap-primitives` documents as +/// suppressing its own trailing-symlink handling). +fn open_metadata_handle(start: &File, path: &Path, follow: FollowSymlinks) -> io::Result { + let mut opts = OpenOptions::new(); + opts.access_mode(0); + match follow { + FollowSymlinks::Yes => { + opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS); + } + FollowSymlinks::No => { + opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT); + } + } + cap_primitives::fs::open(start, path, &opts) +} + +pub(crate) fn stat(f: &std::fs::File) -> io::Result { + let meta = Metadata::from_file(f)?; + + // Note that this is intentionally scoped to a separate block to + // minimize the surface area that is depended on by cap-fs-ext. + let link_count = { + use cap_fs_ext_avoid_using_this::MetadataExt; + meta.nlink() + }; + Ok(DescriptorStat::new(&meta, link_count)) +} + +pub(crate) fn stat_at( + start: &File, + path: &Path, + follow: FollowSymlinks, +) -> io::Result { + let file = open_metadata_handle(start, path, follow)?; + stat(&file) +} + +pub(crate) fn maybe_dir(opts: &mut OpenOptions) { + opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS); + opts.share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE); +} + +pub(crate) fn descriptor_type(ft: FileType) -> DescriptorType { + if is_char_device(ft) { + DescriptorType::CharacterDevice + } else { + DescriptorType::Unknown + } +} + +/// Returns whether `ft` is a character device. +/// +/// This is a bit of a hack around the lack of documented/public API in +/// `cap-primitives` for exposing this information. The `NUL` file is always a +/// character-device, so its type is cached globally once and then used to +/// compare. +fn is_char_device(ft: FileType) -> bool { + static CHAR_DEVICE: OnceLock> = OnceLock::new(); + let probe = CHAR_DEVICE.get_or_init(|| { + let nul = File::open("NUL").ok()?; + let file_type = Metadata::from_file(&nul).ok()?.file_type(); + if file_type.is_file() || file_type.is_dir() || file_type.is_symlink() { + return None; + } + Some(file_type) + }); + *probe == Some(ft) +} + +pub(crate) fn symlink(original: &Path, start: &File, link: &Path) -> io::Result<()> { + if cap_primitives::fs::stat(start, original, FollowSymlinks::Yes)?.is_dir() { + cap_primitives::fs::symlink_dir(original, start, link) + } else { + cap_primitives::fs::symlink_file(original, start, link) + } +} + +pub(crate) fn remove_file_or_symlink(start: &File, path: &Path) -> io::Result<()> { + // Note that `FILE_FLAG_OPEN_REPARSE_POINT` here means a trailing symlink + // is opened as the reparse point itself rather than followed, so no + // nofollow option is needed. + let mut opts = OpenOptions::new(); + opts.access_mode(DELETE); + opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); + let file = cap_primitives::fs::open(start, path, &opts)?; + + let meta = Metadata::from_file(&file)?; + if meta.file_type().is_symlink() + && cap_primitives::fs::MetadataExt::file_attributes(&meta) & FILE_ATTRIBUTE_DIRECTORY + == FILE_ATTRIBUTE_DIRECTORY + { + cap_primitives::fs::remove_dir(start, path)?; + } else { + cap_primitives::fs::remove_file(start, path)?; + } + + // Drop the file after calling `remove_file` or `remove_dir`, since + // Windows doesn't actually remove the file until after the last open + // handle is closed, and this protects us from race conditions where + // other processes replace the file out from underneath us. + drop(file); + + Ok(()) } fn query_access_information(handle: BorrowedHandle<'_>) -> io::Result { diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index 506fcc3756ed..1d43a08fda3e 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -3,9 +3,9 @@ //! # Wasmtime's WASI Implementation //! //! This crate provides a Wasmtime host implementations of different versions of WASI. -//! WASI is implemented with the Rust crates [`tokio`] and [`cap-std`](cap_std) primarily, meaning that -//! operations are implemented in terms of their native platform equivalents by -//! default. +//! WASI is implemented with the Rust crates [`tokio`] and +//! [`cap-primitives`](cap_primitives) primarily, meaning that operations are +//! implemented in terms of their native platform equivalents by default. //! //! For components and WASIp2, see [`p2`]. //! For WASIp1 and core modules, see the [`p1`] module documentation. @@ -59,7 +59,7 @@ pub use self::view::{WasiCtxView, WasiView}; #[doc(no_inline)] pub use async_trait::async_trait; #[doc(no_inline)] -pub use cap_fs_ext::SystemTimeSpec; +pub use cap_primitives::fs::SystemTimeSpec; #[doc(no_inline)] pub use rand::Rng; #[doc(no_inline)] diff --git a/crates/wasi/src/p1.rs b/crates/wasi/src/p1.rs index 2b52cc4a08a2..3559576ee926 100644 --- a/crates/wasi/src/p1.rs +++ b/crates/wasi/src/p1.rs @@ -653,7 +653,7 @@ impl WasiP1Ctx { drop(t); let f = self.table.get(&fd)?.file()?; - let do_write = move |f: &cap_std::fs::File, buf: &[u8]| match (append, write) { + let do_write = move |f: &std::fs::File, buf: &[u8]| match (append, write) { // Note that this is implementing Linux semantics of // `pwrite` where the offset is ignored if the file was // opened in append mode. diff --git a/crates/wasi/src/p2/filesystem.rs b/crates/wasi/src/p2/filesystem.rs index 39e10e354c87..cad47f68d0dd 100644 --- a/crates/wasi/src/p2/filesystem.rs +++ b/crates/wasi/src/p2/filesystem.rs @@ -84,7 +84,7 @@ impl FileInputStream { } } - fn blocking_read(file: &cap_std::fs::File, offset: u64, size: usize) -> ReadState { + fn blocking_read(file: &std::fs::File, offset: u64, size: usize) -> ReadState { let mut buf = BytesMut::zeroed(size.min(crate::MAX_READ_SIZE_ALLOC)); loop { match sys::read_at_cursor_unspecified(file, &mut buf, offset) { @@ -238,7 +238,7 @@ impl FileOutputStream { } fn blocking_write( - file: &cap_std::fs::File, + file: &std::fs::File, mut buf: Bytes, mode: FileOutputMode, ) -> io::Result { diff --git a/crates/wasi/src/p2/host/clocks.rs b/crates/wasi/src/p2/host/clocks.rs index cb94d4536485..f9e4764e7c27 100644 --- a/crates/wasi/src/p2/host/clocks.rs +++ b/crates/wasi/src/p2/host/clocks.rs @@ -4,7 +4,7 @@ use crate::p2::bindings::{ clocks::monotonic_clock::{self, Duration as WasiDuration, Instant}, clocks::wall_clock::{self, Datetime}, }; -use cap_std::time::SystemTime; +use std::time::SystemTime; use std::time::Duration; use wasmtime::component::Resource; use wasmtime_wasi_io::poll::{Pollable, subscribe}; diff --git a/crates/wasi/src/p2/host/filesystem.rs b/crates/wasi/src/p2/host/filesystem.rs index cc7addb893cd..a81b6622e545 100644 --- a/crates/wasi/src/p2/host/filesystem.rs +++ b/crates/wasi/src/p2/host/filesystem.rs @@ -178,7 +178,7 @@ impl HostDescriptor for WasiFilesystemCtxView<'_> { // within this `block` call, rather than delay calculating the metadata // for entries when they're demanded later in the iterator chain. Ok::<_, std::io::Error>( - d.entries()? + cap_primitives::fs::read_base_dir(d)? .map(|entry| { let entry = entry?; let meta = entry.metadata()?; @@ -718,22 +718,8 @@ impl From for ErrorCode { } } -fn descriptortype_from(ft: cap_std::fs::FileType) -> types::DescriptorType { - use cap_fs_ext::FileTypeExt; - use types::DescriptorType; - if ft.is_dir() { - DescriptorType::Directory - } else if ft.is_symlink() { - DescriptorType::SymbolicLink - } else if ft.is_block_device() { - DescriptorType::BlockDevice - } else if ft.is_char_device() { - DescriptorType::CharacterDevice - } else if ft.is_file() { - DescriptorType::RegularFile - } else { - DescriptorType::Unknown - } +fn descriptortype_from(ft: cap_primitives::fs::FileType) -> types::DescriptorType { + crate::filesystem::DescriptorType::from(ft).into() } fn systemtime_from(t: wall_clock::Datetime) -> Result { diff --git a/crates/wasi/src/p2/mod.rs b/crates/wasi/src/p2/mod.rs index 7b34ddd0a9db..7da590627671 100644 --- a/crates/wasi/src/p2/mod.rs +++ b/crates/wasi/src/p2/mod.rs @@ -3,7 +3,7 @@ //! //! This module provides a Wasmtime host implementation of WASI 0.2 (aka WASIp2 //! aka Preview 2) and WASI 0.1 (aka WASIp1 aka Preview 1). WASI is implemented -//! with the Rust crates [`tokio`] and [`cap-std`] primarily, meaning that +//! with the Rust crates [`tokio`] and [`cap-primitives`] primarily, meaning that //! operations are implemented in terms of their native platform equivalents by //! default. //! @@ -187,7 +187,7 @@ //! //! [`wasmtime::component::bindgen!`]: https://docs.rs/wasmtime/latest/wasmtime/component/macro.bindgen.html //! [`tokio`]: https://crates.io/crates/tokio -//! [`cap-std`]: https://crates.io/crates/cap-std +//! [`cap-primitives`]: https://crates.io/crates/cap-primitives //! [`wasmtime-wasi-io`]: https://crates.io/crates/wasmtime-wasi-io //! [`wasi:cli/environment`]: bindings::cli::environment::Host //! [`wasi:cli/exit`]: bindings::cli::exit::Host diff --git a/crates/wasi/src/p2/tcp.rs b/crates/wasi/src/p2/tcp.rs index 379e5cf676aa..0c52a3b92fea 100644 --- a/crates/wasi/src/p2/tcp.rs +++ b/crates/wasi/src/p2/tcp.rs @@ -4,7 +4,6 @@ use crate::p2::{ }; use crate::runtime::AbortOnDropJoinHandle; use crate::sockets::TcpSocket; -use io_lifetimes::AsSocketlike; use rustix::io::Errno; use std::io; use std::mem; @@ -338,9 +337,7 @@ impl Pollable for TcpWriteStream { } fn native_shutdown(stream: &tokio::net::TcpStream, how: Shutdown) { - _ = stream - .as_socketlike_view::() - .shutdown(how); + _ = crate::sockets::util::shutdown(stream, how); } fn try_lock_for_stream(mutex: &Mutex) -> Result, StreamError> { diff --git a/crates/wasi/src/p3/filesystem/host.rs b/crates/wasi/src/p3/filesystem/host.rs index 729701f3a8e1..d36589f9c3b4 100644 --- a/crates/wasi/src/p3/filesystem/host.rs +++ b/crates/wasi/src/p3/filesystem/host.rs @@ -198,7 +198,7 @@ impl StreamProducer for ReadStreamProducer { let file = Arc::clone(me.file.as_file()); let offset = me.offset; spawn_blocking(move || { - sys::read_at_cursor_unspecified(file, &mut buf, offset).map(|n| { + sys::read_at_cursor_unspecified(&file, &mut buf, offset).map(|n| { buf.truncate(n); buf }) @@ -244,7 +244,7 @@ impl StreamProducer for ReadStreamProducer { } fn map_dir_entry( - entry: std::io::Result, + entry: std::io::Result, ) -> Result, ErrorCode> { match entry { Ok(entry) => { @@ -284,13 +284,13 @@ struct ReadDirStream { impl ReadDirStream { fn new( - dir: Arc, + dir: Arc, result: oneshot::Sender>, ) -> ReadDirStream { let (tx, rx) = mpsc::channel(1); ReadDirStream { task: spawn_blocking(move || { - let entries = dir.entries()?; + let entries = cap_primitives::fs::read_base_dir(&dir)?; for entry in entries { if let Some(entry) = map_dir_entry(entry)? { if let Err(_) = tx.blocking_send(entry) { @@ -428,7 +428,7 @@ impl WriteStreamConsumer { } impl WriteLocation { - fn write(&self, file: &cap_std::fs::File, bytes: &[u8]) -> io::Result { + fn write(&self, file: &std::fs::File, bytes: &[u8]) -> io::Result { match *self { WriteLocation::End => sys::append_cursor_unspecified(file, bytes), WriteLocation::Offset(at) => sys::write_at_cursor_unspecified(file, bytes, at), @@ -681,7 +681,7 @@ impl types::HostDescriptorWithStore for WasiFilesystem { let allow_blocking_current_thread = dir.allow_blocking_current_thread; let dir = Arc::clone(dir.as_dir()); if allow_blocking_current_thread { - match dir.entries() { + match cap_primitives::fs::read_base_dir(&dir) { Ok(readdir) => StreamReader::new( &mut store, FallibleIteratorProducer::new( diff --git a/crates/wasi/src/p3/filesystem/mod.rs b/crates/wasi/src/p3/filesystem/mod.rs index 06862db3e4e5..11816c12ea3d 100644 --- a/crates/wasi/src/p3/filesystem/mod.rs +++ b/crates/wasi/src/p3/filesystem/mod.rs @@ -265,21 +265,8 @@ impl From for types::DescriptorType { } } -impl From for types::DescriptorType { - fn from(ft: cap_std::fs::FileType) -> Self { - use cap_fs_ext::FileTypeExt as _; - if ft.is_dir() { - Self::Directory - } else if ft.is_symlink() { - Self::SymbolicLink - } else if ft.is_block_device() { - Self::BlockDevice - } else if ft.is_char_device() { - Self::CharacterDevice - } else if ft.is_file() { - Self::RegularFile - } else { - Self::Other(None) - } +impl From for types::DescriptorType { + fn from(ft: cap_primitives::fs::FileType) -> Self { + crate::filesystem::DescriptorType::from(ft).into() } } diff --git a/crates/wasi/src/p3/sockets/host/types/tcp.rs b/crates/wasi/src/p3/sockets/host/types/tcp.rs index b3b0e9658c9e..303c72da4208 100644 --- a/crates/wasi/src/p3/sockets/host/types/tcp.rs +++ b/crates/wasi/src/p3/sockets/host/types/tcp.rs @@ -10,7 +10,6 @@ use bytes::BytesMut; use core::iter; use core::pin::Pin; use core::task::{Context, Poll}; -use io_lifetimes::AsSocketlike as _; use std::net::{Shutdown, SocketAddr}; use std::sync::Arc; use tokio::net::{TcpListener, TcpStream}; @@ -103,10 +102,7 @@ impl Drop for ReceiveStreamProducer { impl ReceiveStreamProducer { fn close(&mut self, res: Result<(), ErrorCode>) { if let Some(tx) = self.result.take() { - _ = self - .stream - .as_socketlike_view::() - .shutdown(Shutdown::Read); + _ = crate::sockets::util::shutdown(&self.stream, Shutdown::Read); _ = tx.send(res); } } @@ -177,10 +173,7 @@ impl Drop for SendStreamConsumer { impl SendStreamConsumer { fn close(&mut self, res: Result<(), ErrorCode>) { if let Some(tx) = self.result.take() { - _ = self - .stream - .as_socketlike_view::() - .shutdown(Shutdown::Write); + _ = crate::sockets::util::shutdown(&self.stream, Shutdown::Write); _ = tx.send(res); } } diff --git a/crates/wasi/src/sockets/tcp.rs b/crates/wasi/src/sockets/tcp.rs index 37f4ea7fe34c..eb2ed344c7ae 100644 --- a/crates/wasi/src/sockets/tcp.rs +++ b/crates/wasi/src/sockets/tcp.rs @@ -7,8 +7,6 @@ use crate::sockets::util::{ set_send_buffer_size, set_unicast_hop_limit, tcp_bind, }; use crate::sockets::{DEFAULT_TCP_BACKLOG, SocketAddressFamily, WasiSocketsCtx}; -use io_lifetimes::AsSocketlike as _; -use io_lifetimes::views::SocketlikeView; use rustix::io::Errno; use rustix::net::sockopt; use std::fmt::Debug; @@ -236,14 +234,14 @@ impl TcpSocket { } } - pub(crate) fn as_std_view(&self) -> Result, ErrorCode> { + fn as_std_view(&self) -> Result, ErrorCode> { match &self.tcp_state { TcpState::Default(socket) | TcpState::BindStarted(socket) | TcpState::Bound(socket) - | TcpState::ListenStarted(socket) => Ok(socket.as_socketlike_view()), - TcpState::Connected { stream, .. } => Ok(stream.as_socketlike_view()), - TcpState::Listening { listener, .. } => Ok(listener.as_socketlike_view()), + | TcpState::ListenStarted(socket) => Ok(StdView::Socket(socket)), + TcpState::Connected { stream, .. } => Ok(StdView::Stream(stream)), + TcpState::Listening { listener, .. } => Ok(StdView::Listener(listener)), TcpState::Connecting(..) | TcpState::ConnectReady(_) | TcpState::Closed => { Err(ErrorCode::InvalidState) } @@ -533,19 +531,19 @@ impl TcpSocket { } pub(crate) fn keep_alive_enabled(&self) -> Result { - let fd = &*self.as_std_view()?; + let fd = self.as_std_view()?; let v = sockopt::socket_keepalive(fd)?; Ok(v) } pub(crate) fn set_keep_alive_enabled(&self, value: bool) -> Result<(), ErrorCode> { - let fd = &*self.as_std_view()?; + let fd = self.as_std_view()?; sockopt::set_socket_keepalive(fd, value)?; Ok(()) } pub(crate) fn keep_alive_idle_time(&self) -> Result { - let fd = &*self.as_std_view()?; + let fd = self.as_std_view()?; let v = sockopt::tcp_keepidle(fd)?; Ok(v.as_nanos().try_into().unwrap_or(u64::MAX)) } @@ -553,45 +551,45 @@ impl TcpSocket { pub(crate) fn set_keep_alive_idle_time(&mut self, value: u64) -> Result<(), ErrorCode> { let value = { let fd = self.as_std_view()?; - set_keep_alive_idle_time(&*fd, value)? + set_keep_alive_idle_time(fd, value)? }; self.options.set_keep_alive_idle_time(value); Ok(()) } pub(crate) fn keep_alive_interval(&self) -> Result { - let fd = &*self.as_std_view()?; + let fd = self.as_std_view()?; let v = sockopt::tcp_keepintvl(fd)?; Ok(v.as_nanos().try_into().unwrap_or(u64::MAX)) } pub(crate) fn set_keep_alive_interval(&self, value: u64) -> Result<(), ErrorCode> { - let fd = &*self.as_std_view()?; + let fd = self.as_std_view()?; set_keep_alive_interval(fd, Duration::from_nanos(value))?; Ok(()) } pub(crate) fn keep_alive_count(&self) -> Result { - let fd = &*self.as_std_view()?; + let fd = self.as_std_view()?; let v = sockopt::tcp_keepcnt(fd)?; Ok(v) } pub(crate) fn set_keep_alive_count(&self, value: u32) -> Result<(), ErrorCode> { - let fd = &*self.as_std_view()?; + let fd = self.as_std_view()?; set_keep_alive_count(fd, value)?; Ok(()) } pub(crate) fn hop_limit(&self) -> Result { - let fd = &*self.as_std_view()?; + let fd = self.as_std_view()?; let n = get_unicast_hop_limit(fd, self.family)?; Ok(n) } pub(crate) fn set_hop_limit(&mut self, value: u8) -> Result<(), ErrorCode> { { - let fd = &*self.as_std_view()?; + let fd = self.as_std_view()?; set_unicast_hop_limit(fd, self.family, value)?; } self.options.set_hop_limit(value); @@ -599,14 +597,14 @@ impl TcpSocket { } pub(crate) fn receive_buffer_size(&self) -> Result { - let fd = &*self.as_std_view()?; + let fd = self.as_std_view()?; let n = receive_buffer_size(fd)?; Ok(n) } pub(crate) fn set_receive_buffer_size(&mut self, value: u64) -> Result<(), ErrorCode> { let res = { - let fd = &*self.as_std_view()?; + let fd = self.as_std_view()?; set_receive_buffer_size(fd, value)? }; self.options.set_receive_buffer_size(res); @@ -614,14 +612,14 @@ impl TcpSocket { } pub(crate) fn send_buffer_size(&self) -> Result { - let fd = &*self.as_std_view()?; + let fd = self.as_std_view()?; let n = send_buffer_size(fd)?; Ok(n) } pub(crate) fn set_send_buffer_size(&mut self, value: u64) -> Result<(), ErrorCode> { let res = { - let fd = &*self.as_std_view()?; + let fd = self.as_std_view()?; set_send_buffer_size(fd, value)? }; self.options.set_send_buffer_size(res); @@ -838,3 +836,18 @@ mod does_not_inherit_options { } } } +enum StdView<'a> { + Socket(&'a tokio::net::TcpSocket), + Stream(&'a tokio::net::TcpStream), + Listener(&'a tokio::net::TcpListener), +} + +impl rustix::fd::AsFd for StdView<'_> { + fn as_fd(&self) -> rustix::fd::BorrowedFd<'_> { + match self { + StdView::Socket(socket) => socket.as_fd(), + StdView::Stream(stream) => stream.as_fd(), + StdView::Listener(listener) => listener.as_fd(), + } + } +} diff --git a/crates/wasi/src/sockets/udp.rs b/crates/wasi/src/sockets/udp.rs index a009f3bd221f..79c1431f1372 100644 --- a/crates/wasi/src/sockets/udp.rs +++ b/crates/wasi/src/sockets/udp.rs @@ -5,8 +5,6 @@ use crate::sockets::util::{ set_unicast_hop_limit, udp_bind, udp_connect, udp_disconnect, udp_socket, }; use crate::sockets::{SocketAddrCheck, SocketAddressFamily, WasiSocketsCtx}; -use io_lifetimes::AsSocketlike as _; -use io_lifetimes::raw::{FromRawSocketlike as _, IntoRawSocketlike as _}; use rustix::io::Errno; use std::net::SocketAddr; use std::sync::Arc; @@ -66,9 +64,7 @@ impl UdpSocket { } let socket = with_ambient_tokio_runtime(|| { - tokio::net::UdpSocket::try_from(unsafe { - std::net::UdpSocket::from_raw_socketlike(fd.into_raw_socketlike()) - }) + tokio::net::UdpSocket::try_from(std::net::UdpSocket::from(fd)) })?; Ok(Self { @@ -244,10 +240,7 @@ impl UdpSocket { if matches!(self.udp_state, UdpState::Default | UdpState::BindStarted) { return Err(ErrorCode::InvalidState); } - let addr = self - .socket - .as_socketlike_view::() - .local_addr()?; + let addr = self.socket.local_addr()?; Ok(addr) } @@ -255,10 +248,7 @@ impl UdpSocket { if !matches!(self.udp_state, UdpState::Connected(..)) { return Err(ErrorCode::InvalidState); } - let addr = self - .socket - .as_socketlike_view::() - .peer_addr()?; + let addr = self.socket.peer_addr()?; Ok(addr) } diff --git a/crates/wasi/src/sockets/util.rs b/crates/wasi/src/sockets/util.rs index 83c8f8863901..57a2b68ea076 100644 --- a/crates/wasi/src/sockets/util.rs +++ b/crates/wasi/src/sockets/util.rs @@ -486,3 +486,14 @@ pub fn implicit_bind_addr(family: SocketAddressFamily) -> SocketAddr { }; SocketAddr::new(ip, 0) } + +pub fn shutdown(socket: &tokio::net::TcpStream, how: std::net::Shutdown) -> Result<(), Errno> { + rustix::net::shutdown( + socket, + match how { + std::net::Shutdown::Read => rustix::net::Shutdown::Read, + std::net::Shutdown::Write => rustix::net::Shutdown::Write, + std::net::Shutdown::Both => rustix::net::Shutdown::Both, + }, + ) +} From f3f8ae2fcd915c03e717fd278b0aca0a69cd3d55 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 13 Jul 2026 12:09:27 -0700 Subject: [PATCH 2/2] Run format --- crates/wasi/src/p2/host/clocks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wasi/src/p2/host/clocks.rs b/crates/wasi/src/p2/host/clocks.rs index f9e4764e7c27..e89be8ddfff4 100644 --- a/crates/wasi/src/p2/host/clocks.rs +++ b/crates/wasi/src/p2/host/clocks.rs @@ -4,8 +4,8 @@ use crate::p2::bindings::{ clocks::monotonic_clock::{self, Duration as WasiDuration, Instant}, clocks::wall_clock::{self, Datetime}, }; -use std::time::SystemTime; use std::time::Duration; +use std::time::SystemTime; use wasmtime::component::Resource; use wasmtime_wasi_io::poll::{Pollable, subscribe};