Skip to content

Commit 0ac54d6

Browse files
Auto merge of #146341 - Qelxiros:dirfd-minimum, r=<try>
minimal dirfd implementation (1/4) try-job: aarch64-apple try-job: dist-various* try-job: test-various* try-job: x86_64-msvc* try-job: x86_64-mingw
2 parents 565a9ca + 3083d58 commit 0ac54d6

File tree

15 files changed

+595
-126
lines changed

15 files changed

+595
-126
lines changed

library/std/src/fs.rs

Lines changed: 115 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
@@ -1474,6 +1511,84 @@ impl Seek for Arc<File> {
14741511
}
14751512
}
14761513

1514+
impl Dir {
1515+
/// Attempts to open a directory at `path` in read-only mode.
1516+
///
1517+
/// # Errors
1518+
///
1519+
/// This function will return an error if `path` does not point to an existing directory.
1520+
/// Other errors may also be returned according to [`OpenOptions::open`].
1521+
///
1522+
/// # Examples
1523+
///
1524+
/// ```no_run
1525+
/// #![feature(dirfd)]
1526+
/// use std::{fs::Dir, io};
1527+
///
1528+
/// fn main() -> std::io::Result<()> {
1529+
/// let dir = Dir::open("foo")?;
1530+
/// let mut f = dir.open_file("bar.txt")?;
1531+
/// let contents = io::read_to_string(f)?;
1532+
/// assert_eq!(contents, "Hello, world!");
1533+
/// Ok(())
1534+
/// }
1535+
/// ```
1536+
#[unstable(feature = "dirfd", issue = "120426")]
1537+
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
1538+
fs_imp::Dir::open(path).map(|inner| Self { inner })
1539+
}
1540+
1541+
/// Attempts to open a file in read-only mode relative to this directory.
1542+
///
1543+
/// # Errors
1544+
///
1545+
/// This function will return an error if `path` does not point to an existing file.
1546+
/// Other errors may also be returned according to [`OpenOptions::open`].
1547+
///
1548+
/// # Examples
1549+
///
1550+
/// ```no_run
1551+
/// #![feature(dirfd)]
1552+
/// use std::{fs::Dir, io};
1553+
///
1554+
/// fn main() -> std::io::Result<()> {
1555+
/// let dir = Dir::open("foo")?;
1556+
/// let mut f = dir.open_file("bar.txt")?;
1557+
/// let contents = io::read_to_string(f)?;
1558+
/// assert_eq!(contents, "Hello, world!");
1559+
/// Ok(())
1560+
/// }
1561+
/// ```
1562+
#[unstable(feature = "dirfd", issue = "120426")]
1563+
pub fn open_file<P: AsRef<Path>>(&self, path: P) -> io::Result<File> {
1564+
self.inner.open_file(path).map(|f| File { inner: f })
1565+
}
1566+
}
1567+
1568+
impl AsInner<fs_imp::Dir> for Dir {
1569+
#[inline]
1570+
fn as_inner(&self) -> &fs_imp::Dir {
1571+
&self.inner
1572+
}
1573+
}
1574+
impl FromInner<fs_imp::Dir> for Dir {
1575+
fn from_inner(f: fs_imp::Dir) -> Dir {
1576+
Dir { inner: f }
1577+
}
1578+
}
1579+
impl IntoInner<fs_imp::Dir> for Dir {
1580+
fn into_inner(self) -> fs_imp::Dir {
1581+
self.inner
1582+
}
1583+
}
1584+
1585+
#[unstable(feature = "dirfd", issue = "120426")]
1586+
impl fmt::Debug for Dir {
1587+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1588+
self.inner.fmt(f)
1589+
}
1590+
}
1591+
14771592
impl OpenOptions {
14781593
/// Creates a blank new set of options ready for configuration.
14791594
///

library/std/src/fs/tests.rs

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

3+
use super::Dir;
34
#[cfg(any(
45
windows,
56
target_os = "freebsd",
@@ -2226,3 +2227,28 @@ fn test_open_options_invalid_combinations() {
22262227
assert_eq!(err.kind(), ErrorKind::InvalidInput);
22272228
assert_eq!(err.to_string(), "must specify at least one of read, write, or append access");
22282229
}
2230+
2231+
#[test]
2232+
// FIXME: libc calls fail on miri
2233+
#[cfg(not(miri))]
2234+
fn test_dir_smoke_test() {
2235+
let tmpdir = tmpdir();
2236+
let dir = Dir::open(tmpdir.path());
2237+
check!(dir);
2238+
}
2239+
2240+
#[test]
2241+
// FIXME: libc calls fail on miri
2242+
#[cfg(not(miri))]
2243+
fn test_dir_read_file() {
2244+
let tmpdir = tmpdir();
2245+
let mut f = check!(File::create(tmpdir.join("foo.txt")));
2246+
check!(f.write(b"bar"));
2247+
check!(f.flush());
2248+
drop(f);
2249+
let dir = check!(Dir::open(tmpdir.path()));
2250+
let mut f = check!(dir.open_file("foo.txt"));
2251+
let mut buf = [0u8; 3];
2252+
check!(f.read_exact(&mut buf));
2253+
assert_eq!(b"bar", &buf);
2254+
}

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

Lines changed: 25 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,25 @@ 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<P: AsRef<Path>>(path: P) -> io::Result<Self> {
69+
path.as_ref().canonicalize().map(|path| Self { path })
70+
}
71+
72+
pub fn open_file<P: AsRef<Path>>(&self, path: P) -> io::Result<File> {
73+
let mut opts = OpenOptions::new();
74+
opts.read(true);
75+
File::open(&self.path.join(path), &opts)
76+
}
77+
}
78+
79+
impl fmt::Debug for Dir {
80+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81+
f.debug_struct("Dir").field("path", &self.path).finish()
82+
}
83+
}

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
@@ -53,7 +53,7 @@ pub fn with_native_path<T>(path: &Path, f: &dyn Fn(&Path) -> io::Result<T>) -> i
5353
}
5454

5555
pub use imp::{
56-
DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions,
56+
Dir, DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions,
5757
ReadDir,
5858
};
5959

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
@@ -1,5 +1,6 @@
11
use r_efi::protocols::file;
22

3+
pub use super::common::Dir;
34
use crate::ffi::OsString;
45
use crate::fmt;
56
use crate::fs::TryLockError;

0 commit comments

Comments
 (0)