Skip to content

Commit 1479928

Browse files
committed
Add Dir::open(_file) and some trait impls
1 parent bd4a800 commit 1479928

File tree

13 files changed

+620
-125
lines changed

13 files changed

+620
-125
lines changed

library/std/src/fs.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,43 @@ pub enum TryLockError {
153153
WouldBlock,
154154
}
155155

156+
/// An object providing access to a directory on the filesystem.
157+
///
158+
/// Directories are automatically closed when they go out of scope. Errors detected
159+
/// on closing are ignored by the implementation of `Drop`.
160+
///
161+
/// # Platform-specific behavior
162+
///
163+
/// On supported systems (including Windows and some UNIX-based OSes), this function acquires a
164+
/// handle/file descriptor for the directory. This allows functions like [`Dir::open_file`] to
165+
/// avoid [TOCTOU] errors when the directory itself is being moved.
166+
///
167+
/// On other systems, it stores an absolute path (see [`canonicalize()`]). In the latter case, no
168+
/// [TOCTOU] guarantees are made.
169+
///
170+
/// # Examples
171+
///
172+
/// Opens a directory and then a file inside it.
173+
///
174+
/// ```no_run
175+
/// #![feature(dirfd)]
176+
/// use std::{fs::Dir, io};
177+
///
178+
/// fn main() -> std::io::Result<()> {
179+
/// let dir = Dir::open("foo")?;
180+
/// let mut file = dir.open_file("bar.txt")?;
181+
/// let contents = io::read_to_string(file)?;
182+
/// assert_eq!(contents, "Hello, world!");
183+
/// Ok(())
184+
/// }
185+
/// ```
186+
///
187+
/// [TOCTOU]: self#time-of-check-to-time-of-use-toctou
188+
#[unstable(feature = "dirfd", issue = "120426")]
189+
pub struct Dir {
190+
inner: fs_imp::Dir,
191+
}
192+
156193
/// Metadata information about a file.
157194
///
158195
/// This structure is returned from the [`metadata`] or
@@ -1555,6 +1592,87 @@ impl Seek for Arc<File> {
15551592
}
15561593
}
15571594

1595+
impl Dir {
1596+
/// Attempts to open a directory at `path` in read-only mode.
1597+
///
1598+
/// # Errors
1599+
///
1600+
/// This function will return an error if `path` does not point to an existing directory.
1601+
/// Other errors may also be returned according to [`OpenOptions::open`].
1602+
///
1603+
/// # Examples
1604+
///
1605+
/// ```no_run
1606+
/// #![feature(dirfd)]
1607+
/// use std::{fs::Dir, io};
1608+
///
1609+
/// fn main() -> std::io::Result<()> {
1610+
/// let dir = Dir::open("foo")?;
1611+
/// let mut f = dir.open_file("bar.txt")?;
1612+
/// let contents = io::read_to_string(f)?;
1613+
/// assert_eq!(contents, "Hello, world!");
1614+
/// Ok(())
1615+
/// }
1616+
/// ```
1617+
#[unstable(feature = "dirfd", issue = "120426")]
1618+
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
1619+
fs_imp::Dir::open(path.as_ref(), &OpenOptions::new().read(true).0)
1620+
.map(|inner| Self { inner })
1621+
}
1622+
1623+
/// Attempts to open a file in read-only mode relative to this directory.
1624+
///
1625+
/// # Errors
1626+
///
1627+
/// This function will return an error if `path` does not point to an existing file.
1628+
/// Other errors may also be returned according to [`OpenOptions::open`].
1629+
///
1630+
/// # Examples
1631+
///
1632+
/// ```no_run
1633+
/// #![feature(dirfd)]
1634+
/// use std::{fs::Dir, io};
1635+
///
1636+
/// fn main() -> std::io::Result<()> {
1637+
/// let dir = Dir::open("foo")?;
1638+
/// let mut f = dir.open_file("bar.txt")?;
1639+
/// let contents = io::read_to_string(f)?;
1640+
/// assert_eq!(contents, "Hello, world!");
1641+
/// Ok(())
1642+
/// }
1643+
/// ```
1644+
#[unstable(feature = "dirfd", issue = "120426")]
1645+
pub fn open_file<P: AsRef<Path>>(&self, path: P) -> io::Result<File> {
1646+
self.inner
1647+
.open_file(path.as_ref(), &OpenOptions::new().read(true).0)
1648+
.map(|f| File { inner: f })
1649+
}
1650+
}
1651+
1652+
impl AsInner<fs_imp::Dir> for Dir {
1653+
#[inline]
1654+
fn as_inner(&self) -> &fs_imp::Dir {
1655+
&self.inner
1656+
}
1657+
}
1658+
impl FromInner<fs_imp::Dir> for Dir {
1659+
fn from_inner(f: fs_imp::Dir) -> Dir {
1660+
Dir { inner: f }
1661+
}
1662+
}
1663+
impl IntoInner<fs_imp::Dir> for Dir {
1664+
fn into_inner(self) -> fs_imp::Dir {
1665+
self.inner
1666+
}
1667+
}
1668+
1669+
#[unstable(feature = "dirfd", issue = "120426")]
1670+
impl fmt::Debug for Dir {
1671+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1672+
self.inner.fmt(f)
1673+
}
1674+
}
1675+
15581676
impl OpenOptions {
15591677
/// Creates a blank new set of options ready for configuration.
15601678
///

library/std/src/fs/tests.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use rand::RngCore;
22

3+
#[cfg(not(miri))]
4+
use super::Dir;
35
#[cfg(any(
46
windows,
57
target_os = "freebsd",
@@ -18,6 +20,8 @@ use crate::char::MAX_LEN_UTF8;
1820
))]
1921
use crate::fs::TryLockError;
2022
use crate::fs::{self, File, FileTimes, OpenOptions};
23+
#[cfg(not(miri))]
24+
use crate::io;
2125
use crate::io::prelude::*;
2226
use crate::io::{BorrowedBuf, ErrorKind, SeekFrom};
2327
use crate::mem::MaybeUninit;
@@ -2445,3 +2449,30 @@ fn test_fs_set_times_nofollow() {
24452449
assert_ne!(target_metadata.accessed().unwrap(), accessed);
24462450
assert_ne!(target_metadata.modified().unwrap(), modified);
24472451
}
2452+
2453+
#[test]
2454+
// FIXME: libc calls fail on miri
2455+
#[cfg(not(miri))]
2456+
fn test_dir_smoke_test() {
2457+
let tmpdir = tmpdir();
2458+
let dir = Dir::open(tmpdir.path());
2459+
check!(dir);
2460+
}
2461+
2462+
#[test]
2463+
// FIXME: libc calls fail on miri
2464+
#[cfg(not(miri))]
2465+
fn test_dir_read_file() {
2466+
let tmpdir = tmpdir();
2467+
let mut f = check!(File::create(tmpdir.join("foo.txt")));
2468+
check!(f.write(b"bar"));
2469+
check!(f.flush());
2470+
drop(f);
2471+
let dir = check!(Dir::open(tmpdir.path()));
2472+
let f = check!(dir.open_file("foo.txt"));
2473+
let buf = check!(io::read_to_string(f));
2474+
assert_eq!("bar", &buf);
2475+
let f = check!(dir.open_file(tmpdir.join("foo.txt")));
2476+
let buf = check!(io::read_to_string(f));
2477+
assert_eq!("bar", &buf);
2478+
}

library/std/src/sys/fs/common.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#![allow(dead_code)] // not used on all platforms
22

3-
use crate::fs;
43
use crate::io::{self, Error, ErrorKind};
5-
use crate::path::Path;
4+
use crate::path::{Path, PathBuf};
5+
use crate::sys::fs::{File, OpenOptions};
66
use crate::sys_common::ignore_notfound;
7+
use crate::{fmt, fs};
78

89
pub(crate) const NOT_FILE_ERROR: Error = io::const_error!(
910
ErrorKind::InvalidInput,
@@ -58,3 +59,23 @@ pub fn exists(path: &Path) -> io::Result<bool> {
5859
Err(error) => Err(error),
5960
}
6061
}
62+
63+
pub struct Dir {
64+
path: PathBuf,
65+
}
66+
67+
impl Dir {
68+
pub fn open(path: &Path, _opts: &OpenOptions) -> io::Result<Self> {
69+
path.canonicalize().map(|path| Self { path })
70+
}
71+
72+
pub fn open_file(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> {
73+
File::open(&self.path.join(path), &opts)
74+
}
75+
}
76+
77+
impl fmt::Debug for Dir {
78+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79+
f.debug_struct("Dir").field("path", &self.path).finish()
80+
}
81+
}

library/std/src/sys/fs/hermit.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ use crate::path::{Path, PathBuf};
1111
use crate::sync::Arc;
1212
use crate::sys::common::small_c_string::run_path_with_cstr;
1313
use crate::sys::fd::FileDesc;
14-
pub use crate::sys::fs::common::{copy, exists};
14+
pub use crate::sys::fs::common::{Dir, copy, exists};
1515
use crate::sys::time::SystemTime;
1616
use crate::sys::{cvt, unsupported, unsupported_err};
1717
use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
1818
use crate::{fmt, mem};
1919

2020
#[derive(Debug)]
2121
pub struct File(FileDesc);
22+
2223
#[derive(Clone)]
2324
pub struct FileAttr {
2425
stat_val: stat_struct,

library/std/src/sys/fs/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ pub fn with_native_path<T>(path: &Path, f: &dyn Fn(&Path) -> io::Result<T>) -> i
6161
}
6262

6363
pub use imp::{
64-
DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions,
64+
Dir, DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions,
6565
ReadDir,
6666
};
6767

library/std/src/sys/fs/solid.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::os::raw::{c_int, c_short};
99
use crate::os::solid::ffi::OsStrExt;
1010
use crate::path::{Path, PathBuf};
1111
use crate::sync::Arc;
12-
pub use crate::sys::fs::common::exists;
12+
pub use crate::sys::fs::common::{Dir, exists};
1313
use crate::sys::pal::{abi, error};
1414
use crate::sys::time::SystemTime;
1515
use crate::sys::{unsupported, unsupported_err};

library/std/src/sys/fs/uefi.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::fs::TryLockError;
66
use crate::hash::Hash;
77
use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom};
88
use crate::path::{Path, PathBuf};
9+
pub use crate::sys::fs::common::Dir;
910
use crate::sys::time::SystemTime;
1011
use crate::sys::unsupported;
1112

0 commit comments

Comments
 (0)