From df559d9b1448dd298b3be782c4775365e76186c1 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 14 Jan 2021 19:03:55 -0800 Subject: [PATCH] Minimal optimization for cap-async-std. Use `spawn_blocking` to run important blocking tests off the main thread. --- Cargo.toml | 2 +- cap-async-std/Cargo.toml | 3 +- cap-async-std/README.md | 4 - cap-async-std/src/fs/dir.rs | 372 +++++--- cap-async-std/src/fs/dir_entry.rs | 2 + cap-async-std/src/fs/file.rs | 26 +- cap-async-std/src/fs_utf8/dir.rs | 141 +-- cap-async-std/src/fs_utf8/dir_entry.rs | 2 + cap-async-std/src/fs_utf8/file.rs | 19 +- cap-async-std/src/net/tcp_stream.rs | 1 + cap-async-std/src/os/unix/net/unix_stream.rs | 1 + cap-fs-ext/Cargo.toml | 8 +- cap-fs-ext/src/dir_ext.rs | 853 ++++++++++++++----- examples/async_std_fs_misc.rs | 37 +- 14 files changed, 1066 insertions(+), 405 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f9fa92e2..723a5f18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ publish = false rustc_version = "0.4.0" [dev-dependencies] -async-std = { version = "1.9.0", features = ["attributes"] } +async-std = { version = "1.10.0", features = ["attributes"] } anyhow = "1.0.37" cap-async-std = { path = "cap-async-std", version = "^0.17.1-alpha.0"} cap-fs-ext = { path = "cap-fs-ext", version = "^0.17.1-alpha.0"} diff --git a/cap-async-std/Cargo.toml b/cap-async-std/Cargo.toml index 471ddb3c..6cecb5a4 100644 --- a/cap-async-std/Cargo.toml +++ b/cap-async-std/Cargo.toml @@ -14,7 +14,8 @@ edition = "2018" [dependencies] arf-strings = { version = "0.4.0", optional = true } -async-std = { version = "1.9.0", features = ["attributes"] } +# Enable "unstable" for `spawn_blocking`. +async-std = { version = "1.10.0", features = ["attributes", "unstable"] } cap-primitives = { path = "../cap-primitives", version = "^0.17.1-alpha.0"} io-lifetimes = { version = "0.3.0", default-features = false, features = ["async-std"] } ipnet = "2.3.0" diff --git a/cap-async-std/README.md b/cap-async-std/README.md index 759007dd..22cf075b 100644 --- a/cap-async-std/README.md +++ b/cap-async-std/README.md @@ -15,9 +15,5 @@ This crate provides a capability-based version of [`async-std`]. See the [toplevel README.md] for more information about capability-based security. -At the moment, `cap-async-std` is a very rudimentary translation of [`cap-std`] to -`async-std`. It hasn't yet been optimized to make effective use of `async`. - [`async-std`]: https://crates.io/crates/async-std -[`cap-std`]: https://github.com/bytecodealliance/cap-std/blob/main/cap-std/README.md [toplevel README.md]: https://github.com/bytecodealliance/cap-std/blob/main/README.md diff --git a/cap-async-std/src/fs/dir.rs b/cap-async-std/src/fs/dir.rs index bb00d0ee..2fd2ba5a 100644 --- a/cap-async-std/src/fs/dir.rs +++ b/cap-async-std/src/fs/dir.rs @@ -5,6 +5,7 @@ use async_std::os::wasi::{ io::{AsRawFd, IntoRawFd}, }; use async_std::path::{Path, PathBuf}; +use async_std::task::spawn_blocking; use async_std::{fs, io}; use cap_primitives::fs::{ canonicalize, copy, create_dir, hard_link, open, open_ambient_dir, open_dir, read_base_dir, @@ -42,6 +43,7 @@ use { /// since absolute paths don't interoperate well with the capability model. /// /// [functions in `async_std::fs`]: https://docs.rs/async-std/latest/async_std/fs/index.html#functions +#[derive(Clone)] pub struct Dir { std_file: fs::File, } @@ -73,8 +75,8 @@ impl Dir { /// This corresponds to [`async_std::fs::File::open`], but only accesses /// paths relative to `self`. #[inline] - pub fn open>(&self, path: P) -> io::Result { - self.open_with(path, OpenOptions::new().read(true)) + pub async fn open>(&self, path: P) -> io::Result { + self.open_with(path, OpenOptions::new().read(true)).await } /// Opens a file at `path` with the options specified by `options`. @@ -84,28 +86,51 @@ impl Dir { /// Instead of being a method on `OpenOptions`, this is a method on `Dir`, /// and it only accesses paths relative to `self`. #[inline] - pub fn open_with>(&self, path: P, options: &OpenOptions) -> io::Result { - let file = self.std_file.as_filelike_view::(); - Self::_open_with(&file, path.as_ref(), options) + pub async fn open_with>( + &self, + path: P, + options: &OpenOptions, + ) -> io::Result { + self._open_with(path.as_ref(), options).await } #[cfg(not(target_os = "wasi"))] - fn _open_with(file: &std::fs::File, path: &Path, options: &OpenOptions) -> io::Result { - let file = open(file, path.as_ref(), options)?.into(); + async fn _open_with(&self, path: &Path, options: &OpenOptions) -> io::Result { + let path = path.to_path_buf(); + let clone = self.clone(); + let options = options.clone(); + let file = spawn_blocking(move || { + open( + &*clone.as_filelike_view::(), + path.as_ref(), + &options, + ) + }) + .await? + .into(); Ok(File::from_std(file, ambient_authority())) } #[cfg(target_os = "wasi")] - fn _open_with(file: &std::fs::File, path: &Path, options: &OpenOptions) -> io::Result { + async fn _open_with( + file: &std::fs::File, + path: &Path, + options: &OpenOptions, + ) -> io::Result { let file = options.open_at(&self.std_file, path)?.into(); Ok(File::from_std(file, ambient_authority())) } /// Attempts to open a directory. #[inline] - pub fn open_dir>(&self, path: P) -> io::Result { - let file = self.std_file.as_filelike_view::(); - let dir = open_dir(&file, path.as_ref().as_ref())?.into(); + pub async fn open_dir>(&self, path: P) -> io::Result { + let path = path.as_ref().to_path_buf(); + let clone = self.clone(); + let dir = spawn_blocking(move || { + open_dir(&clone.as_filelike_view::(), path.as_ref()) + }) + .await? + .into(); Ok(Self::from_std_file(dir, ambient_authority())) } @@ -113,6 +138,8 @@ impl Dir { /// /// This corresponds to [`async_std::fs::create_dir`], but only accesses /// paths relative to `self`. + /// + /// TODO: async: fix this when we fix https://github.com/bytecodealliance/cap-std/issues/51 #[inline] pub fn create_dir>(&self, path: P) -> io::Result<()> { self._create_dir_one(path.as_ref(), &DirOptions::new()) @@ -123,6 +150,8 @@ impl Dir { /// /// This corresponds to [`async_std::fs::create_dir_all`], but only /// accesses paths relative to `self`. + /// + /// TODO: async: fix this when we fix https://github.com/bytecodealliance/cap-std/issues/51 #[inline] pub fn create_dir_all>(&self, path: P) -> io::Result<()> { self._create_dir_all(path.as_ref(), &DirOptions::new()) @@ -132,6 +161,8 @@ impl Dir { /// builder. /// /// This corresponds to [`async_std::fs::DirBuilder::create`]. + /// + /// TODO: async: fix this when we fix https://github.com/bytecodealliance/cap-std/issues/51 #[inline] pub fn create_dir_with>( &self, @@ -146,9 +177,13 @@ impl Dir { } } + #[inline] fn _create_dir_one(&self, path: &Path, dir_options: &DirOptions) -> io::Result<()> { - let file = self.std_file.as_filelike_view::(); - create_dir(&file, path.as_ref(), dir_options) + create_dir( + &self.as_filelike_view::(), + path.as_ref(), + dir_options, + ) } fn _create_dir_all(&self, path: &Path, dir_options: &DirOptions) -> io::Result<()> { @@ -159,7 +194,7 @@ impl Dir { match self._create_dir_one(path, dir_options) { Ok(()) => return Ok(()), Err(ref e) if e.kind() == io::ErrorKind::NotFound => {} - Err(_) if self.is_dir(path) => return Ok(()), + Err(_) if self.is_dir_blocking(path) => return Ok(()), Err(e) => return Err(e), } match path.parent() { @@ -173,7 +208,7 @@ impl Dir { } match self._create_dir_one(path, dir_options) { Ok(()) => Ok(()), - Err(_) if self.is_dir(path) => Ok(()), + Err(_) if self.is_dir_blocking(path) => Ok(()), Err(e) => Err(e), } } @@ -183,11 +218,12 @@ impl Dir { /// This corresponds to [`async_std::fs::File::create`], but only accesses /// paths relative to `self`. #[inline] - pub fn create>(&self, path: P) -> io::Result { + pub async fn create>(&self, path: P) -> io::Result { self.open_with( path, OpenOptions::new().write(true).create(true).truncate(true), ) + .await } /// Returns the canonical form of a path with all intermediate components @@ -197,9 +233,14 @@ impl Dir { /// returning an absolute path, returns a path relative to the /// directory represented by `self`. #[inline] - pub fn canonicalize>(&self, path: P) -> io::Result { - let file = self.std_file.as_filelike_view::(); - canonicalize(&file, path.as_ref().as_ref()).map(PathBuf::from) + pub async fn canonicalize>(&self, path: P) -> io::Result { + let path = path.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + canonicalize(&clone.as_filelike_view::(), path.as_ref()) + }) + .await + .map(PathBuf::from) } /// Copies the contents of one file to another. This function will also @@ -215,14 +256,19 @@ impl Dir { to_dir: &Self, to: Q, ) -> io::Result { - let from_file = self.std_file.as_filelike_view::(); - let to_file = to_dir.std_file.as_filelike_view::(); - copy( - &from_file, - from.as_ref().as_ref(), - &to_file, - to.as_ref().as_ref(), - ) + let from = from.as_ref().to_path_buf(); + let to = to.as_ref().to_path_buf(); + let from_clone = self.clone(); + let to_clone = to_dir.clone(); + spawn_blocking(move || { + copy( + &from_clone.as_filelike_view::(), + from.as_ref(), + &to_clone.as_filelike_view::(), + to.as_ref(), + ) + }) + .await } /// Creates a new hard link on a filesystem. @@ -230,20 +276,25 @@ impl Dir { /// This corresponds to [`async_std::fs::hard_link`], but only accesses /// paths relative to `self`. #[inline] - pub fn hard_link, Q: AsRef>( + pub async fn hard_link, Q: AsRef>( &self, src: P, dst_dir: &Self, dst: Q, ) -> io::Result<()> { - let src_file = self.std_file.as_filelike_view::(); - let dst_file = dst_dir.std_file.as_filelike_view::(); - hard_link( - &src_file, - src.as_ref().as_ref(), - &dst_file, - dst.as_ref().as_ref(), - ) + let dst = dst.as_ref().to_path_buf(); + let src = src.as_ref().to_path_buf(); + let src_clone = self.clone(); + let dst_clone = dst_dir.clone(); + spawn_blocking(move || { + hard_link( + &src_clone.as_filelike_view::(), + src.as_ref(), + &dst_clone.as_filelike_view::(), + dst.as_ref(), + ) + }) + .await } /// Given a path, query the file system to get information about a file, @@ -252,16 +303,37 @@ impl Dir { /// This corresponds to [`async_std::fs::metadata`], but only accesses /// paths relative to `self`. #[inline] - pub fn metadata>(&self, path: P) -> io::Result { - let file = self.std_file.as_filelike_view::(); - stat(&file, path.as_ref().as_ref(), FollowSymlinks::Yes) + pub async fn metadata>(&self, path: P) -> io::Result { + let path = path.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + stat( + &clone.as_filelike_view::(), + path.as_ref(), + FollowSymlinks::Yes, + ) + }) + .await + } + + /// TODO: Remove this once `create_dir` and friends are async. + #[inline] + fn metadata_blocking>(&self, path: P) -> io::Result { + let path = path.as_ref().to_path_buf(); + stat( + &self.as_filelike_view::(), + path.as_ref(), + FollowSymlinks::Yes, + ) } /// Returns an iterator over the entries within `self`. #[inline] - pub fn entries(&self) -> io::Result { - let file = self.std_file.as_filelike_view::(); - read_base_dir(&file).map(|inner| ReadDir { inner }) + pub async fn entries(&self) -> io::Result { + let clone = self.clone(); + spawn_blocking(move || read_base_dir(&clone.as_filelike_view::())) + .await + .map(|inner| ReadDir { inner }) } /// Returns an iterator over the entries within a directory. @@ -269,9 +341,12 @@ impl Dir { /// This corresponds to [`async_std::fs::read_dir`], but only accesses /// paths relative to `self`. #[inline] - pub fn read_dir>(&self, path: P) -> io::Result { - let file = self.std_file.as_filelike_view::(); - read_dir(&file, path.as_ref().as_ref()).map(|inner| ReadDir { inner }) + pub async fn read_dir>(&self, path: P) -> io::Result { + let path = path.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || read_dir(&clone.as_filelike_view::(), path.as_ref())) + .await + .map(|inner| ReadDir { inner }) } /// Read the entire contents of a file into a bytes vector. @@ -281,8 +356,8 @@ impl Dir { #[inline] pub async fn read>(&self, path: P) -> io::Result> { use async_std::prelude::*; - let mut file = self.open(path)?; - let mut bytes = Vec::with_capacity(initial_buffer_size(&file)); + let mut file = self.open(path).await?; + let mut bytes = Vec::with_capacity(initial_buffer_size(&file).await); file.read_to_end(&mut bytes).await?; Ok(bytes) } @@ -292,9 +367,12 @@ impl Dir { /// This corresponds to [`async_std::fs::read_link`], but only accesses /// paths relative to `self`. #[inline] - pub fn read_link>(&self, path: P) -> io::Result { - let file = self.std_file.as_filelike_view::(); - read_link(&file, path.as_ref().as_ref()).map(PathBuf::from) + pub async fn read_link>(&self, path: P) -> io::Result { + let path = path.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || read_link(&clone.as_filelike_view::(), path.as_ref())) + .await + .map(PathBuf::from) } /// Read the entire contents of a file into a string. @@ -305,7 +383,7 @@ impl Dir { pub async fn read_to_string>(&self, path: P) -> io::Result { use async_std::prelude::*; let mut s = String::new(); - self.open(path)?.read_to_string(&mut s).await?; + self.open(path).await?.read_to_string(&mut s).await?; Ok(s) } @@ -314,9 +392,13 @@ impl Dir { /// This corresponds to [`async_std::fs::remove_dir`], but only accesses /// paths relative to `self`. #[inline] - pub fn remove_dir>(&self, path: P) -> io::Result<()> { - let file = self.std_file.as_filelike_view::(); - remove_dir(&file, path.as_ref().as_ref()) + pub async fn remove_dir>(&self, path: P) -> io::Result<()> { + let path = path.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + remove_dir(&clone.as_filelike_view::(), path.as_ref()) + }) + .await } /// Removes a directory at this path, after removing all its contents. Use @@ -326,8 +408,12 @@ impl Dir { /// accesses paths relative to `self`. #[inline] pub async fn remove_dir_all>(&self, path: P) -> io::Result<()> { - let file = self.std_file.as_filelike_view::(); - remove_dir_all(&file, path.as_ref().as_ref()) + let path = path.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + remove_dir_all(&clone.as_filelike_view::(), path.as_ref()) + }) + .await } /// Remove the directory referenced by `self` and consume `self`. @@ -336,8 +422,9 @@ impl Dir { /// as much as possible, removal is not guaranteed to be atomic with /// respect to a concurrent rename of the directory. #[inline] - pub fn remove_open_dir(self) -> io::Result<()> { - remove_open_dir(std::fs::File::from_into_filelike(self.std_file)) + pub async fn remove_open_dir(self) -> io::Result<()> { + let file = std::fs::File::from_into_filelike(self.std_file); + spawn_blocking(move || remove_open_dir(file)).await } /// Removes the directory referenced by `self`, after removing all its @@ -347,8 +434,9 @@ impl Dir { /// as much as possible, removal is not guaranteed to be atomic with /// respect to a concurrent rename of the directory. #[inline] - pub fn remove_open_dir_all(self) -> io::Result<()> { - remove_open_dir_all(std::fs::File::from_into_filelike(self.std_file)) + pub async fn remove_open_dir_all(self) -> io::Result<()> { + let file = std::fs::File::from_into_filelike(self.std_file); + spawn_blocking(move || remove_open_dir_all(file)).await } /// Removes a file from a filesystem. @@ -356,9 +444,13 @@ impl Dir { /// This corresponds to [`async_std::fs::remove_file`], but only accesses /// paths relative to `self`. #[inline] - pub fn remove_file>(&self, path: P) -> io::Result<()> { - let file = self.std_file.as_filelike_view::(); - remove_file(&file, path.as_ref().as_ref()) + pub async fn remove_file>(&self, path: P) -> io::Result<()> { + let path = path.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + remove_file(&clone.as_filelike_view::(), path.as_ref()) + }) + .await } /// Rename a file or directory to a new name, replacing the original file @@ -367,20 +459,25 @@ impl Dir { /// This corresponds to [`async_std::fs::rename`], but only accesses paths /// relative to `self`. #[inline] - pub fn rename, Q: AsRef>( + pub async fn rename, Q: AsRef>( &self, from: P, to_dir: &Self, to: Q, ) -> io::Result<()> { - let file = self.std_file.as_filelike_view::(); - let to_file = to_dir.std_file.as_filelike_view::(); - rename( - &file, - from.as_ref().as_ref(), - &to_file, - to.as_ref().as_ref(), - ) + let from = from.as_ref().to_path_buf(); + let to = to.as_ref().to_path_buf(); + let clone = self.clone(); + let to_clone = to_dir.clone(); + spawn_blocking(move || { + rename( + &clone.as_filelike_view::(), + from.as_ref(), + &to_clone.as_filelike_view::(), + to.as_ref(), + ) + }) + .await } /// Changes the permissions found on a file or a directory. @@ -389,9 +486,21 @@ impl Dir { /// accesses paths relative to `self`. Also, on some platforms, this /// function may fail if the file or directory cannot be opened for /// reading or writing first. - pub fn set_permissions>(&self, path: P, perm: Permissions) -> io::Result<()> { - let file = self.std_file.as_filelike_view::(); - set_permissions(&file, path.as_ref().as_ref(), perm) + pub async fn set_permissions>( + &self, + path: P, + perm: Permissions, + ) -> io::Result<()> { + let path = path.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + set_permissions( + &clone.as_filelike_view::(), + path.as_ref(), + perm, + ) + }) + .await } /// Query the metadata about a file without following symlinks. @@ -399,9 +508,17 @@ impl Dir { /// This corresponds to [`async_std::fs::symlink_metadata`], but only /// accesses paths relative to `self`. #[inline] - pub fn symlink_metadata>(&self, path: P) -> io::Result { - let file = self.std_file.as_filelike_view::(); - stat(&file, path.as_ref().as_ref(), FollowSymlinks::No) + pub async fn symlink_metadata>(&self, path: P) -> io::Result { + let path = path.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + stat( + &clone.as_filelike_view::(), + path.as_ref(), + FollowSymlinks::No, + ) + }) + .await } /// Write a slice as the entire contents of a file. @@ -415,7 +532,7 @@ impl Dir { contents: C, ) -> io::Result<()> { use async_std::prelude::*; - let mut file = self.create(path)?; + let mut file = self.create(path).await?; file.write_all(contents.as_ref()).await } @@ -427,9 +544,18 @@ impl Dir { /// [`async_std::os::unix::fs::symlink`]: https://docs.rs/async-std/latest/async_std/os/unix/fs/fn.symlink.html #[cfg(not(windows))] #[inline] - pub fn symlink, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { - let file = self.std_file.as_filelike_view::(); - symlink(src.as_ref().as_ref(), &file, dst.as_ref().as_ref()) + pub async fn symlink, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { + let src = src.as_ref().to_path_buf(); + let dst = dst.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + symlink( + src.as_ref(), + &clone.as_filelike_view::(), + dst.as_ref(), + ) + }) + .await } /// Creates a new file symbolic link on a filesystem. @@ -440,9 +566,22 @@ impl Dir { /// [`async_std::os::windows::fs::symlink_file`]: https://docs.rs/async-std/latest/async_std/os/windows/fs/fn.symlink_file.html #[cfg(windows)] #[inline] - pub fn symlink_file, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { - let file = self.std_file.as_filelike_view::(); - symlink_file(src.as_ref().as_ref(), &file, dst.as_ref().as_ref()) + pub async fn symlink_file, Q: AsRef>( + &self, + src: P, + dst: Q, + ) -> io::Result<()> { + let src = src.as_ref().to_path_buf(); + let dst = dst.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + symlink_file( + src.as_ref(), + &clone.as_filelike_view::(), + dst.as_ref(), + ) + }) + .await } /// Creates a new directory symlink on a filesystem. @@ -453,9 +592,22 @@ impl Dir { /// [`async_std::os::windows::fs::symlink_dir`]: https://docs.rs/async-std/latest/async_std/os/windows/fs/fn.symlink_dir.html #[cfg(windows)] #[inline] - pub fn symlink_dir, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { - let file = self.std_file.as_filelike_view::(); - symlink_dir(src.as_ref().as_ref(), &file, dst.as_ref().as_ref()) + pub async fn symlink_dir, Q: AsRef>( + &self, + src: P, + dst: Q, + ) -> io::Result<()> { + let src = src.as_ref().to_path_buf(); + let dst = dst.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + symlink_dir( + src.as_ref(), + &clone.as_filelike_view::(), + dst.as_ref(), + ) + }) + .await } /// Creates a new `UnixListener` bound to the specified socket. @@ -468,7 +620,7 @@ impl Dir { /// [`async_std::os::unix::net::UnixListener::bind`]: https://docs.rs/async-std/latest/async_std/os/unix/net/struct.UnixListener.html#method.bind #[cfg(unix)] #[inline] - pub fn bind_unix_listener>(&self, path: P) -> io::Result { + pub async fn bind_unix_listener>(&self, path: P) -> io::Result { todo!( "Dir::bind_unix_listener({:?}, {})", self.std_file, @@ -486,7 +638,7 @@ impl Dir { /// [`async_std::os::unix::net::UnixStream::connect`]: https://docs.rs/async-std/latest/async_std/os/unix/net/struct.UnixStream.html#method.connect #[cfg(unix)] #[inline] - pub fn connect_unix_stream>(&self, path: P) -> io::Result { + pub async fn connect_unix_stream>(&self, path: P) -> io::Result { todo!( "Dir::connect_unix_stream({:?}, {})", self.std_file, @@ -504,7 +656,7 @@ impl Dir { /// [`async_std::os::unix::net::UnixDatagram::bind`]: https://docs.rs/async-std/latest/async_std/os/unix/net/struct.UnixDatagram.html#method.bind #[cfg(unix)] #[inline] - pub fn bind_unix_datagram>(&self, path: P) -> io::Result { + pub async fn bind_unix_datagram>(&self, path: P) -> io::Result { todo!( "Dir::bind_unix_datagram({:?}, {})", self.std_file, @@ -523,7 +675,7 @@ impl Dir { /// [`async_std::os::unix::net::UnixDatagram::connect`]: https://docs.rs/async-std/latest/async_std/os/unix/net/struct.UnixDatagram.html#method.connect #[cfg(unix)] #[inline] - pub fn connect_unix_datagram>( + pub async fn connect_unix_datagram>( &self, _unix_datagram: &UnixDatagram, path: P, @@ -546,7 +698,7 @@ impl Dir { /// [`async_std::os::unix::net::UnixDatagram::send_to`]: https://docs.rs/async-std/latest/async_std/os/unix/net/struct.UnixDatagram.html#method.send_to #[cfg(unix)] #[inline] - pub fn send_to_unix_datagram_addr>( + pub async fn send_to_unix_datagram_addr>( &self, _unix_datagram: &UnixDatagram, buf: &[u8], @@ -567,8 +719,8 @@ impl Dir { /// This corresponds to [`async_std::path::Path::exists`], but only /// accesses paths relative to `self`. #[inline] - pub fn exists>(&self, path: P) -> bool { - self.metadata(path).is_ok() + pub async fn exists>(&self, path: P) -> bool { + self.metadata(path).await.is_ok() } /// Returns `true` if the path exists on disk and is pointing at a regular @@ -577,8 +729,11 @@ impl Dir { /// This corresponds to [`async_std::path::Path::is_file`], but only /// accesses paths relative to `self`. #[inline] - pub fn is_file>(&self, path: P) -> bool { - self.metadata(path).map(|m| m.is_file()).unwrap_or(false) + pub async fn is_file>(&self, path: P) -> bool { + self.metadata(path) + .await + .map(|m| m.is_file()) + .unwrap_or(false) } /// Checks if `path` is a directory. @@ -588,8 +743,19 @@ impl Dir { /// symbolic links to query information about the destination file. In case /// of broken symbolic links, this will return `false`. #[inline] - pub fn is_dir>(&self, path: P) -> bool { - self.metadata(path).map(|m| m.is_dir()).unwrap_or(false) + pub async fn is_dir>(&self, path: P) -> bool { + self.metadata(path) + .await + .map(|m| m.is_dir()) + .unwrap_or(false) + } + + /// TODO: Remove this once `create_dir` and friends are async. + #[inline] + fn is_dir_blocking>(&self, path: P) -> bool { + self.metadata_blocking(path) + .map(|m| m.is_dir()) + .unwrap_or(false) } /// Constructs a new instance of `Self` by opening the given path as a @@ -600,11 +766,13 @@ impl Dir { /// This function is not sandboxed and may access any path that the host /// process has access to. #[inline] - pub fn open_ambient_dir>( + pub async fn open_ambient_dir>( path: P, ambient_authority: AmbientAuthority, ) -> io::Result { - open_ambient_dir(path.as_ref().as_ref(), ambient_authority) + let path = path.as_ref().to_path_buf(); + spawn_blocking(move || open_ambient_dir(path.as_ref(), ambient_authority)) + .await .map(|f| Self::from_std_file(f.into(), ambient_authority)) } } @@ -731,7 +899,7 @@ unsafe impl OwnsRaw for Dir {} /// /// Derived from the function of the same name in Rust's library/std/src/fs.rs /// at revision 108e90ca78f052c0c1c49c42a22c85620be19712. -fn initial_buffer_size(file: &File) -> usize { +async fn initial_buffer_size(file: &File) -> usize { // Allocate one extra byte so the buffer doesn't need to grow before the // final `read` call at the end of the file. Don't worry about `usize` // overflow because reading will fail regardless in that case. diff --git a/cap-async-std/src/fs/dir_entry.rs b/cap-async-std/src/fs/dir_entry.rs index 930547c0..c364afbe 100644 --- a/cap-async-std/src/fs/dir_entry.rs +++ b/cap-async-std/src/fs/dir_entry.rs @@ -23,6 +23,8 @@ use std::fmt; /// Note that there is no `from_std` method, as `async_std::fs::DirEntry` /// doesn't provide a way to construct a `DirEntry` without opening directories /// by ambient paths. +/// +/// TODO: async pub struct DirEntry { pub(crate) inner: cap_primitives::fs::DirEntry, } diff --git a/cap-async-std/src/fs/file.rs b/cap-async-std/src/fs/file.rs index ba935dac..82c1dcc8 100644 --- a/cap-async-std/src/fs/file.rs +++ b/cap-async-std/src/fs/file.rs @@ -5,7 +5,7 @@ use async_std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write}; use async_std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; #[cfg(target_os = "wasi")] use async_std::os::wasi::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; -use async_std::task::{Context, Poll}; +use async_std::task::{spawn_blocking, Context, Poll}; use cap_primitives::fs::{is_file_read_write, open_ambient}; use cap_primitives::{ambient_authority, AmbientAuthority}; use io_lifetimes::AsFilelike; @@ -34,6 +34,7 @@ use { /// [`Dir`]: crate::fs::Dir /// [`Dir::open`]: crate::fs::Dir::open /// [`Dir::create`]: crate::fs::Dir::create +#[derive(Clone)] pub struct File { pub(crate) std: fs::File, } @@ -115,15 +116,19 @@ impl File { /// This function is not sandboxed and may access any path that the host /// process has access to. #[inline] - pub fn open_ambient>( + pub async fn open_ambient>( path: P, ambient_authority: AmbientAuthority, ) -> io::Result { - open_ambient( - path.as_ref(), - &OpenOptions::new().read(true), - ambient_authority, - ) + let path = path.as_ref().to_path_buf(); + spawn_blocking(move || { + open_ambient( + path.as_ref(), + &OpenOptions::new().read(true), + ambient_authority, + ) + }) + .await .map(|f| Self::from_std(f.into(), ambient_authority)) } @@ -136,12 +141,15 @@ impl File { /// This function is not sandboxed and may access any path that the host /// process has access to. #[inline] - pub fn open_ambient_with>( + pub async fn open_ambient_with>( path: P, options: &OpenOptions, ambient_authority: AmbientAuthority, ) -> io::Result { - open_ambient(path.as_ref(), options, ambient_authority) + let path = path.as_ref().to_path_buf(); + let options = options.clone(); + spawn_blocking(move || open_ambient(path.as_ref(), &options, ambient_authority)) + .await .map(|f| Self::from_std(f.into(), ambient_authority)) } } diff --git a/cap-async-std/src/fs_utf8/dir.rs b/cap-async-std/src/fs_utf8/dir.rs index 0a2fe571..1d323737 100644 --- a/cap-async-std/src/fs_utf8/dir.rs +++ b/cap-async-std/src/fs_utf8/dir.rs @@ -29,6 +29,7 @@ use { /// since absolute paths don't interoperate well with the capability model. /// /// [functions in `async_std::fs`]: https://docs.rs/async-std/latest/async_std/fs/index.html#functions +#[derive(Clone)] pub struct Dir { cap_std: crate::fs::Dir, } @@ -60,9 +61,9 @@ impl Dir { /// This corresponds to [`async_std::fs::File::open`], but only accesses /// paths relative to `self`. #[inline] - pub fn open>(&self, path: P) -> io::Result { + pub async fn open>(&self, path: P) -> io::Result { let path = from_utf8(path)?; - self.cap_std.open(path).map(File::from_cap_std) + self.cap_std.open(path).await.map(File::from_cap_std) } /// Opens a file at `path` with the options specified by `options`. @@ -72,24 +73,31 @@ impl Dir { /// Instead of being a method on `OpenOptions`, this is a method on `Dir`, /// and it only accesses paths relative to `self`. #[inline] - pub fn open_with>(&self, path: P, options: &OpenOptions) -> io::Result { + pub async fn open_with>( + &self, + path: P, + options: &OpenOptions, + ) -> io::Result { let path = from_utf8(path)?; self.cap_std .open_with(path, options) + .await .map(File::from_cap_std) } /// Attempts to open a directory. #[inline] - pub fn open_dir>(&self, path: P) -> io::Result { + pub async fn open_dir>(&self, path: P) -> io::Result { let path = from_utf8(path)?; - self.cap_std.open_dir(path).map(Self::from_cap_std) + self.cap_std.open_dir(path).await.map(Self::from_cap_std) } /// Creates a new, empty directory at the provided path. /// /// This corresponds to [`async_std::fs::create_dir`], but only accesses /// paths relative to `self`. + /// + /// TODO: async: fix this when we fix https://github.com/bytecodealliance/cap-std/issues/51 #[inline] pub fn create_dir>(&self, path: P) -> io::Result<()> { let path = from_utf8(path)?; @@ -101,6 +109,8 @@ impl Dir { /// /// This corresponds to [`async_std::fs::create_dir_all`], but only /// accesses paths relative to `self`. + /// + /// TODO: async: fix this when we fix https://github.com/bytecodealliance/cap-std/issues/51 #[inline] pub fn create_dir_all>(&self, path: P) -> io::Result<()> { let path = from_utf8(path)?; @@ -111,6 +121,8 @@ impl Dir { /// builder. /// /// This corresponds to [`async_std::fs::DirBuilder::create`]. + /// + /// TODO: async: fix this when we fix https://github.com/bytecodealliance/cap-std/issues/51 #[inline] pub fn create_dir_with>( &self, @@ -126,9 +138,9 @@ impl Dir { /// This corresponds to [`async_std::fs::File::create`], but only accesses /// paths relative to `self`. #[inline] - pub fn create>(&self, path: P) -> io::Result { + pub async fn create>(&self, path: P) -> io::Result { let path = from_utf8(path)?; - self.cap_std.create(path).map(File::from_cap_std) + self.cap_std.create(path).await.map(File::from_cap_std) } /// Returns the canonical form of a path with all intermediate components @@ -138,9 +150,9 @@ impl Dir { /// returning an absolute path, returns a path relative to the /// directory represented by `self`. #[inline] - pub fn canonicalize>(&self, path: P) -> io::Result { + pub async fn canonicalize>(&self, path: P) -> io::Result { let path = from_utf8(path)?; - self.cap_std.canonicalize(path).and_then(to_utf8) + self.cap_std.canonicalize(path).await.and_then(to_utf8) } /// Copies the contents of one file to another. This function will also @@ -166,7 +178,7 @@ impl Dir { /// This corresponds to [`async_std::fs::hard_link`], but only accesses /// paths relative to `self`. #[inline] - pub fn hard_link, Q: AsRef>( + pub async fn hard_link, Q: AsRef>( &self, src: P, dst_dir: &Self, @@ -174,7 +186,7 @@ impl Dir { ) -> io::Result<()> { let src = from_utf8(src)?; let dst = from_utf8(dst)?; - self.cap_std.hard_link(src, &dst_dir.cap_std, dst) + self.cap_std.hard_link(src, &dst_dir.cap_std, dst).await } /// Given a path, query the file system to get information about a file, @@ -183,15 +195,15 @@ impl Dir { /// This corresponds to [`async_std::fs::metadata`], but only accesses /// paths relative to `self`. #[inline] - pub fn metadata>(&self, path: P) -> io::Result { + pub async fn metadata>(&self, path: P) -> io::Result { let path = from_utf8(path)?; - self.cap_std.metadata(path) + self.cap_std.metadata(path).await } /// Returns an iterator over the entries within `self`. #[inline] - pub fn entries(&self) -> io::Result { - self.cap_std.entries().map(ReadDir::from_cap_std) + pub async fn entries(&self) -> io::Result { + self.cap_std.entries().await.map(ReadDir::from_cap_std) } /// Returns an iterator over the entries within a directory. @@ -199,9 +211,9 @@ impl Dir { /// This corresponds to [`async_std::fs::read_dir`], but only accesses /// paths relative to `self`. #[inline] - pub fn read_dir>(&self, path: P) -> io::Result { + pub async fn read_dir>(&self, path: P) -> io::Result { let path = from_utf8(path)?; - self.cap_std.read_dir(path).map(ReadDir::from_cap_std) + self.cap_std.read_dir(path).await.map(ReadDir::from_cap_std) } /// Read the entire contents of a file into a bytes vector. @@ -219,9 +231,9 @@ impl Dir { /// This corresponds to [`async_std::fs::read_link`], but only accesses /// paths relative to `self`. #[inline] - pub fn read_link>(&self, path: P) -> io::Result { + pub async fn read_link>(&self, path: P) -> io::Result { let path = from_utf8(path)?; - self.cap_std.read_link(path).and_then(to_utf8) + self.cap_std.read_link(path).await.and_then(to_utf8) } /// Read the entire contents of a file into a string. @@ -239,9 +251,9 @@ impl Dir { /// This corresponds to [`async_std::fs::remove_dir`], but only accesses /// paths relative to `self`. #[inline] - pub fn remove_dir>(&self, path: P) -> io::Result<()> { + pub async fn remove_dir>(&self, path: P) -> io::Result<()> { let path = from_utf8(path)?; - self.cap_std.remove_dir(path) + self.cap_std.remove_dir(path).await } /// Removes a directory at this path, after removing all its contents. Use @@ -261,8 +273,8 @@ impl Dir { /// as much as possible, removal is not guaranteed to be atomic with /// respect to a concurrent rename of the directory. #[inline] - pub fn remove_open_dir(self) -> io::Result<()> { - self.cap_std.remove_open_dir() + pub async fn remove_open_dir(self) -> io::Result<()> { + self.cap_std.remove_open_dir().await } /// Removes the directory referenced by `self`, after removing all its @@ -272,8 +284,8 @@ impl Dir { /// as much as possible, removal is not guaranteed to be atomic with /// respect to a concurrent rename of the directory. #[inline] - pub fn remove_open_dir_all(self) -> io::Result<()> { - self.cap_std.remove_open_dir_all() + pub async fn remove_open_dir_all(self) -> io::Result<()> { + self.cap_std.remove_open_dir_all().await } /// Removes a file from a filesystem. @@ -281,9 +293,9 @@ impl Dir { /// This corresponds to [`async_std::fs::remove_file`], but only accesses /// paths relative to `self`. #[inline] - pub fn remove_file>(&self, path: P) -> io::Result<()> { + pub async fn remove_file>(&self, path: P) -> io::Result<()> { let path = from_utf8(path)?; - self.cap_std.remove_file(path) + self.cap_std.remove_file(path).await } /// Rename a file or directory to a new name, replacing the original file @@ -292,7 +304,7 @@ impl Dir { /// This corresponds to [`async_std::fs::rename`], but only accesses paths /// relative to `self`. #[inline] - pub fn rename, Q: AsRef>( + pub async fn rename, Q: AsRef>( &self, from: P, to_dir: &Self, @@ -300,7 +312,7 @@ impl Dir { ) -> io::Result<()> { let from = from_utf8(from)?; let to = from_utf8(to)?; - self.cap_std.rename(from, &to_dir.cap_std, to) + self.cap_std.rename(from, &to_dir.cap_std, to).await } /// Changes the permissions found on a file or a directory. @@ -309,9 +321,13 @@ impl Dir { /// accesses paths relative to `self`. Also, on some platforms, this /// function may fail if the file or directory cannot be opened for /// reading or writing first. - pub fn set_permissions>(&self, path: P, perm: Permissions) -> io::Result<()> { + pub async fn set_permissions>( + &self, + path: P, + perm: Permissions, + ) -> io::Result<()> { let path = from_utf8(path)?; - self.cap_std.set_permissions(path, perm) + self.cap_std.set_permissions(path, perm).await } /// Query the metadata about a file without following symlinks. @@ -319,9 +335,9 @@ impl Dir { /// This corresponds to [`async_std::fs::symlink_metadata`], but only /// accesses paths relative to `self`. #[inline] - pub fn symlink_metadata>(&self, path: P) -> io::Result { + pub async fn symlink_metadata>(&self, path: P) -> io::Result { let path = from_utf8(path)?; - self.cap_std.symlink_metadata(path) + self.cap_std.symlink_metadata(path).await } /// Write a slice as the entire contents of a file. @@ -346,10 +362,10 @@ impl Dir { /// [`async_std::os::unix::fs::symlink`]: https://docs.rs/async-std/latest/async_std/os/unix/fs/fn.symlink.html #[cfg(not(windows))] #[inline] - pub fn symlink, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { + pub async fn symlink, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { let src = from_utf8(src)?; let dst = from_utf8(dst)?; - self.cap_std.symlink(src, dst) + self.cap_std.symlink(src, dst).await } /// Creates a new file symbolic link on a filesystem. @@ -360,10 +376,14 @@ impl Dir { /// [`async_std::os::windows::fs::symlink_file`]: https://docs.rs/async-std/latest/async_std/os/windows/fs/fn.symlink_file.html #[cfg(windows)] #[inline] - pub fn symlink_file, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { + pub async fn symlink_file, Q: AsRef>( + &self, + src: P, + dst: Q, + ) -> io::Result<()> { let src = from_utf8(src)?; let dst = from_utf8(dst)?; - self.cap_std.symlink_file(src, dst) + self.cap_std.symlink_file(src, dst).await } /// Creates a new directory symlink on a filesystem. @@ -374,10 +394,14 @@ impl Dir { /// [`async_std::os::windows::fs::symlink_dir`]: https://docs.rs/async-std/latest/async_std/os/windows/fs/fn.symlink_dir.html #[cfg(windows)] #[inline] - pub fn symlink_dir, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { + pub async fn symlink_dir, Q: AsRef>( + &self, + src: P, + dst: Q, + ) -> io::Result<()> { let src = from_utf8(src)?; let dst = from_utf8(dst)?; - self.cap_std.symlink_dir(src, dst) + self.cap_std.symlink_dir(src, dst).await } /// Creates a new `UnixListener` bound to the specified socket. @@ -390,9 +414,9 @@ impl Dir { /// [`async_std::os::unix::net::UnixListener::bind`]: https://docs.rs/async-std/latest/async_std/os/unix/net/struct.UnixListener.html#method.bind #[cfg(unix)] #[inline] - pub fn bind_unix_listener>(&self, path: P) -> io::Result { + pub async fn bind_unix_listener>(&self, path: P) -> io::Result { let path = from_utf8(path)?; - self.cap_std.bind_unix_listener(path) + self.cap_std.bind_unix_listener(path).await } /// Connects to the socket named by path. @@ -405,9 +429,9 @@ impl Dir { /// [`async_std::os::unix::net::UnixStream::connect`]: https://docs.rs/async-std/latest/async_std/os/unix/net/struct.UnixStream.html#method.connect #[cfg(unix)] #[inline] - pub fn connect_unix_stream>(&self, path: P) -> io::Result { + pub async fn connect_unix_stream>(&self, path: P) -> io::Result { let path = from_utf8(path)?; - self.cap_std.connect_unix_stream(path) + self.cap_std.connect_unix_stream(path).await } /// Creates a Unix datagram socket bound to the given path. @@ -420,9 +444,9 @@ impl Dir { /// [`async_std::os::unix::net::UnixDatagram::bind`]: https://docs.rs/async-std/latest/async_std/os/unix/net/struct.UnixDatagram.html#method.bind #[cfg(unix)] #[inline] - pub fn bind_unix_datagram>(&self, path: P) -> io::Result { + pub async fn bind_unix_datagram>(&self, path: P) -> io::Result { let path = from_utf8(path)?; - self.cap_std.bind_unix_datagram(path) + self.cap_std.bind_unix_datagram(path).await } /// Connects the socket to the specified address. @@ -436,13 +460,15 @@ impl Dir { /// [`async_std::os::unix::net::UnixDatagram::connect`]: https://docs.rs/async-std/latest/async_std/os/unix/net/struct.UnixDatagram.html#method.connect #[cfg(unix)] #[inline] - pub fn connect_unix_datagram>( + pub async fn connect_unix_datagram>( &self, unix_datagram: &UnixDatagram, path: P, ) -> io::Result<()> { let path = from_utf8(path)?; - self.cap_std.connect_unix_datagram(unix_datagram, path) + self.cap_std + .connect_unix_datagram(unix_datagram, path) + .await } /// Sends data on the socket to the specified address. @@ -456,7 +482,7 @@ impl Dir { /// [`async_std::os::unix::net::UnixDatagram::send_to`]: https://docs.rs/async-std/latest/async_std/os/unix/net/struct.UnixDatagram.html#method.send_to #[cfg(unix)] #[inline] - pub fn send_to_unix_datagram_addr>( + pub async fn send_to_unix_datagram_addr>( &self, unix_datagram: &UnixDatagram, buf: &[u8], @@ -465,6 +491,7 @@ impl Dir { let path = from_utf8(path)?; self.cap_std .send_to_unix_datagram_addr(unix_datagram, buf, path) + .await } // async_std doesn't have `try_clone`. @@ -474,9 +501,9 @@ impl Dir { /// This corresponds to [`async_std::path::Path::exists`], but only /// accesses paths relative to `self`. #[inline] - pub fn exists>(&self, path: P) -> bool { + pub async fn exists>(&self, path: P) -> bool { match from_utf8(path) { - Ok(path) => self.cap_std.exists(path), + Ok(path) => self.cap_std.exists(path).await, Err(_) => false, } } @@ -487,9 +514,9 @@ impl Dir { /// This corresponds to [`async_std::path::Path::is_file`], but only /// accesses paths relative to `self`. #[inline] - pub fn is_file>(&self, path: P) -> bool { + pub async fn is_file>(&self, path: P) -> bool { match from_utf8(path) { - Ok(path) => self.cap_std.is_file(path), + Ok(path) => self.cap_std.is_file(path).await, Err(_) => false, } } @@ -501,9 +528,9 @@ impl Dir { /// symbolic links to query information about the destination file. In case /// of broken symbolic links, this will return `false`. #[inline] - pub fn is_dir>(&self, path: P) -> bool { + pub async fn is_dir>(&self, path: P) -> bool { match from_utf8(path) { - Ok(path) => self.cap_std.is_dir(path), + Ok(path) => self.cap_std.is_dir(path).await, Err(_) => false, } } @@ -516,12 +543,14 @@ impl Dir { /// This function is not sandboxed and may access any path that the host /// process has access to. #[inline] - pub fn open_ambient_dir>( + pub async fn open_ambient_dir>( path: P, ambient_authority: AmbientAuthority, ) -> io::Result { let path = from_utf8(path)?; - crate::fs::Dir::open_ambient_dir(path, ambient_authority).map(Self::from_cap_std) + crate::fs::Dir::open_ambient_dir(path, ambient_authority) + .await + .map(Self::from_cap_std) } } diff --git a/cap-async-std/src/fs_utf8/dir_entry.rs b/cap-async-std/src/fs_utf8/dir_entry.rs index 815e5d92..9f8a7d0f 100644 --- a/cap-async-std/src/fs_utf8/dir_entry.rs +++ b/cap-async-std/src/fs_utf8/dir_entry.rs @@ -19,6 +19,8 @@ use std::{fmt, io}; /// Note that there is no `from_std` method, as `async_std::fs::DirEntry` /// doesn't provide a way to construct a `DirEntry` without opening directories /// by ambient paths. +/// +/// TODO: async pub struct DirEntry { cap_std: crate::fs::DirEntry, } diff --git a/cap-async-std/src/fs_utf8/file.rs b/cap-async-std/src/fs_utf8/file.rs index 5b36759c..7a2feffe 100644 --- a/cap-async-std/src/fs_utf8/file.rs +++ b/cap-async-std/src/fs_utf8/file.rs @@ -115,15 +115,14 @@ impl File { /// This function is not sandboxed and may access any path that the host /// process has access to. #[inline] - pub fn open_ambient>( + pub async fn open_ambient>( path: P, ambient_authority: AmbientAuthority, ) -> io::Result { let path = from_utf8(path)?; - Ok(Self::from_cap_std(crate::fs::File::open_ambient( - path, - ambient_authority, - )?)) + crate::fs::File::open_ambient(path, ambient_authority) + .await + .map(Self::from_cap_std) } /// Constructs a new instance of `Self` with the options specified by @@ -135,17 +134,15 @@ impl File { /// This function is not sandboxed and may access any path that the host /// process has access to. #[inline] - pub fn open_ambient_with>( + pub async fn open_ambient_with>( path: P, options: &OpenOptions, ambient_authority: AmbientAuthority, ) -> io::Result { let path = from_utf8(path)?; - Ok(Self::from_cap_std(crate::fs::File::open_ambient_with( - path, - options, - ambient_authority, - )?)) + crate::fs::File::open_ambient_with(path, options, ambient_authority) + .await + .map(Self::from_cap_std) } } diff --git a/cap-async-std/src/net/tcp_stream.rs b/cap-async-std/src/net/tcp_stream.rs index 773bbdcb..0f9832b9 100644 --- a/cap-async-std/src/net/tcp_stream.rs +++ b/cap-async-std/src/net/tcp_stream.rs @@ -28,6 +28,7 @@ use { /// /// [`Pool`]: struct.Pool.html /// [`Pool::connect_tcp_stream`]: struct.Pool.html#method.connect_tcp_stream +#[derive(Clone)] pub struct TcpStream { std: net::TcpStream, } diff --git a/cap-async-std/src/os/unix/net/unix_stream.rs b/cap-async-std/src/os/unix/net/unix_stream.rs index 1402bf52..55117033 100644 --- a/cap-async-std/src/os/unix/net/unix_stream.rs +++ b/cap-async-std/src/os/unix/net/unix_stream.rs @@ -21,6 +21,7 @@ use unsafe_io::OwnsRaw; /// [`async_std::os::unix::net::UnixStream`]: https://docs.rs/async-std/latest/async_std/os/unix/net/struct.UnixStream.html /// [`Dir`]: struct.Dir.html /// [`Dir::connect_unix_stream`]: struct.Dir.html#method.connect_unix_stream +#[derive(Clone)] pub struct UnixStream { std: unix::net::UnixStream, } diff --git a/cap-fs-ext/Cargo.toml b/cap-fs-ext/Cargo.toml index 014f1000..34bddb04 100644 --- a/cap-fs-ext/Cargo.toml +++ b/cap-fs-ext/Cargo.toml @@ -17,17 +17,19 @@ rustc_version = "0.4.0" [dependencies] arf-strings = { version = "0.4.0", optional = true } -async-std = { version = "1.9.0", optional = true } cap-async-std = { path = "../cap-async-std", optional = true, version = "^0.17.1-alpha.0"} cap-std = { path = "../cap-std", optional = true, version = "^0.17.1-alpha.0"} cap-primitives = { path = "../cap-primitives", version = "^0.17.1-alpha.0"} -io-lifetimes = { version = "0.3.0", default-features = false } +io-lifetimes = "0.3.0" +# Enable "unstable" for `spawn_blocking`. +async-std = { version = "1.10.0", features = ["attributes", "unstable"], optional = true } +async-trait = { version = "0.1.42", optional = true } [features] default = ["std"] fs_utf8 = ["arf-strings", "cap-std/fs_utf8"] std = ["cap-std"] -async_std = ["cap-async-std", "async-std", "io-lifetimes/async-std"] +async_std = ["cap-async-std", "async-std", "io-lifetimes/async-std", "async-trait"] async_std_fs_utf8 = ["cap-async-std/fs_utf8"] [target.'cfg(windows)'.dependencies] diff --git a/cap-fs-ext/src/dir_ext.rs b/cap-fs-ext/src/dir_ext.rs index 7b5e0f13..45c487f2 100644 --- a/cap-fs-ext/src/dir_ext.rs +++ b/cap-fs-ext/src/dir_ext.rs @@ -2,11 +2,15 @@ use cap_primitives::ambient_authority; #[cfg(not(windows))] use cap_primitives::fs::symlink; use cap_primitives::fs::{open_dir_nofollow, set_times, set_times_nofollow}; +#[cfg(all(windows, feature = "async_std", feature = "fs_utf8"))] +use cap_primitives::fs::{stat, FollowSymlinks}; #[cfg(windows)] use cap_primitives::fs::{symlink_dir, symlink_file}; use io_lifetimes::AsFilelike; use std::io; use std::path::Path; +#[cfg(feature = "async_std")] +use {async_std::task::spawn_blocking, async_trait::async_trait}; pub use cap_primitives::fs::SystemTimeSpec; @@ -92,6 +96,127 @@ pub trait DirExt { fn remove_file_or_symlink>(&self, path: P) -> io::Result<()>; } +/// Extension trait for `Dir`, async. +/// +/// The path parameters include `Send` for the `async_trait` macro. +#[cfg(feature = "async_std")] +#[async_trait] +pub trait AsyncDirExt { + /// Set the last access time for a file on a filesystem. + /// + /// This corresponds to [`filetime::set_file_atime`]. + /// + /// [`filetime::set_file_atime`]: https://docs.rs/filetime/latest/filetime/fn.set_file_atime.html + async fn set_atime + Send>( + &self, + path: P, + atime: SystemTimeSpec, + ) -> io::Result<()>; + + /// Set the last modification time for a file on a filesystem. + /// + /// This corresponds to [`filetime::set_file_mtime`]. + /// + /// [`filetime::set_file_mtime`]: https://docs.rs/filetime/latest/filetime/fn.set_file_mtime.html + async fn set_mtime + Send>( + &self, + path: P, + mtime: SystemTimeSpec, + ) -> io::Result<()>; + + /// Set the last access and modification times for a file on a filesystem. + /// + /// This corresponds to [`filetime::set_file_times`]. + /// + /// [`filetime::set_file_times`]: https://docs.rs/filetime/latest/filetime/fn.set_file_times.html + async fn set_times + Send>( + &self, + path: P, + atime: Option, + mtime: Option, + ) -> io::Result<()>; + + /// Set the last access and modification times for a file on a filesystem. + /// This function does not follow symlink. + /// + /// This corresponds to [`filetime::set_symlink_file_times`]. + /// + /// [`filetime::set_symlink_file_times`]: https://docs.rs/filetime/latest/filetime/fn.set_symlink_file_times.html + async fn set_symlink_times + Send>( + &self, + path: P, + atime: Option, + mtime: Option, + ) -> io::Result<()>; + + /// Creates a new symbolic link on a filesystem. + /// + /// This corresponds to [`std::os::unix::fs::symlink`], except that + /// it's supported on non-Unix platforms as well, and it's not guaranteed + /// to be atomic. + /// + /// [`std::os::unix::fs::symlink`]: https://doc.rust-lang.org/std/os/unix/fs/fn.symlink.html + async fn symlink< + P: AsRef + Send, + Q: AsRef + Send, + >( + &self, + src: P, + dst: Q, + ) -> io::Result<()>; + + /// Creates a new file symbolic link on a filesystem. + /// + /// This corresponds to [`std::os::windows::fs::symlink_file`], except that + /// it's supported on non-Windows platforms as well, and it's not + /// guaranteed to fail if the target is not a file. + /// + /// [`std::os::windows::fs::symlink_file`]: https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_file.html + async fn symlink_file< + P: AsRef + Send, + Q: AsRef + Send, + >( + &self, + src: P, + dst: Q, + ) -> io::Result<()>; + + /// Creates a new directory symbolic link on a filesystem. + /// + /// This corresponds to [`std::os::windows::fs::symlink_dir`], except that + /// it's supported on non-Windows platforms as well, and it's not + /// guaranteed to fail if the target is not a directory. + /// + /// [`std::os::windows::fs::symlink_dir`]: https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_dir.html + async fn symlink_dir< + P: AsRef + Send, + Q: AsRef + Send, + >( + &self, + src: P, + dst: Q, + ) -> io::Result<()>; + + /// Similar to `cap_std::fs::Dir::open_dir`, but fails if the path names a + /// symlink. + async fn open_dir_nofollow + Send>( + &self, + path: P, + ) -> io::Result + where + Self: Sized; + + /// Removes a file or symlink from a filesystem. + /// + /// Removal of symlinks has different behavior under Windows - if a symlink + /// points to a directory, it cannot be removed with the `remove_file` + /// operation. This method will remove files and all symlinks. + async fn remove_file_or_symlink + Send>( + &self, + path: P, + ) -> io::Result<()>; +} + /// `fs_utf8` version of `DirExt`. #[cfg(all(any(feature = "std", feature = "async_std"), feature = "fs_utf8"))] pub trait DirExtUtf8 { @@ -175,6 +300,112 @@ pub trait DirExtUtf8 { fn remove_file_or_symlink>(&self, path: P) -> io::Result<()>; } +/// `fs_utf8` version of `DirExt`. +/// +/// The path parameters include `Send` for the `async_trait` macro. +#[cfg(all(feature = "async_std", feature = "fs_utf8"))] +#[async_trait] +pub trait AsyncDirExtUtf8 { + /// Set the last access time for a file on a filesystem. + /// + /// This corresponds to [`filetime::set_file_atime`]. + /// + /// [`filetime::set_file_atime`]: https://docs.rs/filetime/latest/filetime/fn.set_file_atime.html + async fn set_atime + Send>( + &self, + path: P, + atime: SystemTimeSpec, + ) -> io::Result<()>; + + /// Set the last modification time for a file on a filesystem. + /// + /// This corresponds to [`filetime::set_file_mtime`]. + /// + /// [`filetime::set_file_mtime`]: https://docs.rs/filetime/latest/filetime/fn.set_file_mtime.html + async fn set_mtime + Send>( + &self, + path: P, + mtime: SystemTimeSpec, + ) -> io::Result<()>; + + /// Set the last access and modification times for a file on a filesystem. + /// + /// This corresponds to [`filetime::set_file_times`]. + /// + /// [`filetime::set_file_times`]: https://docs.rs/filetime/latest/filetime/fn.set_file_times.html + async fn set_times + Send>( + &self, + path: P, + atime: Option, + mtime: Option, + ) -> io::Result<()>; + + /// Set the last access and modification times for a file on a filesystem. + /// This function does not follow symlink. + /// + /// This corresponds to [`filetime::set_symlink_file_times`]. + /// + /// [`filetime::set_symlink_file_times`]: https://docs.rs/filetime/latest/filetime/fn.set_symlink_file_times.html + async fn set_symlink_times + Send>( + &self, + path: P, + atime: Option, + mtime: Option, + ) -> io::Result<()>; + + /// Creates a new symbolic link on a filesystem. + /// + /// This corresponds to [`std::os::unix::fs::symlink`], except that + /// it's supported on non-Unix platforms as well, and it's not guaranteed + /// to be atomic. + /// + /// [`std::os::unix::fs::symlink`]: https://doc.rust-lang.org/std/os/unix/fs/fn.symlink.html + async fn symlink + Send, Q: AsRef + Send>( + &self, + src: P, + dst: Q, + ) -> io::Result<()>; + + /// Creates a new file symbolic link on a filesystem. + /// + /// This corresponds to [`std::os::windows::fs::symlink_file`], except that + /// it's supported on non-Windows platforms as well, and it's not + /// guaranteed to fail if the target is not a file. + /// + /// [`std::os::windows::fs::symlink_file`]: https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_file.html + async fn symlink_file + Send, Q: AsRef + Send>( + &self, + src: P, + dst: Q, + ) -> io::Result<()>; + + /// Creates a new directory symbolic link on a filesystem. + /// + /// This corresponds to [`std::os::windows::fs::symlink_dir`], except that + /// it's supported on non-Windows platforms as well, and it's not + /// guaranteed to fail if the target is not a directory. + /// + /// [`std::os::windows::fs::symlink_dir`]: https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_dir.html + async fn symlink_dir + Send, Q: AsRef + Send>( + &self, + src: P, + dst: Q, + ) -> io::Result<()>; + + /// Similar to `cap_std::fs::Dir::open_dir`, but fails if the path names a + /// symlink. + async fn open_dir_nofollow + Send>(&self, path: P) -> io::Result + where + Self: Sized; + + /// Removes a file or symlink from a filesystem. + /// + /// Removal of symlinks has different behavior under Windows - if a symlink + /// points to a directory, it cannot be removed with the `remove_file` + /// operation. This method will remove files and all symlinks. + async fn remove_file_or_symlink + Send>(&self, path: P) -> io::Result<()>; +} + #[cfg(feature = "std")] impl DirExt for cap_std::fs::Dir { #[inline] @@ -330,157 +561,296 @@ impl DirExt for cap_std::fs::Dir { } #[cfg(feature = "async_std")] -impl DirExt for cap_async_std::fs::Dir { +#[async_trait] +impl AsyncDirExt for cap_async_std::fs::Dir { #[inline] - fn set_atime>(&self, path: P, atime: SystemTimeSpec) -> io::Result<()> { - set_times( - &self.as_filelike_view::(), - path.as_ref(), - Some(atime), - None, - ) + async fn set_atime + Send>( + &self, + path: P, + atime: SystemTimeSpec, + ) -> io::Result<()> { + let path = path.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + set_times( + &clone.as_filelike_view::(), + path.as_ref(), + Some(atime), + None, + ) + }) + .await } #[inline] - fn set_mtime>(&self, path: P, mtime: SystemTimeSpec) -> io::Result<()> { - set_times( - &self.as_filelike_view::(), - path.as_ref(), - None, - Some(mtime), - ) + async fn set_mtime + Send>( + &self, + path: P, + mtime: SystemTimeSpec, + ) -> io::Result<()> { + let path = path.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + set_times( + &clone.as_filelike_view::(), + path.as_ref(), + None, + Some(mtime), + ) + }) + .await } #[inline] - fn set_times>( + async fn set_times + Send>( &self, path: P, atime: Option, mtime: Option, ) -> io::Result<()> { - set_times( - &self.as_filelike_view::(), - path.as_ref(), - atime, - mtime, - ) + let path = path.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + set_times( + &clone.as_filelike_view::(), + path.as_ref(), + atime, + mtime, + ) + }) + .await } #[inline] - fn set_symlink_times>( + async fn set_symlink_times + Send>( &self, path: P, atime: Option, mtime: Option, ) -> io::Result<()> { - set_times_nofollow( - &self.as_filelike_view::(), - path.as_ref(), - atime, - mtime, - ) + let path = path.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + set_times_nofollow( + &clone.as_filelike_view::(), + path.as_ref(), + atime, + mtime, + ) + }) + .await } #[cfg(not(windows))] #[inline] - fn symlink, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { - symlink( - src.as_ref(), - &self.as_filelike_view::(), - dst.as_ref(), - ) + async fn symlink< + P: AsRef + Send, + Q: AsRef + Send, + >( + &self, + src: P, + dst: Q, + ) -> io::Result<()> { + let src = src.as_ref().to_path_buf(); + let dst = dst.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + symlink( + src.as_ref(), + &clone.as_filelike_view::(), + dst.as_ref(), + ) + }) + .await } #[cfg(not(windows))] #[inline] - fn symlink_file, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { - self.symlink(src.as_ref(), dst.as_ref()) + async fn symlink_file< + P: AsRef + Send, + Q: AsRef + Send, + >( + &self, + src: P, + dst: Q, + ) -> io::Result<()> { + let src = src.as_ref().to_path_buf(); + let dst = dst.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + symlink( + src.as_ref(), + &clone.as_filelike_view::(), + dst.as_ref(), + ) + }) + .await } #[cfg(not(windows))] #[inline] - fn symlink_dir, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { - self.symlink(src.as_ref(), dst.as_ref()) + async fn symlink_dir< + P: AsRef + Send, + Q: AsRef + Send, + >( + &self, + src: P, + dst: Q, + ) -> io::Result<()> { + let src = src.as_ref().to_path_buf(); + let dst = dst.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + symlink( + src.as_ref(), + &clone.as_filelike_view::(), + dst.as_ref(), + ) + }) + .await } #[cfg(windows)] #[inline] - fn symlink, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { - if self.metadata(src.as_ref())?.is_dir() { - self.symlink_dir(src.as_ref(), dst.as_ref()) + async fn symlink< + P: AsRef + Send, + Q: AsRef + Send, + >( + &self, + src: P, + dst: Q, + ) -> io::Result<()> { + let src = src.as_ref().to_path_buf(); + let dst = dst.as_ref().to_path_buf(); + let clone = self.clone(); + if self.metadata(&src).await?.is_dir() { + spawn_blocking(move || { + symlink_dir( + src.as_ref(), + &clone.as_filelike_view::(), + dst.as_ref(), + ) + }) + .await } else { - self.symlink_file(src.as_ref(), dst.as_ref()) + spawn_blocking(move || { + symlink_file( + src.as_ref(), + &clone.as_filelike_view::(), + dst.as_ref(), + ) + }) + .await } } #[cfg(windows)] #[inline] - fn symlink_file, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { - symlink_file( - src.as_ref(), - &self.as_filelike_view::(), - dst.as_ref(), - ) + async fn symlink_file< + P: AsRef + Send, + Q: AsRef + Send, + >( + &self, + src: P, + dst: Q, + ) -> io::Result<()> { + let src = src.as_ref().to_path_buf(); + let dst = dst.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + symlink_file( + src.as_ref(), + &clone.as_filelike_view::(), + dst.as_ref(), + ) + }) + .await } #[cfg(windows)] #[inline] - fn symlink_dir, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { - symlink_dir( - src.as_ref(), - &self.as_filelike_view::(), - dst.as_ref(), - ) + async fn symlink_dir< + P: AsRef + Send, + Q: AsRef + Send, + >( + &self, + src: P, + dst: Q, + ) -> io::Result<()> { + let src = src.as_ref().to_path_buf(); + let dst = dst.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + symlink_dir( + src.as_ref(), + &clone.as_filelike_view::(), + dst.as_ref(), + ) + }) + .await } #[inline] - fn open_dir_nofollow>(&self, path: P) -> io::Result { - match open_dir_nofollow(&self.as_filelike_view::(), path.as_ref()) { - Ok(file) => Ok(Self::from_std_file(file.into(), ambient_authority())), - Err(e) => Err(e), - } + async fn open_dir_nofollow + Send>( + &self, + path: P, + ) -> io::Result { + let path = path.as_ref().to_path_buf(); + let clone = self.clone(); + spawn_blocking(move || { + match open_dir_nofollow(&clone.as_filelike_view::(), path.as_ref()) { + Ok(file) => Ok(Self::from_std_file(file.into(), ambient_authority())), + Err(e) => Err(e), + } + }) + .await } #[cfg(not(windows))] #[inline] - fn remove_file_or_symlink>(&self, path: P) -> io::Result<()> { - self.remove_file(path.as_ref()) + async fn remove_file_or_symlink + Send>( + &self, + path: P, + ) -> io::Result<()> { + self.remove_file(path).await } #[cfg(windows)] #[inline] - fn remove_file_or_symlink>(&self, path: P) -> io::Result<()> { - // This operation may race, because it checks the metadata before deleting - // the symlink. We tried to do this atomically by ReOpenFile with - // DELETE_ON_CLOSE but could not get it to work. - fn delete_symlink_to_dir(dir: &cap_async_std::fs::Dir, path: &Path) -> io::Result<()> { - use crate::{FollowSymlinks, OpenOptionsFollowExt}; - use cap_std::fs::OpenOptions; - use std::os::windows::fs::OpenOptionsExt; - use winapi::um::winbase::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT}; - - let mut opts = OpenOptions::new(); - opts.read(true); - opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); - opts.follow(FollowSymlinks::No); - let file = dir.open_with(path, &opts)?; - - let meta = file.metadata()?; - if meta.file_type().is_symlink() { - drop(file); - // Symlinks that point to directories use the remove_dir, not remove_file, - // operation on windows: - dir.remove_dir(path)?; - Ok(()) - } else { - Err(io::Error::from_raw_os_error( - winapi::shared::winerror::ERROR_DIRECTORY as i32, - )) - } + async fn remove_file_or_symlink + Send>( + &self, + path: P, + ) -> io::Result<()> { + use crate::{FollowSymlinks, OpenOptionsFollowExt}; + use cap_primitives::fs::_WindowsByHandle; + use cap_std::fs::OpenOptions; + use std::os::windows::fs::OpenOptionsExt; + use winapi::um::winbase::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT}; + use winapi::um::winnt::{DELETE, FILE_ATTRIBUTE_DIRECTORY}; + let path = path.as_ref(); + + let mut opts = OpenOptions::new(); + opts.access_mode(DELETE); + opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); + opts.follow(FollowSymlinks::No); + let file = self.open_with(path, &opts).await?; + + let meta = file.metadata()?; + if meta.file_type().is_symlink() + && meta.file_attributes() & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY + { + self.remove_dir(path).await?; + } else { + self.remove_file(path).await?; } - self.remove_file(path.as_ref()) - .or_else(|e| delete_symlink_to_dir(&self, path.as_ref()).map_err(|_| e)) + // 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(()) } } @@ -600,193 +970,268 @@ impl DirExtUtf8 for cap_std::fs_utf8::Dir { #[cfg(windows)] #[inline] fn remove_file_or_symlink>(&self, path: P) -> io::Result<()> { - // This operation may race, because it checks the metadata before deleting - // the symlink. We tried to do this atomically by ReOpenFile with - // DELETE_ON_CLOSE but could not get it to work. - fn delete_symlink_to_dir(dir: &cap_std::fs_utf8::Dir, path: &str) -> io::Result<()> { - use crate::{FollowSymlinks, OpenOptionsFollowExt}; - use cap_std::fs::OpenOptions; - use std::os::windows::fs::OpenOptionsExt; - use winapi::um::winbase::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT}; - - let mut opts = OpenOptions::new(); - opts.read(true); - opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); - opts.follow(FollowSymlinks::No); - let file = dir.open_with(path, &opts)?; - - let meta = file.metadata()?; - if meta.file_type().is_symlink() { - drop(file); - // Symlinks that point to directories use the remove_dir, not remove_file, - // operation on windows: - dir.remove_dir(path)?; - Ok(()) - } else { - Err(io::Error::from_raw_os_error( - winapi::shared::winerror::ERROR_DIRECTORY as i32, - )) - } + use crate::{FollowSymlinks, OpenOptionsFollowExt}; + use cap_primitives::fs::_WindowsByHandle; + use cap_std::fs::OpenOptions; + use std::os::windows::fs::OpenOptionsExt; + use winapi::um::winbase::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT}; + use winapi::um::winnt::{DELETE, FILE_ATTRIBUTE_DIRECTORY}; + let path = path.as_ref(); + + let mut opts = OpenOptions::new(); + opts.access_mode(DELETE); + opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); + opts.follow(FollowSymlinks::No); + let file = self.open_with(path, &opts)?; + + let meta = file.metadata()?; + if meta.file_type().is_symlink() + && meta.file_attributes() & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY + { + self.remove_dir(path)?; + } else { + self.remove_file(path)?; } - self.remove_file(path.as_ref()) - .or_else(|e| delete_symlink_to_dir(&self, path.as_ref()).map_err(|_| e)) + // 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(()) } } #[cfg(all(feature = "async_std", feature = "fs_utf8"))] -impl DirExtUtf8 for cap_async_std::fs_utf8::Dir { +#[async_trait] +impl AsyncDirExtUtf8 for cap_async_std::fs_utf8::Dir { #[inline] - fn set_atime>(&self, path: P, atime: SystemTimeSpec) -> io::Result<()> { + async fn set_atime + Send>( + &self, + path: P, + atime: SystemTimeSpec, + ) -> io::Result<()> { let path = from_utf8(path)?; - set_times( - &self.as_filelike_view::(), - &path, - Some(atime), - None, - ) + let clone = self.clone(); + spawn_blocking(move || { + set_times( + &clone.as_filelike_view::(), + &path, + Some(atime), + None, + ) + }) + .await } #[inline] - fn set_mtime>(&self, path: P, mtime: SystemTimeSpec) -> io::Result<()> { + async fn set_mtime + Send>( + &self, + path: P, + mtime: SystemTimeSpec, + ) -> io::Result<()> { let path = from_utf8(path)?; - set_times( - &self.as_filelike_view::(), - &path, - None, - Some(mtime), - ) + let clone = self.clone(); + spawn_blocking(move || { + set_times( + &clone.as_filelike_view::(), + &path, + None, + Some(mtime), + ) + }) + .await } #[inline] - fn set_times>( + async fn set_times + Send>( &self, path: P, atime: Option, mtime: Option, ) -> io::Result<()> { let path = from_utf8(path)?; - set_times( - &self.as_filelike_view::(), - &path, - atime, - mtime, - ) + let clone = self.clone(); + spawn_blocking(move || { + set_times( + &clone.as_filelike_view::(), + &path, + atime, + mtime, + ) + }) + .await } #[inline] - fn set_symlink_times>( + async fn set_symlink_times + Send>( &self, path: P, atime: Option, mtime: Option, ) -> io::Result<()> { let path = from_utf8(path)?; - set_times_nofollow( - &self.as_filelike_view::(), - &path, - atime, - mtime, - ) + let clone = self.clone(); + spawn_blocking(move || { + set_times_nofollow( + &clone.as_filelike_view::(), + &path, + atime, + mtime, + ) + }) + .await } #[cfg(not(windows))] #[inline] - fn symlink, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { + async fn symlink + Send, Q: AsRef + Send>( + &self, + src: P, + dst: Q, + ) -> io::Result<()> { let src = from_utf8(src)?; let dst = from_utf8(dst)?; - symlink(&src, &self.as_filelike_view::(), &dst) + let clone = self.clone(); + spawn_blocking(move || symlink(&src, &clone.as_filelike_view::(), &dst)) + .await } #[cfg(not(windows))] #[inline] - fn symlink_file, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { - self.symlink(src, dst) + async fn symlink_file + Send, Q: AsRef + Send>( + &self, + src: P, + dst: Q, + ) -> io::Result<()> { + self.symlink(src, dst).await } #[cfg(not(windows))] #[inline] - fn symlink_dir, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { - self.symlink(src, dst) + async fn symlink_dir + Send, Q: AsRef + Send>( + &self, + src: P, + dst: Q, + ) -> io::Result<()> { + self.symlink(src, dst).await } #[cfg(windows)] #[inline] - fn symlink, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { - if self.metadata(src.as_ref())?.is_dir() { - self.symlink_dir(src, dst) + async fn symlink + Send, Q: AsRef + Send>( + &self, + src: P, + dst: Q, + ) -> io::Result<()> { + let src = from_utf8(src)?; + let src_ = src.clone(); + let dst = from_utf8(dst)?; + let clone = self.clone(); + // Call `stat` directly to avoid `async_trait` capturing `self`. + let metadata = spawn_blocking(move || { + stat( + &clone.as_filelike_view::(), + &src_, + FollowSymlinks::Yes, + ) + }) + .await?; + let clone = self.clone(); + if metadata.is_dir() { + spawn_blocking(move || { + symlink_dir(&src, &clone.as_filelike_view::(), &dst) + }) + .await } else { - self.symlink_file(src, dst) + spawn_blocking(move || { + symlink_file(&src, &clone.as_filelike_view::(), &dst) + }) + .await } } #[cfg(windows)] #[inline] - fn symlink_file, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { + async fn symlink_file + Send, Q: AsRef + Send>( + &self, + src: P, + dst: Q, + ) -> io::Result<()> { let src = from_utf8(src)?; let dst = from_utf8(dst)?; - symlink_file(&src, &self.as_filelike_view::(), &dst) + let clone = self.clone(); + spawn_blocking(move || symlink_file(&src, &clone.as_filelike_view::(), &dst)) + .await } #[cfg(windows)] #[inline] - fn symlink_dir, Q: AsRef>(&self, src: P, dst: Q) -> io::Result<()> { + async fn symlink_dir + Send, Q: AsRef + Send>( + &self, + src: P, + dst: Q, + ) -> io::Result<()> { let src = from_utf8(src)?; let dst = from_utf8(dst)?; - symlink_dir(&src, &self.as_filelike_view::(), &dst) + let clone = self.clone(); + spawn_blocking(move || symlink_dir(&src, &clone.as_filelike_view::(), &dst)) + .await } #[inline] - fn open_dir_nofollow>(&self, path: P) -> io::Result { - match open_dir_nofollow( - &self.as_filelike_view::(), - path.as_ref().as_ref(), - ) { - Ok(file) => Ok(Self::from_std_file(file.into(), ambient_authority())), - Err(e) => Err(e), - } + async fn open_dir_nofollow + Send>(&self, path: P) -> io::Result { + let path = from_utf8(path)?; + let clone = self.clone(); + spawn_blocking(move || { + match open_dir_nofollow(&clone.as_filelike_view::(), path.as_ref()) { + Ok(file) => Ok(Self::from_std_file(file.into(), ambient_authority())), + Err(e) => Err(e), + } + }) + .await } #[cfg(not(windows))] #[inline] - fn remove_file_or_symlink>(&self, path: P) -> io::Result<()> { - self.remove_file(path.as_ref()) + async fn remove_file_or_symlink + Send>(&self, path: P) -> io::Result<()> { + self.remove_file(path).await } #[cfg(windows)] #[inline] - fn remove_file_or_symlink>(&self, path: P) -> io::Result<()> { - // This operation may race, because it checks the metadata before deleting - // the symlink. We tried to do this atomically by ReOpenFile with - // DELETE_ON_CLOSE but could not get it to work. - fn delete_symlink_to_dir(dir: &cap_async_std::fs_utf8::Dir, path: &str) -> io::Result<()> { - use crate::{FollowSymlinks, OpenOptionsFollowExt}; - use cap_std::fs::OpenOptions; - use std::os::windows::fs::OpenOptionsExt; - use winapi::um::winbase::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT}; - - let mut opts = OpenOptions::new(); - opts.read(true); - opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); - opts.follow(FollowSymlinks::No); - let file = dir.open_with(path, &opts)?; - - let meta = file.metadata()?; - if meta.file_type().is_symlink() { - drop(file); - // Symlinks that point to directories use the remove_dir, not remove_file, - // operation on windows: - dir.remove_dir(path)?; - Ok(()) - } else { - Err(io::Error::from_raw_os_error( - winapi::shared::winerror::ERROR_DIRECTORY as i32, - )) - } + async fn remove_file_or_symlink + Send>(&self, path: P) -> io::Result<()> { + use crate::{FollowSymlinks, OpenOptionsFollowExt}; + use cap_primitives::fs::_WindowsByHandle; + use cap_std::fs::OpenOptions; + use std::os::windows::fs::OpenOptionsExt; + use winapi::um::winbase::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT}; + use winapi::um::winnt::{DELETE, FILE_ATTRIBUTE_DIRECTORY}; + let path = path.as_ref(); + + let mut opts = OpenOptions::new(); + opts.access_mode(DELETE); + opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); + opts.follow(FollowSymlinks::No); + let file = self.open_with(path, &opts).await?; + + let meta = file.metadata()?; + if meta.file_type().is_symlink() + && meta.file_attributes() & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY + { + self.remove_dir(path).await?; + } else { + self.remove_file(path).await?; } - self.remove_file(path.as_ref()) - .or_else(|e| delete_symlink_to_dir(&self, path.as_ref()).map_err(|_| e)) + // 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(()) } } diff --git a/examples/async_std_fs_misc.rs b/examples/async_std_fs_misc.rs index 7b9b91d4..cfd9a5c0 100644 --- a/examples/async_std_fs_misc.rs +++ b/examples/async_std_fs_misc.rs @@ -10,7 +10,7 @@ use std::path::Path; // A simple implementation of `% cat path` async fn cat(dir: &mut Dir, path: &Path) -> io::Result { - let mut f = dir.open(path)?; + let mut f = dir.open(path).await?; let mut s = String::new(); match f.read_to_string(&mut s).await { Ok(_) => Ok(s), @@ -20,14 +20,17 @@ async fn cat(dir: &mut Dir, path: &Path) -> io::Result { // A simple implementation of `% echo s > path` async fn echo(s: &str, dir: &mut Dir, path: &Path) -> io::Result<()> { - let mut f = dir.create(path)?; + let mut f = dir.create(path).await?; f.write_all(s.as_bytes()).await } // A simple implementation of `% touch path` (ignores existing files) -fn touch(dir: &mut Dir, path: &Path) -> io::Result<()> { - match dir.open_with(path, OpenOptions::new().create(true).write(true)) { +async fn touch(dir: &mut Dir, path: &Path) -> io::Result<()> { + match dir + .open_with(path, OpenOptions::new().create(true).write(true)) + .await + { Ok(_) => Ok(()), Err(e) => Err(e), } @@ -35,7 +38,9 @@ fn touch(dir: &mut Dir, path: &Path) -> io::Result<()> { #[async_std::main] async fn main() { - let mut cwd = Dir::open_ambient_dir(".", ambient_authority()).expect("!"); + let mut cwd = Dir::open_ambient_dir(".", ambient_authority()) + .await + .expect("!"); println!("`mkdir a`"); @@ -60,17 +65,21 @@ async fn main() { }); println!("`touch a/c/e.txt`"); - touch(&mut cwd, &Path::new("a/c/e.txt")).unwrap_or_else(|why| { - println!("! {:?}", why.kind()); - }); + touch(&mut cwd, &Path::new("a/c/e.txt")) + .await + .unwrap_or_else(|why| { + println!("! {:?}", why.kind()); + }); #[cfg(target_family = "unix")] { println!("`ln -s ../b.txt a/c/b.txt`"); // Create a symbolic link, returns `io::Result<()>` - cwd.symlink("../b.txt", "a/c/b.txt").unwrap_or_else(|why| { - println!("! {:?}", why.kind()); - }); + cwd.symlink("../b.txt", "a/c/b.txt") + .await + .unwrap_or_else(|why| { + println!("! {:?}", why.kind()); + }); } println!("`cat a/c/b.txt`"); @@ -81,7 +90,7 @@ async fn main() { println!("`ls a`"); // Read the contents of a directory, returns `io::Result>` - match cwd.read_dir("a") { + match cwd.read_dir("a").await { Err(why) => println!("! {:?}", why.kind()), Ok(paths) => { for path in paths { @@ -92,13 +101,13 @@ async fn main() { println!("`rm a/c/e.txt`"); // Remove a file, returns `io::Result<()>` - cwd.remove_file("a/c/e.txt").unwrap_or_else(|why| { + cwd.remove_file("a/c/e.txt").await.unwrap_or_else(|why| { println!("! {:?}", why.kind()); }); println!("`rmdir a/c/d`"); // Remove an empty directory, returns `io::Result<()>` - cwd.remove_dir("a/c/d").unwrap_or_else(|why| { + cwd.remove_dir("a/c/d").await.unwrap_or_else(|why| { println!("! {:?}", why.kind()); }); }