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
150 changes: 150 additions & 0 deletions library/std/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,63 @@ impl Dir {
.map(|inner| Self { inner })
}

/// Attempts to open a directory at `path` according to `opts`.
///
/// This function opens a directory. To open a file instead, see [`File::open`].
///
/// # Errors
///
/// This function will return an error if `path` does not point to an existing directory.
/// Other errors may also be returned according to [`OpenOptions::open`].
///
/// # Examples
///
/// ```no_run
/// #![feature(dirfd)]
/// use std::{fs::{Dir, OpenOptions}, io};
///
/// fn main() -> std::io::Result<()> {
/// let dir = Dir::open_with("foo", &OpenOptions::new().read(true))?;
/// let mut f = dir.open_file("bar.txt")?;
/// let contents = io::read_to_string(f)?;
/// assert_eq!(contents, "Hello, world!");
/// Ok(())
/// }
/// ```
#[unstable(feature = "dirfd", issue = "120426")]
pub fn open_with<P: AsRef<Path>>(path: P, opts: &OpenOptions) -> io::Result<Self> {
fs_imp::Dir::open(path.as_ref(), &opts.0).map(|inner| Self { inner })
}

/// Attempts to open a directory at `path` with the minimum permissions for traversal.
Comment thread
Qelxiros marked this conversation as resolved.
///
/// The permissions requested by this function are guaranteed to be sufficient to open a child
/// file or folder, but not necessarily to list all children.
///
/// # Errors
///
/// This function may return an error according to [`OpenOptions::open`].
///
/// # Examples
///
/// ```no_run
/// #![feature(dirfd)]
/// use std::{fs::Dir, io};
///
/// fn main() -> std::io::Result<()> {
/// let foo = Dir::open_for_traversal("foo")?;
/// let foobar = foo.open_dir("bar")?;
/// let mut foobarbaz = foobar.open_file("baz")?;
/// let contents = io::read_to_string(foobarbaz)?;
/// assert_eq!(contents, "Hello, world!");
/// Ok(())
/// }
/// ```
#[unstable(feature = "dirfd", issue = "120426")]
pub fn open_for_traversal<P: AsRef<Path>>(path: P) -> io::Result<Self> {
fs_imp::Dir::open_for_traversal(path.as_ref()).map(|inner| Self { inner })
}

/// Queries metadata about the underlying directory.
///
/// # Examples
Expand Down Expand Up @@ -1711,6 +1768,99 @@ impl Dir {
) -> io::Result<()> {
self.inner.rename(from.as_ref(), &to_dir.inner, to.as_ref())
}

/// Attempts to create a directory relative to this directory.
///
/// This function interprets `path` relative to the directory provided by `self`. To create a directory
/// relative to the current working directory, or at an absolute path, see
/// [`fs::create_dir`][crate::fs::create_dir].
#[unstable(feature = "dirfd", issue = "120426")]
pub fn create_dir<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
self.inner.create_dir(path.as_ref())
}

/// Attempts to open a directory in read-only mode relative to this directory.
///
/// This function interprets `path` relative to the directory provided by `self`. To open a directory
/// relative to the current working directory, or at an absolute path, see [`Dir::open`].
///
/// # Errors
///
/// This function will return an error if `path` does not point to an existing directory.
/// Other errors may also be returned according to [`OpenOptions::open`].
///
/// # Examples
///
/// ```no_run
/// #![feature(dirfd)]
/// use std::{fs::Dir};
///
/// fn main() -> std::io::Result<()> {
/// let dir = Dir::open("foo")?;
/// let foobar = dir.open_dir("bar")?;
/// Ok(())
/// }
/// ```
#[unstable(feature = "dirfd", issue = "120426")]
pub fn open_dir<P: AsRef<Path>>(&self, path: P) -> io::Result<Self> {
self.inner
.open_dir(path.as_ref(), &OpenOptions::new().read(true).0)
.map(|inner| Self { inner })
}

/// Attempts to open a directory relative to this directory according to `opts`.
///
/// This function interprets `path` relative to the directory provided by `self`. To open a directory
/// relative to the current working directory, or at an absolute path, see [`Dir::open`].
///
/// # Errors
///
/// This function will return errors according to [`OpenOptions::open`].
///
/// # Examples
///
/// ```no_run
/// #![feature(dirfd)]
/// use std::fs::{Dir, OpenOptions};
///
/// fn main() -> std::io::Result<()> {
/// let dir = Dir::open("foo")?;
/// let foobar_w = dir.open_dir_with("bar", &OpenOptions::new().write(true))?;
/// Ok(())
/// }
/// ```
#[unstable(feature = "dirfd", issue = "120426")]
pub fn open_dir_with<P: AsRef<Path>>(&self, path: P, opts: &OpenOptions) -> io::Result<Self> {
self.inner.open_dir(path.as_ref(), &opts.0).map(|inner| Self { inner })
}

/// Attempts to remove a directory relative to this directory.
///
/// This function interprets `path` relative to the directory provided by `self`. To remove a directory
/// relative to the current working directory, or at an absolute path, see
/// [`fs::remove_dir`][crate::fs::remove_dir].
///
/// # Errors
///
/// This function will return an error if `path` does not point to an existing directory.
/// Other errors may also be returned according to [`OpenOptions::open`].
///
/// # Examples
///
/// ```no_run
/// #![feature(dirfd)]
/// use std::{fs::Dir};
///
/// fn main() -> std::io::Result<()> {
/// let dir = Dir::open("foo")?;
/// dir.remove_dir("bar")?;
/// Ok(())
/// }
/// ```
#[unstable(feature = "dirfd", issue = "120426")]
pub fn remove_dir<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
self.inner.remove_dir(path.as_ref())
}
}

impl AsInner<fs_imp::Dir> for Dir {
Expand Down
35 changes: 35 additions & 0 deletions library/std/src/fs/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2711,3 +2711,38 @@ fn test_dir_rename_file() {
check!(f.read_exact(&mut buf));
assert_eq!(b"bar", &buf);
}

#[test]
fn test_dir_remove_dir() {
let tmpdir = tmpdir();
check!(fs::create_dir(tmpdir.join("foo")));
let dir = check!(Dir::open(tmpdir.path()));
check!(dir.remove_dir("foo"));
assert!(!matches!(exists(tmpdir.join("foo")), Ok(true)));
}

#[test]
fn test_dir_create_dir() {
let tmpdir = tmpdir();
let dir = check!(Dir::open(tmpdir.path()));
check!(dir.create_dir("foo"));
check!(Dir::open(tmpdir.join("foo")));
}

#[test]
fn test_dir_open_dir() {
let tmpdir = tmpdir();
let dir1 = check!(Dir::open(tmpdir.path()));
check!(dir1.create_dir("foo"));
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.flush());
drop(f);
let dir3 = check!(dir1.open_dir("foo"));
let mut f = check!(dir3.open_file("bar.txt"));
let mut buf = [0u8; 3];
check!(f.read_exact(&mut buf));
assert_eq!(b"baz", &buf);
}
20 changes: 19 additions & 1 deletion library/std/src/sys/fs/common.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![allow(dead_code)] // not used on all platforms

use crate::fs::{remove_file, rename};
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;
Expand Down Expand Up @@ -71,6 +71,12 @@ impl Dir {
path.canonicalize().map(|path| Self { path })
}

pub fn open_for_traversal(path: &Path) -> io::Result<Self> {
let mut opts = OpenOptions::new();
opts.read(true);
Self::open(path, &opts)
}

pub fn open_file(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> {
File::open(&self.path.join(path), &opts)
}
Expand All @@ -86,6 +92,18 @@ impl Dir {
pub fn rename(&self, from: &Path, to_dir: &Self, to: &Path) -> io::Result<()> {
rename(self.path.join(from), to_dir.path.join(to))
}

pub fn create_dir(&self, path: &Path) -> io::Result<()> {
create_dir(self.path.join(path))
}

pub fn open_dir(&self, path: &Path, opts: &OpenOptions) -> io::Result<Self> {
Self::open(&self.path.join(path), opts)
}

pub fn remove_dir(&self, path: &Path) -> io::Result<()> {
remove_dir(path)
}
}

impl fmt::Debug for Dir {
Expand Down
62 changes: 56 additions & 6 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, renameat, unlinkat};
use libc::{c_int, mkdirat, renameat, unlinkat};

cfg_select! {
not(
Expand Down Expand Up @@ -30,15 +30,37 @@ use crate::sys::helpers::run_path_with_cstr;
use crate::sys::{AsInner, FromInner, IntoInner, cvt, cvt_r};
use crate::{fmt, fs, io};

#[cfg(any(target_os = "freebsd", target_os = "aix"))]
const TRAVERSE_DIRECTORY: i32 = libc::O_EXEC;
#[cfg(any(target_os = "linux", target_os = "android", target_os = "l4re"))]
const TRAVERSE_DIRECTORY: i32 = libc::O_PATH;
#[cfg(target_os = "illumos")]
const TRAVERSE_DIRECTORY: i32 = libc::O_SEARCH;
#[cfg(not(any(
target_os = "aix",
target_os = "android",
target_os = "freebsd",
target_os = "illumos",
target_os = "l4re",
target_os = "linux",
)))]
const TRAVERSE_DIRECTORY: i32 = libc::O_RDONLY;

pub struct Dir(OwnedFd);

impl Dir {
pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<Self> {
run_path_with_cstr(path, &|path| Self::open_with_c(path, opts))
}

pub fn open_for_traversal(path: &Path) -> io::Result<Self> {
run_path_with_cstr(path, &|path| Self::open_traversal_c(path))
}

pub fn open_file(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> {
run_path_with_cstr(path.as_ref(), &|path| self.open_file_c(path, &opts))
run_path_with_cstr(path.as_ref(), &|path| self.open_file_c(path, &opts, 0))
.map(|fd| FileDesc::from_inner(fd))
.map(File)
}

pub fn metadata(&self) -> io::Result<FileAttr> {
Expand All @@ -61,7 +83,19 @@ impl Dir {
})
}

pub fn open_with_c(path: &CStr, opts: &OpenOptions) -> io::Result<Self> {
pub fn open_dir(&self, path: &Path, opts: &OpenOptions) -> io::Result<Self> {
run_path_with_cstr(path, &|path| self.open_file_c(path, &opts, libc::O_DIRECTORY)).map(Self)
}

pub fn create_dir(&self, path: &Path) -> io::Result<()> {
Comment thread
Qelxiros marked this conversation as resolved.
run_path_with_cstr(path.as_ref(), &|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))
}

fn open_with_c(path: &CStr, opts: &OpenOptions) -> io::Result<Self> {
let flags = libc::O_CLOEXEC
| libc::O_DIRECTORY
| opts.get_access_mode()?
Expand All @@ -71,15 +105,27 @@ impl Dir {
Ok(Self(unsafe { OwnedFd::from_raw_fd(fd) }))
}

fn open_file_c(&self, path: &CStr, opts: &OpenOptions) -> io::Result<File> {
fn open_traversal_c(path: &CStr) -> io::Result<Self> {
let flags = libc::O_CLOEXEC | libc::O_DIRECTORY | TRAVERSE_DIRECTORY;
let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, 0) })?;
Ok(Self(unsafe { OwnedFd::from_raw_fd(fd) }))
}

fn open_file_c(
&self,
path: &CStr,
opts: &OpenOptions,
extra_flags: c_int,
) -> io::Result<OwnedFd> {
let flags = libc::O_CLOEXEC
| opts.get_access_mode()?
| opts.get_creation_mode()?
| (opts.custom_flags as c_int & !libc::O_ACCMODE);
| (opts.custom_flags as c_int & !libc::O_ACCMODE)
| extra_flags;
let fd = cvt_r(|| unsafe {
openat64(self.0.as_raw_fd(), path.as_ptr(), flags, opts.mode as c_int)
})?;
Ok(File(unsafe { FileDesc::from_raw_fd(fd) }))
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
}

fn remove_c(&self, path: &CStr, remove_dir: bool) -> io::Result<()> {
Expand All @@ -99,6 +145,10 @@ impl Dir {
})
.map(|_| ())
}

fn create_dir_c(&self, path: &CStr) -> io::Result<()> {
cvt(unsafe { mkdirat(self.0.as_raw_fd(), path.as_ptr(), 0o777) }).map(|_| ())
}
}

impl fmt::Debug for Dir {
Expand Down
22 changes: 22 additions & 0 deletions library/std/src/sys/fs/windows/dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ impl Dir {
with_native_path(path, &|path| Self::open_with_native(path, opts))
}

pub fn open_for_traversal(path: &Path) -> io::Result<Self> {
let mut opts = OpenOptions::new();
opts.access_mode(c::FILE_TRAVERSE);
with_native_path(path, &|path| Self::open_with_native(path, &opts))
}

pub fn open_file(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> {
// NtCreateFile will fail if given an absolute path and a non-null RootDirectory
if path.is_absolute() {
Expand All @@ -87,6 +93,22 @@ impl Dir {
self.rename_native(&from, to_dir, &to, is_dir)
}

pub fn create_dir(&self, path: &Path) -> io::Result<()> {
let mut opts = OpenOptions::new();
opts.create_new(true);
self.open_dir(path, &opts).map(|_| ())
}

pub fn open_dir(&self, path: &Path, opts: &OpenOptions) -> io::Result<Self> {
let path = to_u16s_without_nul(&path)?;
self.open_file_native(&path, &opts, true).map(|handle| Self { handle })
}

pub fn remove_dir(&self, path: &Path) -> io::Result<()> {
let path = to_u16s_without_nul(&path)?;
self.remove_native(&path, true)
}

fn open_with_native(path: &WCStr, opts: &OpenOptions) -> io::Result<Self> {
let creation = opts.get_creation_mode()?;
let sa = c::SECURITY_ATTRIBUTES {
Expand Down
Loading