Skip to content

Commit 88db364

Browse files
committed
Auto merge of #150679 - Qelxiros:dirfd-files, r=<try>
dirfd file operations (2/4) try-job: x86_64-msvc-*
2 parents 77a4fb6 + 9132606 commit 88db364

7 files changed

Lines changed: 324 additions & 15 deletions

File tree

library/std/src/fs.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1547,6 +1547,8 @@ impl crate::io::IoHandle for File {}
15471547
impl Dir {
15481548
/// Attempts to open a directory at `path` in read-only mode.
15491549
///
1550+
/// This function opens a directory. To open a file instead, see [`File::open`].
1551+
///
15501552
/// # Errors
15511553
///
15521554
/// This function will return an error if `path` does not point to an existing directory.
@@ -1574,6 +1576,9 @@ impl Dir {
15741576

15751577
/// Attempts to open a file in read-only mode relative to this directory.
15761578
///
1579+
/// This function interprets `path` relative to the directory provided by `self`. To open a file
1580+
/// relative to the current working directory, or at an absolute path, see [`File::open`].
1581+
///
15771582
/// # Errors
15781583
///
15791584
/// This function will return an error if `path` does not point to an existing file.
@@ -1599,6 +1604,98 @@ impl Dir {
15991604
.open_file(path.as_ref(), &OpenOptions::new().read(true).0)
16001605
.map(|f| File { inner: f })
16011606
}
1607+
1608+
/// Attempts to open a file according to `opts` relative to this directory.
1609+
///
1610+
/// This function interprets `path` relative to the directory provided by `self`. To open a file
1611+
/// relative to the current working directory, or at an absolute path, see [`File::open`].
1612+
///
1613+
/// # Errors
1614+
///
1615+
/// This function will return an error if `path` does not point to an existing file.
1616+
/// Other errors may also be returned according to [`OpenOptions::open`].
1617+
///
1618+
/// # Examples
1619+
///
1620+
/// ```no_run
1621+
/// #![feature(dirfd)]
1622+
/// use std::{fs::{Dir, OpenOptions}, io::{self, Write}};
1623+
///
1624+
/// fn main() -> io::Result<()> {
1625+
/// let dir = Dir::open("foo")?;
1626+
/// let mut opts = OpenOptions::new();
1627+
/// opts.read(true).write(true);
1628+
/// let mut f = dir.open_file_with("bar.txt", &opts)?;
1629+
/// f.write_all(b"Hello, world!")?;
1630+
/// let contents = io::read_to_string(f)?;
1631+
/// assert_eq!(contents, "Hello, world!");
1632+
/// Ok(())
1633+
/// }
1634+
/// ```
1635+
#[unstable(feature = "dirfd", issue = "120426")]
1636+
pub fn open_file_with<P: AsRef<Path>>(&self, path: P, opts: &OpenOptions) -> io::Result<File> {
1637+
self.inner.open_file(path.as_ref(), &opts.0).map(|f| File { inner: f })
1638+
}
1639+
1640+
/// Attempts to remove a file relative to this directory.
1641+
///
1642+
/// This function interprets `path` relative to the directory provided by `self`. To remove a file
1643+
/// relative to the current working directory, or at an absolute path, see [`fs::remove_file`][remove_file].
1644+
///
1645+
/// # Errors
1646+
///
1647+
/// This function will return an error if `path` does not point to an existing file.
1648+
/// Other errors may also be returned according to [`OpenOptions::open`].
1649+
///
1650+
/// # Examples
1651+
///
1652+
/// ```no_run
1653+
/// #![feature(dirfd)]
1654+
/// use std::fs::Dir;
1655+
///
1656+
/// fn main() -> std::io::Result<()> {
1657+
/// let dir = Dir::open("foo")?;
1658+
/// dir.remove_file("bar.txt")?;
1659+
/// Ok(())
1660+
/// }
1661+
/// ```
1662+
#[unstable(feature = "dirfd", issue = "120426")]
1663+
pub fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
1664+
self.inner.remove_file(path.as_ref())
1665+
}
1666+
1667+
/// Attempts to rename a file or directory relative to this directory to a new name, replacing
1668+
/// the destination file if present.
1669+
///
1670+
/// This function interprets `from` relative to the directory provided by `self` and `to` relative to the directory
1671+
/// provided by `to_dir`. To rename a file relative to the current working directory, or at an absolute path, see [`fs::rename`][rename].
1672+
///
1673+
/// # Errors
1674+
///
1675+
/// This function will return an error if `from` does not point to an existing file or directory.
1676+
/// Other errors may also be returned according to [`OpenOptions::open`].
1677+
///
1678+
/// # Examples
1679+
///
1680+
/// ```no_run
1681+
/// #![feature(dirfd)]
1682+
/// use std::fs::Dir;
1683+
///
1684+
/// fn main() -> std::io::Result<()> {
1685+
/// let dir = Dir::open("foo")?;
1686+
/// dir.rename("bar.txt", &dir, "quux.txt")?;
1687+
/// Ok(())
1688+
/// }
1689+
/// ```
1690+
#[unstable(feature = "dirfd", issue = "120426")]
1691+
pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(
1692+
&self,
1693+
from: P,
1694+
to_dir: &Self,
1695+
to: Q,
1696+
) -> io::Result<()> {
1697+
self.inner.rename(from.as_ref(), &to_dir.inner, to.as_ref())
1698+
}
16021699
}
16031700

16041701
impl AsInner<fs_imp::Dir> for Dir {

library/std/src/fs/tests.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use rand::RngCore;
22

33
use super::Dir;
4+
#[cfg(not(miri))]
5+
use crate::fs::exists;
46
use crate::fs::{self, File, FileTimes, OpenOptions, TryLockError};
57
use crate::io::prelude::*;
68
use crate::io::{BorrowedBuf, ErrorKind, SeekFrom};
@@ -2505,8 +2507,7 @@ fn test_dir_smoke_test() {
25052507
fn test_dir_read_file() {
25062508
let tmpdir = tmpdir();
25072509
let mut f = check!(File::create(tmpdir.join("foo.txt")));
2508-
check!(f.write(b"bar"));
2509-
check!(f.flush());
2510+
check!(f.write_all(b"bar"));
25102511
drop(f);
25112512
let dir = check!(Dir::open(tmpdir.path()));
25122513
let f = check!(dir.open_file("foo.txt"));
@@ -2516,3 +2517,49 @@ fn test_dir_read_file() {
25162517
let buf = check!(io::read_to_string(f));
25172518
assert_eq!("bar", &buf);
25182519
}
2520+
2521+
#[test]
2522+
// FIXME: libc calls fail on miri
2523+
#[cfg(not(miri))]
2524+
fn test_dir_write_file() {
2525+
let tmpdir = tmpdir();
2526+
let dir = check!(Dir::open(tmpdir.path()));
2527+
let mut f = check!(dir.open_file_with("foo.txt", &OpenOptions::new().write(true).create(true)));
2528+
check!(f.write(b"bar"));
2529+
check!(f.flush());
2530+
drop(f);
2531+
let mut f = check!(File::open(tmpdir.join("foo.txt")));
2532+
let mut buf = [0u8; 3];
2533+
check!(f.read_exact(&mut buf));
2534+
assert_eq!(b"bar", &buf);
2535+
}
2536+
2537+
#[test]
2538+
// FIXME: libc calls fail on miri
2539+
#[cfg(not(miri))]
2540+
fn test_dir_remove_file() {
2541+
let tmpdir = tmpdir();
2542+
let mut f = check!(File::create(tmpdir.join("foo.txt")));
2543+
check!(f.write(b"bar"));
2544+
check!(f.flush());
2545+
drop(f);
2546+
let dir = check!(Dir::open(tmpdir.path()));
2547+
check!(dir.remove_file("foo.txt"));
2548+
assert!(!matches!(exists(tmpdir.join("foo.txt")), Ok(true)));
2549+
}
2550+
2551+
#[test]
2552+
// FIXME: libc calls fail on miri
2553+
#[cfg(not(miri))]
2554+
fn test_dir_rename_file() {
2555+
let tmpdir = tmpdir();
2556+
let mut f = check!(File::create(tmpdir.join("foo.txt")));
2557+
check!(f.write_all(b"bar"));
2558+
drop(f);
2559+
let dir = check!(Dir::open(tmpdir.path()));
2560+
check!(dir.rename("foo.txt", &dir, "baz.txt"));
2561+
let mut f = check!(File::open(tmpdir.join("baz.txt")));
2562+
let mut buf = [0u8; 3];
2563+
check!(f.read_exact(&mut buf));
2564+
assert_eq!(b"bar", &buf);
2565+
}

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

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

3+
use crate::fs::{remove_file, rename};
34
use crate::io::{self, Error, ErrorKind};
45
use crate::path::{Path, PathBuf};
56
use crate::sys::fs::{File, OpenOptions};
@@ -72,6 +73,14 @@ impl Dir {
7273
pub fn open_file(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> {
7374
File::open(&self.path.join(path), &opts)
7475
}
76+
77+
pub fn remove_file(&self, path: &Path) -> io::Result<()> {
78+
remove_file(self.path.join(path))
79+
}
80+
81+
pub fn rename(&self, from: &Path, to_dir: &Self, to: &Path) -> io::Result<()> {
82+
rename(self.path.join(from), to_dir.path.join(to))
83+
}
7584
}
7685

7786
impl fmt::Debug for Dir {

library/std/src/sys/fs/unix/dir.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use libc::c_int;
1+
use libc::{c_int, renameat, unlinkat};
22

33
cfg_select! {
44
not(
@@ -27,7 +27,7 @@ use crate::sys::fd::FileDesc;
2727
use crate::sys::fs::OpenOptions;
2828
use crate::sys::fs::unix::{File, debug_path_fd};
2929
use crate::sys::helpers::run_path_with_cstr;
30-
use crate::sys::{AsInner, FromInner, IntoInner, cvt_r};
30+
use crate::sys::{AsInner, FromInner, IntoInner, cvt, cvt_r};
3131
use crate::{fmt, fs, io};
3232

3333
pub struct Dir(OwnedFd);
@@ -41,6 +41,16 @@ impl Dir {
4141
run_path_with_cstr(path.as_ref(), &|path| self.open_file_c(path, &opts))
4242
}
4343

44+
pub fn remove_file(&self, path: &Path) -> io::Result<()> {
45+
run_path_with_cstr(path, &|path| self.remove_c(path, false))
46+
}
47+
48+
pub fn rename(&self, from: &Path, to_dir: &Self, to: &Path) -> io::Result<()> {
49+
run_path_with_cstr(from, &|from| {
50+
run_path_with_cstr(to, &|to| self.rename_c(from, to_dir, to))
51+
})
52+
}
53+
4454
pub fn open_with_c(path: &CStr, opts: &OpenOptions) -> io::Result<Self> {
4555
let flags = libc::O_CLOEXEC
4656
| libc::O_DIRECTORY
@@ -61,6 +71,24 @@ impl Dir {
6171
})?;
6272
Ok(File(unsafe { FileDesc::from_raw_fd(fd) }))
6373
}
74+
75+
fn remove_c(&self, path: &CStr, remove_dir: bool) -> io::Result<()> {
76+
cvt(unsafe {
77+
unlinkat(
78+
self.0.as_raw_fd(),
79+
path.as_ptr(),
80+
if remove_dir { libc::AT_REMOVEDIR } else { 0 },
81+
)
82+
})
83+
.map(|_| ())
84+
}
85+
86+
fn rename_c(&self, from: &CStr, to_dir: &Self, to: &CStr) -> io::Result<()> {
87+
cvt(unsafe {
88+
renameat(self.0.as_raw_fd(), from.as_ptr(), to_dir.0.as_raw_fd(), to.as_ptr())
89+
})
90+
.map(|_| ())
91+
}
6492
}
6593

6694
impl fmt::Debug for Dir {

0 commit comments

Comments
 (0)