Skip to content

Commit 12f81ff

Browse files
authored
fs: add fs::try_exists (#4299)
1 parent 3ea5cc5 commit 12f81ff

File tree

3 files changed

+81
-0
lines changed

3 files changed

+81
-0
lines changed

tokio/src/fs/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ pub use self::write::write;
102102
mod copy;
103103
pub use self::copy::copy;
104104

105+
mod try_exists;
106+
pub use self::try_exists::try_exists;
107+
105108
#[cfg(test)]
106109
mod mocks;
107110

tokio/src/fs/try_exists.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use crate::fs::asyncify;
2+
3+
use std::io;
4+
use std::path::Path;
5+
6+
/// Returns `Ok(true)` if the path points at an existing entity.
7+
///
8+
/// This function will traverse symbolic links to query information about the
9+
/// destination file. In case of broken symbolic links this will return `Ok(false)`.
10+
///
11+
/// This is the async equivalent of [`std::path::Path::try_exists`][std].
12+
///
13+
/// [std]: fn@std::path::Path::try_exists
14+
///
15+
/// # Examples
16+
///
17+
/// ```no_run
18+
/// use tokio::fs;
19+
///
20+
/// # async fn dox() -> std::io::Result<()> {
21+
/// fs::try_exists("foo.txt").await?;
22+
/// # Ok(())
23+
/// # }
24+
/// ```
25+
pub async fn try_exists(path: impl AsRef<Path>) -> io::Result<bool> {
26+
let path = path.as_ref().to_owned();
27+
// std's Path::try_exists is not available for current Rust min supported version.
28+
// Current implementation is based on its internal implementation instead.
29+
match asyncify(move || std::fs::metadata(path)).await {
30+
Ok(_) => Ok(true),
31+
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
32+
Err(error) => Err(error),
33+
}
34+
}

tokio/tests/fs_try_exists.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#![warn(rust_2018_idioms)]
2+
#![cfg(all(feature = "full", not(tokio_wasi)))] // Wasi does not support file operations
3+
4+
use tempfile::tempdir;
5+
use tokio::fs;
6+
7+
#[tokio::test]
8+
async fn try_exists() {
9+
let dir = tempdir().unwrap();
10+
11+
let existing_path = dir.path().join("foo.txt");
12+
fs::write(&existing_path, b"Hello File!").await.unwrap();
13+
let nonexisting_path = dir.path().join("bar.txt");
14+
15+
assert!(fs::try_exists(existing_path).await.unwrap());
16+
assert!(!fs::try_exists(nonexisting_path).await.unwrap());
17+
// FreeBSD root user always has permission to stat.
18+
#[cfg(all(unix, not(target_os = "freebsd")))]
19+
{
20+
use std::os::unix::prelude::PermissionsExt;
21+
let permission_denied_directory_path = dir.path().join("baz");
22+
fs::create_dir(&permission_denied_directory_path)
23+
.await
24+
.unwrap();
25+
let permission_denied_file_path = permission_denied_directory_path.join("baz.txt");
26+
fs::write(&permission_denied_file_path, b"Hello File!")
27+
.await
28+
.unwrap();
29+
let mut perms = tokio::fs::metadata(&permission_denied_directory_path)
30+
.await
31+
.unwrap()
32+
.permissions();
33+
34+
perms.set_mode(0o244);
35+
fs::set_permissions(&permission_denied_directory_path, perms)
36+
.await
37+
.unwrap();
38+
let permission_denied_result = fs::try_exists(permission_denied_file_path).await;
39+
assert_eq!(
40+
permission_denied_result.err().unwrap().kind(),
41+
std::io::ErrorKind::PermissionDenied
42+
);
43+
}
44+
}

0 commit comments

Comments
 (0)