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
105 changes: 105 additions & 0 deletions library/std/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,32 @@ 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`.
Comment on lines +156 to +159
Copy link
Contributor

Choose a reason for hiding this comment

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

The docs should also cover some of the API goals and platform-specific behavior (basically what #120426 covers)

///
/// # 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(())
/// }
/// ```
#[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 +1500,85 @@ 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).map(|inner| Self { inner })
}

///
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
///

/// 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).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
22 changes: 22 additions & 0 deletions library/std/src/fs/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use rand::RngCore;

use super::Dir;
#[cfg(any(
windows,
target_os = "freebsd",
Expand Down Expand Up @@ -2226,3 +2227,24 @@ 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]
fn test_dir_smoke_test() {
let tmpdir = tmpdir();
let dir = Dir::open(tmpdir.path());
check!(dir);
}

#[test]
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 mut f = check!(dir.open_file("foo.txt"));
let mut buf = [0u8; 3];
check!(f.read_exact(&mut buf));
assert_eq!(b"bar", &buf);
}
27 changes: 25 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,25 @@ pub fn exists(path: &Path) -> io::Result<bool> {
Err(error) => Err(error),
}
}

pub struct Dir {
path: PathBuf,
}
Comment on lines +63 to +65
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a doc about this being an absolute path, per #120426 and the previous comment. It would also be good to add a note that this fallback doesn't provide the TOCTOU safety.


impl Dir {
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
Ok(Self { path: path.as_ref().to_path_buf() })
Copy link
Contributor

Choose a reason for hiding this comment

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

This won't act as expected if the user passes a relative path here then changes directories, it should store an absolute path.

}

pub fn open_file<P: AsRef<Path>>(&self, path: P) -> io::Result<File> {
let mut opts = OpenOptions::new();
opts.read(true);
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
18 changes: 18 additions & 0 deletions library/std/src/sys/fs/uefi.rs
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this just reexport from unsupported?

Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub struct FileAttr {
size: u64,
}

pub struct Dir(!);

pub struct ReadDir(!);

pub struct DirEntry(!);
Expand Down Expand Up @@ -115,6 +117,22 @@ impl FileType {
}
}

impl Dir {
pub fn open<P: AsRef<Path>>(_path: P) -> io::Result<Self> {
unsupported()
}

pub fn open_file<P: AsRef<Path>>(&self, _path: P) -> io::Result<File> {
self.0
}
}

impl fmt::Debug for Dir {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0
}
}

impl fmt::Debug for ReadDir {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0
Expand Down
Loading
Loading