Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions tokio/src/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ pub use self::write::write;
mod copy;
pub use self::copy::copy;

mod try_exists;
pub use self::try_exists::try_exists;

#[cfg(test)]
mod mocks;

Expand Down
34 changes: 34 additions & 0 deletions tokio/src/fs/try_exists.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use crate::fs::asyncify;

use std::io;
use std::path::Path;

/// Returns `Ok(true)` if the path points at an existing entity.
///
/// This function will traverse symbolic links to query information about the
/// destination file. In case of broken symbolic links this will return `Ok(false)`.
///
/// This is the async equivalent of [`std::path::Path::try_exists`][std].
///
/// [std]: fn@std::path::Path::try_exists
///
/// # Examples
///
/// ```no_run
/// use tokio::fs;
///
/// # async fn dox() -> std::io::Result<()> {
/// fs::try_exists("foo.txt").await?;
/// # Ok(())
/// # }
/// ```
pub async fn try_exists(path: impl AsRef<Path>) -> io::Result<bool> {
let path = path.as_ref().to_owned();
// std's Path::try_exists is not available for current Rust min supported version.
// Current implementation is based on its internal implementation instead.
match asyncify(move || std::fs::metadata(path)).await {
Ok(_) => Ok(true),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(error) => Err(error),
}
}
44 changes: 44 additions & 0 deletions tokio/tests/fs_try_exists.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#![warn(rust_2018_idioms)]
#![cfg(all(feature = "full", not(tokio_wasi)))] // Wasi does not support file operations

use tempfile::tempdir;
use tokio::fs;

#[tokio::test]
async fn try_exists() {
let dir = tempdir().unwrap();

let existing_path = dir.path().join("foo.txt");
fs::write(&existing_path, b"Hello File!").await.unwrap();
let nonexisting_path = dir.path().join("bar.txt");

assert!(fs::try_exists(existing_path).await.unwrap());
assert!(!fs::try_exists(nonexisting_path).await.unwrap());
// FreeBSD root user always has permission to stat.
#[cfg(all(unix, not(target_os = "freebsd")))]
{
use std::os::unix::prelude::PermissionsExt;
let permission_denied_directory_path = dir.path().join("baz");
fs::create_dir(&permission_denied_directory_path)
.await
.unwrap();
let permission_denied_file_path = permission_denied_directory_path.join("baz.txt");
fs::write(&permission_denied_file_path, b"Hello File!")
.await
.unwrap();
let mut perms = tokio::fs::metadata(&permission_denied_directory_path)
.await
.unwrap()
.permissions();

perms.set_mode(0o244);
fs::set_permissions(&permission_denied_directory_path, perms)
.await
.unwrap();
let permission_denied_result = fs::try_exists(permission_denied_file_path).await;
assert_eq!(
permission_denied_result.err().unwrap().kind(),
std::io::ErrorKind::PermissionDenied
);
}
}