Skip to content

Commit dd0551e

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 c6768de + 58b0877 commit dd0551e

File tree

5 files changed

+843
-91
lines changed

5 files changed

+843
-91
lines changed

library/std/src/fs.rs

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

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

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

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)