Skip to content
Open
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
118 changes: 118 additions & 0 deletions library/std/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,43 @@ pub enum TryLockError {
WouldBlock,
}

/// An object providing access to a directory on the filesystem.
///
/// Directories are automatically closed when they go out of scope. Errors detected
/// on closing are ignored by the implementation of `Drop`.
///
/// # Platform-specific behavior
///
/// On supported systems (including Windows and some UNIX-based OSes), this function acquires a
/// handle/file descriptor for the directory. This allows functions like [`Dir::open_file`] to
/// avoid [TOCTOU] errors when the directory itself is being moved.
///
/// On other systems, it stores an absolute path (see [`canonicalize()`]). In the latter case, no
/// [TOCTOU] guarantees are made.
///
/// # Examples
///
/// Opens a directory and then a file inside it.
///
/// ```no_run
/// #![feature(dirfd)]
/// use std::{fs::Dir, io};
///
/// fn main() -> std::io::Result<()> {
/// let dir = Dir::open("foo")?;
/// let mut file = dir.open_file("bar.txt")?;
/// let contents = io::read_to_string(file)?;
/// assert_eq!(contents, "Hello, world!");
/// Ok(())
/// }
/// ```
///
/// [TOCTOU]: self#time-of-check-to-time-of-use-toctou
#[unstable(feature = "dirfd", issue = "120426")]
pub struct Dir {
inner: fs_imp::Dir,
}

/// Metadata information about a file.
///
/// This structure is returned from the [`metadata`] or
Expand Down Expand Up @@ -1474,6 +1511,87 @@ impl Seek for Arc<File> {
}
}

impl Dir {
/// Attempts to open a directory at `path` in read-only mode.
///
/// # Errors
///
/// This function will return an error if `path` does not point to an existing directory.
/// Other errors may also be returned according to [`OpenOptions::open`].
///
/// # Examples
///
/// ```no_run
/// #![feature(dirfd)]
/// use std::{fs::Dir, io};
///
/// fn main() -> std::io::Result<()> {
/// let dir = Dir::open("foo")?;
/// let mut f = dir.open_file("bar.txt")?;
/// let contents = io::read_to_string(f)?;
/// assert_eq!(contents, "Hello, world!");
/// Ok(())
/// }
/// ```
#[unstable(feature = "dirfd", issue = "120426")]
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
fs_imp::Dir::open(path.as_ref(), &OpenOptions::new().read(true).0)
.map(|inner| Self { inner })
}

/// Attempts to open a file in read-only mode relative to this directory.
///
/// # Errors
///
/// This function will return an error if `path` does not point to an existing file.
/// Other errors may also be returned according to [`OpenOptions::open`].
///
/// # Examples
///
/// ```no_run
/// #![feature(dirfd)]
/// use std::{fs::Dir, io};
///
/// fn main() -> std::io::Result<()> {
/// let dir = Dir::open("foo")?;
/// let mut f = dir.open_file("bar.txt")?;
/// let contents = io::read_to_string(f)?;
/// assert_eq!(contents, "Hello, world!");
/// Ok(())
/// }
/// ```
#[unstable(feature = "dirfd", issue = "120426")]
pub fn open_file<P: AsRef<Path>>(&self, path: P) -> io::Result<File> {
self.inner
.open_file(path.as_ref(), &OpenOptions::new().read(true).0)
.map(|f| File { inner: f })
}
}

impl AsInner<fs_imp::Dir> for Dir {
#[inline]
fn as_inner(&self) -> &fs_imp::Dir {
&self.inner
}
}
impl FromInner<fs_imp::Dir> for Dir {
fn from_inner(f: fs_imp::Dir) -> Dir {
Dir { inner: f }
}
}
impl IntoInner<fs_imp::Dir> for Dir {
fn into_inner(self) -> fs_imp::Dir {
self.inner
}
}

#[unstable(feature = "dirfd", issue = "120426")]
impl fmt::Debug for Dir {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt(f)
}
}

impl OpenOptions {
/// Creates a blank new set of options ready for configuration.
///
Expand Down
31 changes: 31 additions & 0 deletions library/std/src/fs/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use rand::RngCore;

#[cfg(not(miri))]
use super::Dir;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a gate on miri to not be unused

#[cfg(any(
windows,
target_os = "freebsd",
Expand All @@ -18,6 +20,8 @@ use crate::char::MAX_LEN_UTF8;
))]
use crate::fs::TryLockError;
use crate::fs::{self, File, FileTimes, OpenOptions};
#[cfg(not(miri))]
use crate::io;
use crate::io::prelude::*;
use crate::io::{BorrowedBuf, ErrorKind, SeekFrom};
use crate::mem::MaybeUninit;
Expand Down Expand Up @@ -2226,3 +2230,30 @@ fn test_open_options_invalid_combinations() {
assert_eq!(err.kind(), ErrorKind::InvalidInput);
assert_eq!(err.to_string(), "must specify at least one of read, write, or append access");
}

#[test]
// FIXME: libc calls fail on miri
#[cfg(not(miri))]
fn test_dir_smoke_test() {
let tmpdir = tmpdir();
let dir = Dir::open(tmpdir.path());
check!(dir);
}

#[test]
// FIXME: libc calls fail on miri
#[cfg(not(miri))]
fn test_dir_read_file() {
let tmpdir = tmpdir();
let mut f = check!(File::create(tmpdir.join("foo.txt")));
check!(f.write(b"bar"));
check!(f.flush());
drop(f);
let dir = check!(Dir::open(tmpdir.path()));
let f = check!(dir.open_file("foo.txt"));
let buf = check!(io::read_to_string(f));
assert_eq!("bar", &buf);
let f = check!(dir.open_file(tmpdir.join("foo.txt")));
let buf = check!(io::read_to_string(f));
assert_eq!("bar", &buf);
}
25 changes: 23 additions & 2 deletions library/std/src/sys/fs/common.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#![allow(dead_code)] // not used on all platforms

use crate::fs;
use crate::io::{self, Error, ErrorKind};
use crate::path::Path;
use crate::path::{Path, PathBuf};
use crate::sys::fs::{File, OpenOptions};
use crate::sys_common::ignore_notfound;
use crate::{fmt, fs};

pub(crate) const NOT_FILE_ERROR: Error = io::const_error!(
ErrorKind::InvalidInput,
Expand Down Expand Up @@ -58,3 +59,23 @@ pub fn exists(path: &Path) -> io::Result<bool> {
Err(error) => Err(error),
}
}

pub struct Dir {
path: PathBuf,
}

impl Dir {
pub fn open(path: &Path, _opts: &OpenOptions) -> io::Result<Self> {
path.canonicalize().map(|path| Self { path })
}

pub fn open_file(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> {
File::open(&self.path.join(path), &opts)
}
}

impl fmt::Debug for Dir {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Dir").field("path", &self.path).finish()
}
}
3 changes: 2 additions & 1 deletion library/std/src/sys/fs/hermit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ use crate::path::{Path, PathBuf};
use crate::sync::Arc;
use crate::sys::common::small_c_string::run_path_with_cstr;
use crate::sys::fd::FileDesc;
pub use crate::sys::fs::common::{copy, exists};
pub use crate::sys::fs::common::{Dir, copy, exists};
use crate::sys::time::SystemTime;
use crate::sys::{cvt, unsupported, unsupported_err};
use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
use crate::{fmt, mem};

#[derive(Debug)]
pub struct File(FileDesc);

#[derive(Clone)]
pub struct FileAttr {
stat_val: stat_struct,
Expand Down
2 changes: 1 addition & 1 deletion library/std/src/sys/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ pub fn with_native_path<T>(path: &Path, f: &dyn Fn(&Path) -> io::Result<T>) -> i
}

pub use imp::{
DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions,
Dir, DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions,
ReadDir,
};

Expand Down
2 changes: 1 addition & 1 deletion library/std/src/sys/fs/solid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::os::raw::{c_int, c_short};
use crate::os::solid::ffi::OsStrExt;
use crate::path::{Path, PathBuf};
use crate::sync::Arc;
pub use crate::sys::fs::common::exists;
pub use crate::sys::fs::common::{Dir, exists};
use crate::sys::pal::{abi, error};
use crate::sys::time::SystemTime;
use crate::sys::{unsupported, unsupported_err};
Expand Down
1 change: 1 addition & 0 deletions library/std/src/sys/fs/uefi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::fs::TryLockError;
use crate::hash::Hash;
use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom};
use crate::path::{Path, PathBuf};
pub use crate::sys::fs::common::Dir;
use crate::sys::time::SystemTime;
use crate::sys::unsupported;

Expand Down
Loading
Loading