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..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 cap_std::time::SystemTime; use std::time::Duration; +use std::time::SystemTime; 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, + }, + ) +}