Skip to content

Commit 17a8062

Browse files
Auto merge of #139514 - Qelxiros:120426-dirfd, r=<try>
dirfd: preliminary unix and windows implementations Tracking issue: #120426 As per [this comment](#120426 (comment)), this issue needs someone to start work on an implementation, so I've implemented a couple functions for UNIX. There's a lot more work to be done here (most of the feature), so I'd love some guidance on what needs fixing in this PR and any notes on how to proceed. Thanks! try-job: `x86_64-msvc*` try-job: `test-various*` try-job: `dist-various*`
2 parents 1677d46 + f4d6ffc commit 17a8062

File tree

5 files changed

+830
-92
lines changed

5 files changed

+830
-92
lines changed

library/std/src/fs.rs

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,33 @@ pub enum TryLockError {
132132
WouldBlock,
133133
}
134134

135+
/// An object providing access to a directory on the filesystem.
136+
///
137+
/// Files are automatically closed when they go out of scope. Errors detected
138+
/// on closing are ignored by the implementation of `Drop`.
139+
///
140+
/// # Examples
141+
///
142+
/// Opens a directory and then a file inside it.
143+
///
144+
/// ```no_run
145+
/// #![feature(dirfd)]
146+
/// use std::{fs::Dir, io::Read};
147+
///
148+
/// fn main() -> std::io::Result<()> {
149+
/// let dir = Dir::new("foo")?;
150+
/// let mut file = dir.open("bar.txt")?;
151+
/// let mut s = String::new();
152+
/// file.read_to_string(&mut s)?;
153+
/// println!("{}", s);
154+
/// Ok(())
155+
/// }
156+
/// ```
157+
#[unstable(feature = "dirfd", issue = "120426")]
158+
pub struct Dir {
159+
inner: fs_imp::Dir,
160+
}
161+
135162
/// Metadata information about a file.
136163
///
137164
/// This structure is returned from the [`metadata`] or
@@ -1453,6 +1480,223 @@ impl Seek for Arc<File> {
14531480
}
14541481
}
14551482

1483+
impl Dir {
1484+
/// Attempts to open a directory at `path` in read-only mode.
1485+
///
1486+
/// See [`new_with`] for more options.
1487+
///
1488+
/// # Errors
1489+
///
1490+
/// This function will return an error if `path` does not point to an existing directory.
1491+
/// Other errors may also be returned according to [`OpenOptions::open`].
1492+
///
1493+
/// # Examples
1494+
///
1495+
/// ```no_run
1496+
/// #![feature(dirfd)]
1497+
/// use std::{fs::Dir, io::Read};
1498+
///
1499+
/// fn main() -> std::io::Result<()> {
1500+
/// let dir = Dir::new("foo")?;
1501+
/// let mut f = dir.open("bar.txt")?;
1502+
/// let mut data = vec![];
1503+
/// f.read_to_end(&mut data)?;
1504+
/// Ok(())
1505+
/// }
1506+
/// ```
1507+
///
1508+
/// [`new_with`]: Dir::new_with
1509+
#[unstable(feature = "dirfd", issue = "120426")]
1510+
pub fn new<P: AsRef<Path>>(path: P) -> io::Result<Self> {
1511+
Ok(Self { inner: fs_imp::Dir::new(path)? })
1512+
}
1513+
1514+
/// Attempts to open a directory at `path` with the options specified by `opts`.
1515+
///
1516+
/// # Errors
1517+
///
1518+
/// This function may return an error according to [`OpenOptions::open`].
1519+
///
1520+
/// # Examples
1521+
///
1522+
/// ```no_run
1523+
/// #![feature(dirfd)]
1524+
/// use std::fs::{Dir, OpenOptions};
1525+
///
1526+
/// fn main() -> std::io::Result<()> {
1527+
/// let dir = Dir::new_with("foo", OpenOptions::new().write(true))?;
1528+
/// let mut f = dir.remove_file("bar.txt")?;
1529+
/// Ok(())
1530+
/// }
1531+
/// ```
1532+
#[unstable(feature = "dirfd", issue = "120426")]
1533+
pub fn new_with<P: AsRef<Path>>(path: P, opts: &OpenOptions) -> io::Result<Self> {
1534+
Ok(Self { inner: fs_imp::Dir::new_with(path, &opts.0)? })
1535+
}
1536+
1537+
/// Attempts to open a file in read-only mode relative to this directory.
1538+
///
1539+
/// # Errors
1540+
///
1541+
/// This function will return an error if `path` does not point to an existing file.
1542+
/// Other errors may also be returned according to [`OpenOptions::open`].
1543+
///
1544+
/// # Examples
1545+
///
1546+
/// ```no_run
1547+
/// #![feature(dirfd)]
1548+
/// use std::{fs::Dir, io::Read};
1549+
///
1550+
/// fn main() -> std::io::Result<()> {
1551+
/// let dir = Dir::new("foo")?;
1552+
/// let mut f = dir.open("bar.txt")?;
1553+
/// let mut data = vec![];
1554+
/// f.read_to_end(&mut data)?;
1555+
/// Ok(())
1556+
/// }
1557+
/// ```
1558+
#[unstable(feature = "dirfd", issue = "120426")]
1559+
pub fn open<P: AsRef<Path>>(&self, path: P) -> io::Result<File> {
1560+
self.inner.open(path).map(|f| File { inner: f })
1561+
}
1562+
1563+
/// Attempts to open a file relative to this directory with the options specified by `opts`.
1564+
///
1565+
/// # Errors
1566+
///
1567+
/// This function may return an error according to [`OpenOptions::open`].
1568+
///
1569+
/// # Examples
1570+
///
1571+
/// ```no_run
1572+
/// #![feature(dirfd)]
1573+
/// use std::{fs::{Dir, OpenOptions}, io::Read};
1574+
///
1575+
/// fn main() -> std::io::Result<()> {
1576+
/// let dir = Dir::new("foo")?;
1577+
/// let mut f = dir.open_with("bar.txt", OpenOptions::new().read(true))?;
1578+
/// let mut data = vec![];
1579+
/// f.read_to_end(&mut data)?;
1580+
/// Ok(())
1581+
/// }
1582+
/// ```
1583+
#[unstable(feature = "dirfd", issue = "120426")]
1584+
pub fn open_with<P: AsRef<Path>>(&self, path: P, opts: &OpenOptions) -> io::Result<File> {
1585+
self.inner.open_with(path, &opts.0).map(|f| File { inner: f })
1586+
}
1587+
1588+
/// Attempts to create a directory relative to this directory.
1589+
///
1590+
/// # Errors
1591+
///
1592+
/// This function will return an error if `path` points to an existing file or directory.
1593+
/// Other errors may also be returned according to [`OpenOptions::open`].
1594+
///
1595+
/// # Examples
1596+
///
1597+
/// ```no_run
1598+
/// #![feature(dirfd)]
1599+
/// use std::{fs::{Dir, OpenOptions}, io::Read};
1600+
///
1601+
/// fn main() -> std::io::Result<()> {
1602+
/// let dir = Dir::new("foo")?;
1603+
/// let mut f = dir.open_with("bar.txt", OpenOptions::new().read(true))?;
1604+
/// let mut data = vec![];
1605+
/// f.read_to_end(&mut data)?;
1606+
/// Ok(())
1607+
/// }
1608+
/// ```
1609+
#[unstable(feature = "dirfd", issue = "120426")]
1610+
pub fn create_dir<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
1611+
self.inner.create_dir(path)
1612+
}
1613+
1614+
/// Attempts to remove a file relative to this directory.
1615+
///
1616+
/// # Errors
1617+
///
1618+
/// This function will return an error if `path` does not point to an existing file.
1619+
/// Other errors may also be returned according to [`OpenOptions::open`].
1620+
///
1621+
/// # Examples
1622+
///
1623+
/// ```no_run
1624+
/// #![feature(dirfd)]
1625+
/// use std::fs::Dir;
1626+
///
1627+
/// fn main() -> std::io::Result<()> {
1628+
/// let dir = Dir::new("foo")?;
1629+
/// dir.remove_file("bar.txt")?;
1630+
/// Ok(())
1631+
/// }
1632+
/// ```
1633+
#[unstable(feature = "dirfd", issue = "120426")]
1634+
pub fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
1635+
self.inner.remove_file(path)
1636+
}
1637+
1638+
/// Attempts to remove a directory relative to this directory.
1639+
///
1640+
/// # Errors
1641+
///
1642+
/// This function will return an error if `path` does not point to an existing, non-empty directory.
1643+
/// Other errors may also be returned according to [`OpenOptions::open`].
1644+
///
1645+
/// # Examples
1646+
///
1647+
/// ```no_run
1648+
/// #![feature(dirfd)]
1649+
/// use std::fs::Dir;
1650+
///
1651+
/// fn main() -> std::io::Result<()> {
1652+
/// let dir = Dir::new("foo")?;
1653+
/// dir.remove_dir("baz")?;
1654+
/// Ok(())
1655+
/// }
1656+
/// ```
1657+
#[unstable(feature = "dirfd", issue = "120426")]
1658+
pub fn remove_dir<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
1659+
self.inner.remove_dir(path)
1660+
}
1661+
1662+
/// Attempts to rename a file or directory relative to this directory to a new name, replacing
1663+
/// the destination file if present.
1664+
///
1665+
/// # Errors
1666+
///
1667+
/// This function will return an error if `from` does not point to an existing file or directory.
1668+
/// Other errors may also be returned according to [`OpenOptions::open`].
1669+
///
1670+
/// # Examples
1671+
///
1672+
/// ```no_run
1673+
/// #![feature(dirfd)]
1674+
/// use std::fs::Dir;
1675+
///
1676+
/// fn main() -> std::io::Result<()> {
1677+
/// let dir = Dir::new("foo")?;
1678+
/// dir.rename("bar.txt", &dir, "quux.txt")?;
1679+
/// Ok(())
1680+
/// }
1681+
/// ```
1682+
#[unstable(feature = "dirfd", issue = "120426")]
1683+
pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(
1684+
&self,
1685+
from: P,
1686+
to_dir: &Self,
1687+
to: Q,
1688+
) -> io::Result<()> {
1689+
self.inner.rename(from, &to_dir.inner, to)
1690+
}
1691+
}
1692+
1693+
#[unstable(feature = "dirfd", issue = "120426")]
1694+
impl fmt::Debug for Dir {
1695+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1696+
self.inner.fmt(f)
1697+
}
1698+
}
1699+
14561700
impl OpenOptions {
14571701
/// Creates a blank new set of options ready for configuration.
14581702
///

library/std/src/fs/tests.rs

Lines changed: 92 additions & 1 deletion
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",
@@ -17,7 +18,7 @@ use crate::char::MAX_LEN_UTF8;
1718
target_vendor = "apple",
1819
))]
1920
use crate::fs::TryLockError;
20-
use crate::fs::{self, File, FileTimes, OpenOptions};
21+
use crate::fs::{self, File, FileTimes, OpenOptions, create_dir};
2122
use crate::io::prelude::*;
2223
use crate::io::{BorrowedBuf, ErrorKind, SeekFrom};
2324
use crate::mem::MaybeUninit;
@@ -2084,3 +2085,93 @@ fn test_rename_junction() {
20842085
// Junction links are always absolute so we just check the file name is correct.
20852086
assert_eq!(fs::read_link(&dest).unwrap().file_name(), Some(not_exist.as_os_str()));
20862087
}
2088+
2089+
#[test]
2090+
fn test_dir_smoke_test() {
2091+
let tmpdir = tmpdir();
2092+
check!(Dir::new(tmpdir.path()));
2093+
}
2094+
2095+
#[test]
2096+
fn test_dir_read_file() {
2097+
let tmpdir = tmpdir();
2098+
let mut f = check!(File::create(tmpdir.join("foo.txt")));
2099+
check!(f.write(b"bar"));
2100+
check!(f.flush());
2101+
drop(f);
2102+
let dir = check!(Dir::new(tmpdir.path()));
2103+
let mut f = check!(dir.open("foo.txt"));
2104+
let mut buf = [0u8; 3];
2105+
check!(f.read_exact(&mut buf));
2106+
assert_eq!(b"bar", &buf);
2107+
}
2108+
2109+
#[test]
2110+
fn test_dir_write_file() {
2111+
let tmpdir = tmpdir();
2112+
let dir = check!(Dir::new(tmpdir.path()));
2113+
let mut f = check!(dir.open_with("foo.txt", &OpenOptions::new().write(true).create(true)));
2114+
check!(f.write(b"bar"));
2115+
check!(f.flush());
2116+
drop(f);
2117+
let mut f = check!(File::open(tmpdir.join("foo.txt")));
2118+
let mut buf = [0u8; 3];
2119+
check!(f.read_exact(&mut buf));
2120+
assert_eq!(b"bar", &buf);
2121+
}
2122+
2123+
#[test]
2124+
fn test_dir_remove_file() {
2125+
let tmpdir = tmpdir();
2126+
let mut f = check!(File::create(tmpdir.join("foo.txt")));
2127+
check!(f.write(b"bar"));
2128+
check!(f.flush());
2129+
drop(f);
2130+
let dir = check!(Dir::new(tmpdir.path()));
2131+
check!(dir.remove_file("foo.txt"));
2132+
let result = File::open(tmpdir.join("foo.txt"));
2133+
#[cfg(all(unix, not(target_os = "vxworks")))]
2134+
error!(result, "No such file or directory");
2135+
#[cfg(target_os = "vxworks")]
2136+
error!(result, "no such file or directory");
2137+
#[cfg(windows)]
2138+
error!(result, 2); // ERROR_FILE_NOT_FOUND
2139+
}
2140+
2141+
#[test]
2142+
fn test_dir_remove_dir() {
2143+
let tmpdir = tmpdir();
2144+
check!(create_dir(tmpdir.join("foo")));
2145+
let dir = check!(Dir::new(tmpdir.path()));
2146+
check!(dir.remove_dir("foo"));
2147+
let result = Dir::new(tmpdir.join("foo"));
2148+
#[cfg(all(unix, not(target_os = "vxworks")))]
2149+
error!(result, "No such file or directory");
2150+
#[cfg(target_os = "vxworks")]
2151+
error!(result, "no such file or directory");
2152+
#[cfg(windows)]
2153+
error!(result, 2); // ERROR_FILE_NOT_FOUND
2154+
}
2155+
2156+
#[test]
2157+
fn test_dir_rename_file() {
2158+
let tmpdir = tmpdir();
2159+
let mut f = check!(File::create(tmpdir.join("foo.txt")));
2160+
check!(f.write(b"bar"));
2161+
check!(f.flush());
2162+
drop(f);
2163+
let dir = check!(Dir::new(tmpdir.path()));
2164+
check!(dir.rename("foo.txt", &dir, "baz.txt"));
2165+
let mut f = check!(File::open(tmpdir.join("baz.txt")));
2166+
let mut buf = [0u8; 3];
2167+
check!(f.read_exact(&mut buf));
2168+
assert_eq!(b"bar", &buf);
2169+
}
2170+
2171+
#[test]
2172+
fn test_dir_create_dir() {
2173+
let tmpdir = tmpdir();
2174+
let dir = check!(Dir::new(tmpdir.path()));
2175+
check!(dir.create_dir("foo"));
2176+
check!(Dir::new(tmpdir.join("foo")));
2177+
}

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

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

4949
pub use imp::{
50-
DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions,
50+
Dir, DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions,
5151
ReadDir,
5252
};
5353

0 commit comments

Comments
 (0)