Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions library/std/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1869,6 +1869,15 @@ impl Dir {
pub fn remove_dir<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
self.inner.remove_dir(path.as_ref())
}

/// Attempts to create a new symbolic link on the filesystem.
///
/// If `original` is a relative path, it is interpreted relative to the created link.
/// If `link` is a relative path, it is interpreted relative to `self`.
#[unstable(feature = "dirfd", issue = "120426")]
pub fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, original: P, link: Q) -> io::Result<()> {
self.inner.symlink(original.as_ref(), link.as_ref())
}
}

impl AsInner<fs_imp::Dir> for Dir {
Expand Down Expand Up @@ -2863,6 +2872,107 @@ impl DirEntry {
pub fn file_name(&self) -> OsString {
self.0.file_name()
}

/// Opens the file represented by `self` in read-only mode.
///
/// # Errors
///
/// This function will return an error if `self` does not represent a regular file.
/// Other errors may also be returned according to [`OpenOptions::open`].
///
/// # Examples
///
/// ```
/// use std::fs;
///
/// if let Ok(entries) = fs::read_dir(".") {
/// for entry in entries {
/// if let Ok(entry) = entry && entry.path().is_file() {
/// println!("{}", fs::read_to_string(entry.open()));
/// }
/// }
/// }
/// ```
#[unstable(feature = "dirfd", issue = "120426")]
pub fn open(&self) -> io::Result<File> {
self.0.open_with(&OpenOptions::new().read(true).0).map(|inner| File { inner })
}

/// Opens the file represented by `self` according to `options`.
///
/// # Errors
///
/// Errors may be returned according to [`OpenOptions::open`].
///
/// # Examples
///
/// ```
/// use std::fs;
/// use std::io::Write;
///
/// if let Ok(entries) = fs::read_dir(".") {
/// for entry in entries {
/// if let Ok(entry) = entry && entry.path().is_file() {
/// let file = entry.open_with(&OpenOptions::new().read(true).write(true));
/// let _ = file.write_all(b"foo");
/// }
/// }
/// }
/// ```
#[unstable(feature = "dirfd", issue = "120426")]
pub fn open_with(&self, options: &OpenOptions) -> io::Result<File> {
self.0.open_with(&options.0).map(|inner| File { inner })
}

/// Removes the file represented by `self`.
///
/// # Errors
///
/// This function returns an error if `self` isn't a file. Errors may also be returned for other
/// reasons such as incorrect permissions.
///
/// # Examples
///
/// ```
/// use std::fs;
///
/// if let Ok(entries) = fs::read_dir(".") {
/// for entry in entries {
/// if let Ok(entry) = entry && entry.path().is_file() {
/// let _ = entry.remove_file();
/// }
/// }
/// }
/// ```
#[unstable(feature = "dirfd", issue = "120426")]
pub fn remove_file(&self) -> io::Result<()> {
self.0.remove_file()
}

/// Removes the directory represented by `self`.
///
/// # Errors
///
/// This function returns an error if `self` isn't a directory. Errors may also be returned for other
/// reasons such as incorrect permissions or a non-empty directory.
///
/// # Examples
///
/// ```
/// use std::fs;
///
/// if let Ok(entries) = fs::read_dir(".") {
/// for entry in entries {
/// if let Ok(entry) = entry && entry.path().is_dir() {
/// let _ = entry.remove_dir();
/// }
/// }
/// }
/// ```
#[unstable(feature = "dirfd", issue = "120426")]
pub fn remove_dir(&self) -> io::Result<()> {
self.0.remove_dir()
}
}

#[stable(feature = "dir_entry_debug", since = "1.13.0")]
Expand Down
77 changes: 73 additions & 4 deletions library/std/src/fs/tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use rand::RngCore;

use super::Dir;
use crate::fs::{self, File, FileTimes, OpenOptions, TryLockError, exists};
use crate::fs::{self, File, FileTimes, OpenOptions, TryLockError, exists, read_dir};
use crate::io::prelude::*;
use crate::io::{BorrowedBuf, ErrorKind, SeekFrom};
use crate::mem::MaybeUninit;
Expand Down Expand Up @@ -2722,7 +2722,7 @@ fn test_dir_write_file() {
let tmpdir = tmpdir();
let dir = check!(Dir::open(tmpdir.path()));
let mut f = check!(dir.open_file_with("foo.txt", &OpenOptions::new().write(true).create(true)));
check!(f.write(b"bar"));
check!(f.write_all(b"bar"));
check!(f.flush());
drop(f);
let mut f = check!(File::open(tmpdir.join("foo.txt")));
Expand All @@ -2735,7 +2735,7 @@ fn test_dir_write_file() {
fn test_dir_remove_file() {
let tmpdir = tmpdir();
let mut f = check!(File::create(tmpdir.join("foo.txt")));
check!(f.write(b"bar"));
check!(f.write_all(b"bar"));
check!(f.flush());
drop(f);
let dir = check!(Dir::open(tmpdir.path()));
Expand Down Expand Up @@ -2782,7 +2782,7 @@ fn test_dir_open_dir() {
let dir2 = check!(Dir::open(tmpdir.path().join("foo")));
let mut f =
check!(dir2.open_file_with("bar.txt", &OpenOptions::new().create(true).write(true)));
check!(f.write(b"baz"));
check!(f.write_all(b"baz"));
check!(f.flush());
drop(f);
let dir3 = check!(dir1.open_dir("foo"));
Expand All @@ -2791,3 +2791,72 @@ fn test_dir_open_dir() {
check!(f.read_exact(&mut buf));
assert_eq!(b"baz", &buf);
}

#[test]
fn test_dir_symlink() {
let tmpdir = tmpdir();
if !got_symlink_permission(&tmpdir) {
return;
};

let dir = check!(Dir::open(tmpdir.path()));
let mut f = check!(dir.open_file_with("foo.txt", &OpenOptions::new().write(true).create(true)));
check!(f.write(b"quux"));
check!(f.flush());
drop(f);
check!(dir.symlink("foo.txt", "bar.txt"));
let mut f = check!(dir.open_file("bar.txt"));
let mut buf = [0u8; 4];
check!(f.read_exact(&mut buf));
assert_eq!(b"quux", &buf);
}

#[test]
fn test_dir_direntry_open() {
let tmpdir = tmpdir();
let mut file1 = check!(File::create(tmpdir.path().join("foo.txt")));
let mut file2 = check!(File::create(tmpdir.path().join("bar.txt")));
check!(file1.write_all(b"baz"));
check!(file2.write_all(b"baz"));

for dirent in check!(read_dir(tmpdir.path())) {
let mut file = check!(check!(dirent).open());
let mut buf = [0u8; 3];
check!(file.read_exact(&mut buf));
assert_eq!(b"baz", &buf);
}
}

#[test]
fn test_dir_direntry_open_with() {
let tmpdir = tmpdir();
check!(File::create(tmpdir.path().join("foo.txt")));

for dirent in check!(read_dir(tmpdir.path())) {
let dirent = check!(dirent);
let mut file = check!(dirent.open_with(&OpenOptions::new().read(true).write(true)));
check!(file.write_all(b"baz"));
let contents = check!(fs::read_to_string(dirent.path()));
assert_eq!("baz", contents);
}
}

#[test]
fn test_dir_direntry_remove() {
let tmpdir = tmpdir();
check!(File::create(tmpdir.path().join("foo.txt")));
check!(File::create(tmpdir.path().join("bar.txt")));
check!(fs::create_dir(tmpdir.path().join("baz")));

for dirent in check!(read_dir(tmpdir.path())) {
let dirent = check!(dirent);
if dirent.path().is_file() {
check!(dirent.remove_file());
}
if dirent.path().is_dir() {
check!(dirent.remove_dir());
}
}

assert!(fs::read_dir(tmpdir.path()).is_ok_and(|i| i.count() == 0))
}
6 changes: 5 additions & 1 deletion library/std/src/sys/fs/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::fs::{create_dir, remove_dir, remove_file, rename};
use crate::io::{self, Error, ErrorKind};
use crate::path::{Path, PathBuf};
use crate::sys::IntoInner;
use crate::sys::fs::{File, FileAttr, OpenOptions};
use crate::sys::fs::{File, FileAttr, OpenOptions, symlink};
use crate::sys::helpers::ignore_notfound;
use crate::{fmt, fs};

Expand Down Expand Up @@ -104,6 +104,10 @@ impl Dir {
pub fn remove_dir(&self, path: &Path) -> io::Result<()> {
remove_dir(path)
}

pub fn symlink(&self, original: &Path, link: &Path) -> io::Result<()> {
symlink(original, link)
}
}

impl fmt::Debug for Dir {
Expand Down
15 changes: 15 additions & 0 deletions library/std/src/sys/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,18 @@ pub fn set_times(path: &Path, times: FileTimes) -> io::Result<()> {
pub fn set_times_nofollow(path: &Path, times: FileTimes) -> io::Result<()> {
with_native_path(path, &|path| imp::set_times_nofollow(path, times.clone()))
}

#[cfg(not(any(target_family = "unix", target_os = "wasi")))]
impl DirEntry {
pub fn open_with(&self, opts: &OpenOptions) -> io::Result<File> {
File::open(&self.path(), opts)
}

pub fn remove_file(&self) -> io::Result<()> {
remove_file(&self.path())
}

pub fn remove_dir(&self) -> io::Result<()> {
remove_dir(&self.path())
}
}
21 changes: 21 additions & 0 deletions library/std/src/sys/fs/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,27 @@ impl DirEntry {
pub fn file_name_os_str(&self) -> &OsStr {
OsStr::from_bytes(self.name.as_bytes())
}

pub fn open_with(&self, opts: &OpenOptions) -> io::Result<File> {
let dir = unsafe {
mem::ManuallyDrop::new(Dir(OwnedFd::from_raw_fd(cvt(dirfd(self.dir.dirp.0))?)))
};
dir.open_file_c(&self.name, opts, 0).map(FileDesc::from_inner).map(File)
}

pub fn remove_file(&self) -> io::Result<()> {
let dir = unsafe {
mem::ManuallyDrop::new(Dir(OwnedFd::from_raw_fd(cvt(dirfd(self.dir.dirp.0))?)))
};
dir.remove_c(&self.name, false)
}

pub fn remove_dir(&self) -> io::Result<()> {
let dir = unsafe {
mem::ManuallyDrop::new(Dir(OwnedFd::from_raw_fd(cvt(dirfd(self.dir.dirp.0))?)))
};
dir.remove_c(&self.name, true)
}
}

impl OpenOptions {
Expand Down
20 changes: 15 additions & 5 deletions library/std/src/sys/fs/unix/dir.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use libc::{c_int, mkdirat, renameat, unlinkat};
use libc::{c_int, mkdirat, renameat, symlinkat, unlinkat};

cfg_select! {
not(any(
Expand Down Expand Up @@ -36,7 +36,7 @@ const TRAVERSE_DIRECTORY: i32 =
_ => libc::O_RDONLY,
};

pub struct Dir(OwnedFd);
pub struct Dir(pub OwnedFd);

impl Dir {
pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<Self> {
Expand Down Expand Up @@ -78,13 +78,19 @@ impl Dir {
}

pub fn create_dir(&self, path: &Path) -> io::Result<()> {
run_path_with_cstr(path.as_ref(), &|path| self.create_dir_c(path))
run_path_with_cstr(path, &|path| self.create_dir_c(path))
}

pub fn remove_dir(&self, path: &Path) -> io::Result<()> {
run_path_with_cstr(path, &|path| self.remove_c(path, true))
}

pub fn symlink(&self, original: &Path, link: &Path) -> io::Result<()> {
run_path_with_cstr(original, &|original| {
run_path_with_cstr(link, &|link| self.symlink_c(original, link))
})
}

fn open_with_c(path: &CStr, opts: &OpenOptions) -> io::Result<Self> {
let flags = libc::O_CLOEXEC
| libc::O_DIRECTORY
Expand All @@ -101,7 +107,7 @@ impl Dir {
Ok(Self(unsafe { OwnedFd::from_raw_fd(fd) }))
}

fn open_file_c(
pub fn open_file_c(
&self,
path: &CStr,
opts: &OpenOptions,
Expand All @@ -118,7 +124,7 @@ impl Dir {
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
}

fn remove_c(&self, path: &CStr, remove_dir: bool) -> io::Result<()> {
pub fn remove_c(&self, path: &CStr, remove_dir: bool) -> io::Result<()> {
cvt(unsafe {
unlinkat(
self.0.as_raw_fd(),
Expand All @@ -139,6 +145,10 @@ impl Dir {
fn create_dir_c(&self, path: &CStr) -> io::Result<()> {
cvt(unsafe { mkdirat(self.0.as_raw_fd(), path.as_ptr(), 0o777) }).map(|_| ())
}

fn symlink_c(&self, original: &CStr, link: &CStr) -> io::Result<()> {
cvt(unsafe { symlinkat(original.as_ptr(), self.0.as_raw_fd(), link.as_ptr()) }).map(|_| ())
}
}

impl fmt::Debug for Dir {
Expand Down
Loading
Loading