Skip to content

Commit f8b1d59

Browse files
committed
Auto merge of #146341 - Qelxiros:dirfd-minimum, r=tgross35
minimal dirfd implementation (1/4) This is the first of four smaller PRs that will eventually be equivalent to #139514. A few notes: - I renamed `new` to `open` because `open_dir` takes `&self` and opens a subdirectory. - I also renamed `open` to `open_file`. - I'm not sure how to `impl AsRawFd` and friends because the `common` implementation uses `PathBuf`s. How should I proceed here? The other PRs will be based on this one, so I'll make drafts and mark them ready as their predecessors get merged. They might take a bit though; I've never done this particular thing with git before. Tracking issue: #120426 try-job: aarch64-apple try-job: dist-various* try-job: test-various* try-job: x86_64-msvc-1 try-job: x86_64-mingw*
2 parents f280e76 + d236b8a commit f8b1d59

File tree

12 files changed

+619
-124
lines changed

12 files changed

+619
-124
lines changed

library/std/src/fs.rs

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

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

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

library/std/src/fs/tests.rs

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

3+
#[cfg(not(miri))]
4+
use super::Dir;
35
use crate::assert_matches::assert_matches;
46
use crate::fs::{self, File, FileTimes, OpenOptions, TryLockError};
7+
#[cfg(not(miri))]
8+
use crate::io;
59
use crate::io::prelude::*;
610
use crate::io::{BorrowedBuf, ErrorKind, SeekFrom};
711
use crate::mem::MaybeUninit;
@@ -2465,3 +2469,30 @@ fn test_fs_set_times_nofollow() {
24652469
assert_ne!(target_metadata.accessed().unwrap(), accessed);
24662470
assert_ne!(target_metadata.modified().unwrap(), modified);
24672471
}
2472+
2473+
#[test]
2474+
// FIXME: libc calls fail on miri
2475+
#[cfg(not(miri))]
2476+
fn test_dir_smoke_test() {
2477+
let tmpdir = tmpdir();
2478+
let dir = Dir::open(tmpdir.path());
2479+
check!(dir);
2480+
}
2481+
2482+
#[test]
2483+
// FIXME: libc calls fail on miri
2484+
#[cfg(not(miri))]
2485+
fn test_dir_read_file() {
2486+
let tmpdir = tmpdir();
2487+
let mut f = check!(File::create(tmpdir.join("foo.txt")));
2488+
check!(f.write(b"bar"));
2489+
check!(f.flush());
2490+
drop(f);
2491+
let dir = check!(Dir::open(tmpdir.path()));
2492+
let f = check!(dir.open_file("foo.txt"));
2493+
let buf = check!(io::read_to_string(f));
2494+
assert_eq!("bar", &buf);
2495+
let f = check!(dir.open_file(tmpdir.join("foo.txt")));
2496+
let buf = check!(io::read_to_string(f));
2497+
assert_eq!("bar", &buf);
2498+
}

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::helpers::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
@@ -10,14 +10,15 @@ use crate::os::hermit::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, Raw
1010
use crate::path::{Path, PathBuf};
1111
use crate::sync::Arc;
1212
use crate::sys::fd::FileDesc;
13-
pub use crate::sys::fs::common::{copy, exists};
13+
pub use crate::sys::fs::common::{Dir, copy, exists};
1414
use crate::sys::helpers::run_path_with_cstr;
1515
use crate::sys::time::SystemTime;
1616
use crate::sys::{AsInner, AsInnerMut, FromInner, IntoInner, cvt, unsupported, unsupported_err};
1717
use crate::{fmt, mem};
1818

1919
#[derive(Debug)]
2020
pub struct File(FileDesc);
21+
2122
#[derive(Clone)]
2223
pub struct FileAttr {
2324
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
@@ -59,7 +59,7 @@ pub fn with_native_path<T>(path: &Path, f: &dyn Fn(&Path) -> io::Result<T>) -> i
5959
}
6060

6161
pub use imp::{
62-
DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions,
62+
Dir, DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions,
6363
ReadDir,
6464
};
6565

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::helpers::ignore_notfound;
1414
use crate::sys::pal::{abi, error};
1515
use crate::sys::time::SystemTime;

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::pal::{helpers, unsupported};
1011
use crate::sys::time::SystemTime;
1112

0 commit comments

Comments
 (0)