Skip to content

Commit 8adc293

Browse files
committed
add file operations to Dir
1 parent cf7da0b commit 8adc293

7 files changed

Lines changed: 310 additions & 13 deletions

File tree

library/std/src/fs.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1649,6 +1649,89 @@ impl Dir {
16491649
.open_file(path.as_ref(), &OpenOptions::new().read(true).0)
16501650
.map(|f| File { inner: f })
16511651
}
1652+
1653+
/// Attempts to open a file according to `opts` relative to this directory.
1654+
///
1655+
/// # Errors
1656+
///
1657+
/// This function will return an error if `path` does not point to an existing file.
1658+
/// Other errors may also be returned according to [`OpenOptions::open`].
1659+
///
1660+
/// # Examples
1661+
///
1662+
/// ```no_run
1663+
/// #![feature(dirfd)]
1664+
/// use std::{fs::{Dir, OpenOptions}, io::{self, Write}};
1665+
///
1666+
/// fn main() -> io::Result<()> {
1667+
/// let dir = Dir::open("foo")?;
1668+
/// let mut opts = OpenOptions::new();
1669+
/// opts.read(true).write(true);
1670+
/// let mut f = dir.open_file_with("bar.txt", &opts)?;
1671+
/// f.write(b"Hello, world!")?;
1672+
/// let contents = io::read_to_string(f)?;
1673+
/// assert_eq!(contents, "Hello, world!");
1674+
/// Ok(())
1675+
/// }
1676+
/// ```
1677+
#[unstable(feature = "dirfd", issue = "120426")]
1678+
pub fn open_file_with<P: AsRef<Path>>(&self, path: P, opts: &OpenOptions) -> io::Result<File> {
1679+
self.inner.open_file(path.as_ref(), &opts.0).map(|f| File { inner: f })
1680+
}
1681+
1682+
/// Attempts to remove a file relative to this directory.
1683+
///
1684+
/// # Errors
1685+
///
1686+
/// This function will return an error if `path` does not point to an existing file.
1687+
/// Other errors may also be returned according to [`OpenOptions::open`].
1688+
///
1689+
/// # Examples
1690+
///
1691+
/// ```no_run
1692+
/// #![feature(dirfd)]
1693+
/// use std::fs::Dir;
1694+
///
1695+
/// fn main() -> std::io::Result<()> {
1696+
/// let dir = Dir::open("foo")?;
1697+
/// dir.remove_file("bar.txt")?;
1698+
/// Ok(())
1699+
/// }
1700+
/// ```
1701+
#[unstable(feature = "dirfd", issue = "120426")]
1702+
pub fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
1703+
self.inner.remove_file(path.as_ref())
1704+
}
1705+
1706+
/// Attempts to rename a file or directory relative to this directory to a new name, replacing
1707+
/// the destination file if present.
1708+
///
1709+
/// # Errors
1710+
///
1711+
/// This function will return an error if `from` does not point to an existing file or directory.
1712+
/// Other errors may also be returned according to [`OpenOptions::open`].
1713+
///
1714+
/// # Examples
1715+
///
1716+
/// ```no_run
1717+
/// #![feature(dirfd)]
1718+
/// use std::fs::Dir;
1719+
///
1720+
/// fn main() -> std::io::Result<()> {
1721+
/// let dir = Dir::open("foo")?;
1722+
/// dir.rename("bar.txt", &dir, "quux.txt")?;
1723+
/// Ok(())
1724+
/// }
1725+
/// ```
1726+
#[unstable(feature = "dirfd", issue = "120426")]
1727+
pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(
1728+
&self,
1729+
from: P,
1730+
to_dir: &Self,
1731+
to: Q,
1732+
) -> io::Result<()> {
1733+
self.inner.rename(from.as_ref(), &to_dir.inner, to.as_ref())
1734+
}
16521735
}
16531736

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

library/std/src/fs/tests.rs

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

33
#[cfg(not(miri))]
44
use super::Dir;
5+
#[cfg(not(miri))]
6+
use crate::fs::exists;
57
use crate::fs::{self, File, FileTimes, OpenOptions, TryLockError};
68
#[cfg(not(miri))]
79
use crate::io;
@@ -2546,3 +2548,50 @@ fn test_dir_read_file() {
25462548
let buf = check!(io::read_to_string(f));
25472549
assert_eq!("bar", &buf);
25482550
}
2551+
2552+
#[test]
2553+
// FIXME: libc calls fail on miri
2554+
#[cfg(not(miri))]
2555+
fn test_dir_write_file() {
2556+
let tmpdir = tmpdir();
2557+
let dir = check!(Dir::open(tmpdir.path()));
2558+
let mut f = check!(dir.open_file_with("foo.txt", &OpenOptions::new().write(true).create(true)));
2559+
check!(f.write(b"bar"));
2560+
check!(f.flush());
2561+
drop(f);
2562+
let mut f = check!(File::open(tmpdir.join("foo.txt")));
2563+
let mut buf = [0u8; 3];
2564+
check!(f.read_exact(&mut buf));
2565+
assert_eq!(b"bar", &buf);
2566+
}
2567+
2568+
#[test]
2569+
// FIXME: libc calls fail on miri
2570+
#[cfg(not(miri))]
2571+
fn test_dir_remove_file() {
2572+
let tmpdir = tmpdir();
2573+
let mut f = check!(File::create(tmpdir.join("foo.txt")));
2574+
check!(f.write(b"bar"));
2575+
check!(f.flush());
2576+
drop(f);
2577+
let dir = check!(Dir::open(tmpdir.path()));
2578+
check!(dir.remove_file("foo.txt"));
2579+
assert!(!matches!(exists(tmpdir.join("foo.txt")), Ok(true)));
2580+
}
2581+
2582+
#[test]
2583+
// FIXME: libc calls fail on miri
2584+
#[cfg(not(miri))]
2585+
fn test_dir_rename_file() {
2586+
let tmpdir = tmpdir();
2587+
let mut f = check!(File::create(tmpdir.join("foo.txt")));
2588+
check!(f.write(b"bar"));
2589+
check!(f.flush());
2590+
drop(f);
2591+
let dir = check!(Dir::open(tmpdir.path()));
2592+
check!(dir.rename("foo.txt", &dir, "baz.txt"));
2593+
let mut f = check!(File::open(tmpdir.join("baz.txt")));
2594+
let mut buf = [0u8; 3];
2595+
check!(f.read_exact(&mut buf));
2596+
assert_eq!(b"bar", &buf);
2597+
}

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)