mirror of
https://github.com/rust-lang/rust.git
synced 2026-01-25 07:48:44 +00:00
Auto merge of #146341 - Qelxiros:dirfd-minimum, r=tgross35
minimal dirfd implementation (1/4) This is the first of four smaller PRs that will eventually be equivalent to rust-lang/rust#139514. A few notes: - I renamed `new` to `open` because `open_dir` takes `&self` and opens a subdirectory. - I also renamed `open` to `open_file`. - I'm not sure how to `impl AsRawFd` and friends because the `common` implementation uses `PathBuf`s. How should I proceed here? The other PRs will be based on this one, so I'll make drafts and mark them ready as their predecessors get merged. They might take a bit though; I've never done this particular thing with git before. Tracking issue: https://github.com/rust-lang/rust/issues/120426 try-job: aarch64-apple try-job: dist-various* try-job: test-various* try-job: x86_64-msvc-1 try-job: x86_64-mingw*
This commit is contained in:
@@ -152,6 +152,43 @@ pub enum TryLockError {
|
||||
WouldBlock,
|
||||
}
|
||||
|
||||
/// An object providing access to a directory on the filesystem.
|
||||
///
|
||||
/// Directories are automatically closed when they go out of scope. Errors detected
|
||||
/// on closing are ignored by the implementation of `Drop`.
|
||||
///
|
||||
/// # Platform-specific behavior
|
||||
///
|
||||
/// On supported systems (including Windows and some UNIX-based OSes), this function acquires a
|
||||
/// handle/file descriptor for the directory. This allows functions like [`Dir::open_file`] to
|
||||
/// avoid [TOCTOU] errors when the directory itself is being moved.
|
||||
///
|
||||
/// On other systems, it stores an absolute path (see [`canonicalize()`]). In the latter case, no
|
||||
/// [TOCTOU] guarantees are made.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Opens a directory and then a file inside it.
|
||||
///
|
||||
/// ```no_run
|
||||
/// #![feature(dirfd)]
|
||||
/// use std::{fs::Dir, io};
|
||||
///
|
||||
/// fn main() -> std::io::Result<()> {
|
||||
/// let dir = Dir::open("foo")?;
|
||||
/// let mut file = dir.open_file("bar.txt")?;
|
||||
/// let contents = io::read_to_string(file)?;
|
||||
/// assert_eq!(contents, "Hello, world!");
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [TOCTOU]: self#time-of-check-to-time-of-use-toctou
|
||||
#[unstable(feature = "dirfd", issue = "120426")]
|
||||
pub struct Dir {
|
||||
inner: fs_imp::Dir,
|
||||
}
|
||||
|
||||
/// Metadata information about a file.
|
||||
///
|
||||
/// This structure is returned from the [`metadata`] or
|
||||
@@ -1554,6 +1591,87 @@ impl Seek for Arc<File> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dir {
|
||||
/// Attempts to open a directory at `path` in read-only mode.
|
||||
///
|
||||
/// # 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, io};
|
||||
///
|
||||
/// fn main() -> std::io::Result<()> {
|
||||
/// let dir = Dir::open("foo")?;
|
||||
/// 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<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
fs_imp::Dir::open(path.as_ref(), &OpenOptions::new().read(true).0)
|
||||
.map(|inner| Self { inner })
|
||||
}
|
||||
|
||||
/// Attempts to open a file in read-only mode relative to this directory.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if `path` does not point to an existing file.
|
||||
/// Other errors may also be returned according to [`OpenOptions::open`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// #![feature(dirfd)]
|
||||
/// use std::{fs::Dir, io};
|
||||
///
|
||||
/// fn main() -> std::io::Result<()> {
|
||||
/// let dir = Dir::open("foo")?;
|
||||
/// 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_file<P: AsRef<Path>>(&self, path: P) -> io::Result<File> {
|
||||
self.inner
|
||||
.open_file(path.as_ref(), &OpenOptions::new().read(true).0)
|
||||
.map(|f| File { inner: f })
|
||||
}
|
||||
}
|
||||
|
||||
impl AsInner<fs_imp::Dir> for Dir {
|
||||
#[inline]
|
||||
fn as_inner(&self) -> &fs_imp::Dir {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
impl FromInner<fs_imp::Dir> for Dir {
|
||||
fn from_inner(f: fs_imp::Dir) -> Dir {
|
||||
Dir { inner: f }
|
||||
}
|
||||
}
|
||||
impl IntoInner<fs_imp::Dir> for Dir {
|
||||
fn into_inner(self) -> fs_imp::Dir {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "dirfd", issue = "120426")]
|
||||
impl fmt::Debug for Dir {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.inner.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenOptions {
|
||||
/// Creates a blank new set of options ready for configuration.
|
||||
///
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
use rand::RngCore;
|
||||
|
||||
#[cfg(not(miri))]
|
||||
use super::Dir;
|
||||
use crate::assert_matches::assert_matches;
|
||||
use crate::fs::{self, File, FileTimes, OpenOptions, TryLockError};
|
||||
#[cfg(not(miri))]
|
||||
use crate::io;
|
||||
use crate::io::prelude::*;
|
||||
use crate::io::{BorrowedBuf, ErrorKind, SeekFrom};
|
||||
use crate::mem::MaybeUninit;
|
||||
@@ -2465,3 +2469,30 @@ fn test_fs_set_times_nofollow() {
|
||||
assert_ne!(target_metadata.accessed().unwrap(), accessed);
|
||||
assert_ne!(target_metadata.modified().unwrap(), modified);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// FIXME: libc calls fail on miri
|
||||
#[cfg(not(miri))]
|
||||
fn test_dir_smoke_test() {
|
||||
let tmpdir = tmpdir();
|
||||
let dir = Dir::open(tmpdir.path());
|
||||
check!(dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// FIXME: libc calls fail on miri
|
||||
#[cfg(not(miri))]
|
||||
fn test_dir_read_file() {
|
||||
let tmpdir = tmpdir();
|
||||
let mut f = check!(File::create(tmpdir.join("foo.txt")));
|
||||
check!(f.write(b"bar"));
|
||||
check!(f.flush());
|
||||
drop(f);
|
||||
let dir = check!(Dir::open(tmpdir.path()));
|
||||
let f = check!(dir.open_file("foo.txt"));
|
||||
let buf = check!(io::read_to_string(f));
|
||||
assert_eq!("bar", &buf);
|
||||
let f = check!(dir.open_file(tmpdir.join("foo.txt")));
|
||||
let buf = check!(io::read_to_string(f));
|
||||
assert_eq!("bar", &buf);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#![allow(dead_code)] // not used on all platforms
|
||||
|
||||
use crate::fs;
|
||||
use crate::io::{self, Error, ErrorKind};
|
||||
use crate::path::Path;
|
||||
use crate::path::{Path, PathBuf};
|
||||
use crate::sys::fs::{File, OpenOptions};
|
||||
use crate::sys::helpers::ignore_notfound;
|
||||
use crate::{fmt, fs};
|
||||
|
||||
pub(crate) const NOT_FILE_ERROR: Error = io::const_error!(
|
||||
ErrorKind::InvalidInput,
|
||||
@@ -58,3 +59,23 @@ pub fn exists(path: &Path) -> io::Result<bool> {
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Dir {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl Dir {
|
||||
pub fn open(path: &Path, _opts: &OpenOptions) -> io::Result<Self> {
|
||||
path.canonicalize().map(|path| Self { path })
|
||||
}
|
||||
|
||||
pub fn open_file(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> {
|
||||
File::open(&self.path.join(path), &opts)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Dir {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Dir").field("path", &self.path).finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::os::hermit::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, Raw
|
||||
use crate::path::{Path, PathBuf};
|
||||
use crate::sync::Arc;
|
||||
use crate::sys::fd::FileDesc;
|
||||
pub use crate::sys::fs::common::{copy, exists};
|
||||
pub use crate::sys::fs::common::{Dir, copy, exists};
|
||||
use crate::sys::helpers::run_path_with_cstr;
|
||||
use crate::sys::time::SystemTime;
|
||||
use crate::sys::{AsInner, AsInnerMut, FromInner, IntoInner, cvt, unsupported, unsupported_err};
|
||||
@@ -18,6 +18,7 @@ use crate::{fmt, mem};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct File(FileDesc);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FileAttr {
|
||||
stat_val: stat_struct,
|
||||
|
||||
@@ -59,7 +59,7 @@ pub fn with_native_path<T>(path: &Path, f: &dyn Fn(&Path) -> io::Result<T>) -> i
|
||||
}
|
||||
|
||||
pub use imp::{
|
||||
DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions,
|
||||
Dir, DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions,
|
||||
ReadDir,
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::os::raw::{c_int, c_short};
|
||||
use crate::os::solid::ffi::OsStrExt;
|
||||
use crate::path::{Path, PathBuf};
|
||||
use crate::sync::Arc;
|
||||
pub use crate::sys::fs::common::exists;
|
||||
pub use crate::sys::fs::common::{Dir, exists};
|
||||
use crate::sys::helpers::ignore_notfound;
|
||||
use crate::sys::pal::{abi, error};
|
||||
use crate::sys::time::SystemTime;
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::fs::TryLockError;
|
||||
use crate::hash::Hash;
|
||||
use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom};
|
||||
use crate::path::{Path, PathBuf};
|
||||
pub use crate::sys::fs::common::Dir;
|
||||
use crate::sys::pal::{helpers, unsupported};
|
||||
use crate::sys::time::SystemTime;
|
||||
|
||||
|
||||
@@ -257,7 +257,7 @@ cfg_has_statx! {{
|
||||
|
||||
// all DirEntry's will have a reference to this struct
|
||||
struct InnerReadDir {
|
||||
dirp: Dir,
|
||||
dirp: DirStream,
|
||||
root: PathBuf,
|
||||
}
|
||||
|
||||
@@ -272,10 +272,134 @@ impl ReadDir {
|
||||
}
|
||||
}
|
||||
|
||||
struct Dir(*mut libc::DIR);
|
||||
struct DirStream(*mut libc::DIR);
|
||||
|
||||
unsafe impl Send for Dir {}
|
||||
unsafe impl Sync for Dir {}
|
||||
// dir::Dir requires openat support
|
||||
cfg_select! {
|
||||
any(
|
||||
target_os = "redox",
|
||||
target_os = "espidf",
|
||||
target_os = "horizon",
|
||||
target_os = "vita",
|
||||
target_os = "nto",
|
||||
target_os = "vxworks",
|
||||
) => {
|
||||
pub use crate::sys::fs::common::Dir;
|
||||
}
|
||||
_ => {
|
||||
mod dir;
|
||||
pub use dir::Dir;
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_path_fd<'a, 'b>(
|
||||
fd: c_int,
|
||||
f: &'a mut fmt::Formatter<'b>,
|
||||
name: &str,
|
||||
) -> fmt::DebugStruct<'a, 'b> {
|
||||
let mut b = f.debug_struct(name);
|
||||
|
||||
fn get_mode(fd: c_int) -> Option<(bool, bool)> {
|
||||
let mode = unsafe { libc::fcntl(fd, libc::F_GETFL) };
|
||||
if mode == -1 {
|
||||
return None;
|
||||
}
|
||||
match mode & libc::O_ACCMODE {
|
||||
libc::O_RDONLY => Some((true, false)),
|
||||
libc::O_RDWR => Some((true, true)),
|
||||
libc::O_WRONLY => Some((false, true)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
b.field("fd", &fd);
|
||||
if let Some(path) = get_path_from_fd(fd) {
|
||||
b.field("path", &path);
|
||||
}
|
||||
if let Some((read, write)) = get_mode(fd) {
|
||||
b.field("read", &read).field("write", &write);
|
||||
}
|
||||
|
||||
b
|
||||
}
|
||||
|
||||
fn get_path_from_fd(fd: c_int) -> Option<PathBuf> {
|
||||
#[cfg(any(target_os = "linux", target_os = "illumos", target_os = "solaris"))]
|
||||
fn get_path(fd: c_int) -> Option<PathBuf> {
|
||||
let mut p = PathBuf::from("/proc/self/fd");
|
||||
p.push(&fd.to_string());
|
||||
run_path_with_cstr(&p, &readlink).ok()
|
||||
}
|
||||
|
||||
#[cfg(any(target_vendor = "apple", target_os = "netbsd"))]
|
||||
fn get_path(fd: c_int) -> Option<PathBuf> {
|
||||
// FIXME: The use of PATH_MAX is generally not encouraged, but it
|
||||
// is inevitable in this case because Apple targets and NetBSD define `fcntl`
|
||||
// with `F_GETPATH` in terms of `MAXPATHLEN`, and there are no
|
||||
// alternatives. If a better method is invented, it should be used
|
||||
// instead.
|
||||
let mut buf = vec![0; libc::PATH_MAX as usize];
|
||||
let n = unsafe { libc::fcntl(fd, libc::F_GETPATH, buf.as_ptr()) };
|
||||
if n == -1 {
|
||||
cfg_select! {
|
||||
target_os = "netbsd" => {
|
||||
// fallback to procfs as last resort
|
||||
let mut p = PathBuf::from("/proc/self/fd");
|
||||
p.push(&fd.to_string());
|
||||
return run_path_with_cstr(&p, &readlink).ok()
|
||||
}
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
let l = buf.iter().position(|&c| c == 0).unwrap();
|
||||
buf.truncate(l as usize);
|
||||
buf.shrink_to_fit();
|
||||
Some(PathBuf::from(OsString::from_vec(buf)))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
fn get_path(fd: c_int) -> Option<PathBuf> {
|
||||
let info = Box::<libc::kinfo_file>::new_zeroed();
|
||||
let mut info = unsafe { info.assume_init() };
|
||||
info.kf_structsize = size_of::<libc::kinfo_file>() as libc::c_int;
|
||||
let n = unsafe { libc::fcntl(fd, libc::F_KINFO, &mut *info) };
|
||||
if n == -1 {
|
||||
return None;
|
||||
}
|
||||
let buf = unsafe { CStr::from_ptr(info.kf_path.as_mut_ptr()).to_bytes().to_vec() };
|
||||
Some(PathBuf::from(OsString::from_vec(buf)))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "vxworks")]
|
||||
fn get_path(fd: c_int) -> Option<PathBuf> {
|
||||
let mut buf = vec![0; libc::PATH_MAX as usize];
|
||||
let n = unsafe { libc::ioctl(fd, libc::FIOGETNAME, buf.as_ptr()) };
|
||||
if n == -1 {
|
||||
return None;
|
||||
}
|
||||
let l = buf.iter().position(|&c| c == 0).unwrap();
|
||||
buf.truncate(l as usize);
|
||||
Some(PathBuf::from(OsString::from_vec(buf)))
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "linux",
|
||||
target_os = "vxworks",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "illumos",
|
||||
target_os = "solaris",
|
||||
target_vendor = "apple",
|
||||
)))]
|
||||
fn get_path(_fd: c_int) -> Option<PathBuf> {
|
||||
// FIXME(#24570): implement this for other Unix platforms
|
||||
None
|
||||
}
|
||||
|
||||
get_path(fd)
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "aix",
|
||||
@@ -874,7 +998,7 @@ pub(crate) fn debug_assert_fd_is_open(fd: RawFd) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Dir {
|
||||
impl Drop for DirStream {
|
||||
fn drop(&mut self) {
|
||||
// dirfd isn't supported everywhere
|
||||
#[cfg(not(any(
|
||||
@@ -902,6 +1026,11 @@ impl Drop for Dir {
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: `int dirfd (DIR *dirstream)` is MT-safe, implying that the pointer
|
||||
// may be safely sent among threads.
|
||||
unsafe impl Send for DirStream {}
|
||||
unsafe impl Sync for DirStream {}
|
||||
|
||||
impl DirEntry {
|
||||
pub fn path(&self) -> PathBuf {
|
||||
self.dir.root.join(self.file_name_os_str())
|
||||
@@ -1860,102 +1989,8 @@ impl FromRawFd for File {
|
||||
|
||||
impl fmt::Debug for File {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
#[cfg(any(target_os = "linux", target_os = "illumos", target_os = "solaris"))]
|
||||
fn get_path(fd: c_int) -> Option<PathBuf> {
|
||||
let mut p = PathBuf::from("/proc/self/fd");
|
||||
p.push(&fd.to_string());
|
||||
run_path_with_cstr(&p, &readlink).ok()
|
||||
}
|
||||
|
||||
#[cfg(any(target_vendor = "apple", target_os = "netbsd"))]
|
||||
fn get_path(fd: c_int) -> Option<PathBuf> {
|
||||
// FIXME: The use of PATH_MAX is generally not encouraged, but it
|
||||
// is inevitable in this case because Apple targets and NetBSD define `fcntl`
|
||||
// with `F_GETPATH` in terms of `MAXPATHLEN`, and there are no
|
||||
// alternatives. If a better method is invented, it should be used
|
||||
// instead.
|
||||
let mut buf = vec![0; libc::PATH_MAX as usize];
|
||||
let n = unsafe { libc::fcntl(fd, libc::F_GETPATH, buf.as_ptr()) };
|
||||
if n == -1 {
|
||||
cfg_select! {
|
||||
target_os = "netbsd" => {
|
||||
// fallback to procfs as last resort
|
||||
let mut p = PathBuf::from("/proc/self/fd");
|
||||
p.push(&fd.to_string());
|
||||
return run_path_with_cstr(&p, &readlink).ok()
|
||||
}
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
let l = buf.iter().position(|&c| c == 0).unwrap();
|
||||
buf.truncate(l as usize);
|
||||
buf.shrink_to_fit();
|
||||
Some(PathBuf::from(OsString::from_vec(buf)))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
fn get_path(fd: c_int) -> Option<PathBuf> {
|
||||
let info = Box::<libc::kinfo_file>::new_zeroed();
|
||||
let mut info = unsafe { info.assume_init() };
|
||||
info.kf_structsize = size_of::<libc::kinfo_file>() as libc::c_int;
|
||||
let n = unsafe { libc::fcntl(fd, libc::F_KINFO, &mut *info) };
|
||||
if n == -1 {
|
||||
return None;
|
||||
}
|
||||
let buf = unsafe { CStr::from_ptr(info.kf_path.as_mut_ptr()).to_bytes().to_vec() };
|
||||
Some(PathBuf::from(OsString::from_vec(buf)))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "vxworks")]
|
||||
fn get_path(fd: c_int) -> Option<PathBuf> {
|
||||
let mut buf = vec![0; libc::PATH_MAX as usize];
|
||||
let n = unsafe { libc::ioctl(fd, libc::FIOGETNAME, buf.as_ptr()) };
|
||||
if n == -1 {
|
||||
return None;
|
||||
}
|
||||
let l = buf.iter().position(|&c| c == 0).unwrap();
|
||||
buf.truncate(l as usize);
|
||||
Some(PathBuf::from(OsString::from_vec(buf)))
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "linux",
|
||||
target_os = "vxworks",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "illumos",
|
||||
target_os = "solaris",
|
||||
target_vendor = "apple",
|
||||
)))]
|
||||
fn get_path(_fd: c_int) -> Option<PathBuf> {
|
||||
// FIXME(#24570): implement this for other Unix platforms
|
||||
None
|
||||
}
|
||||
|
||||
fn get_mode(fd: c_int) -> Option<(bool, bool)> {
|
||||
let mode = unsafe { libc::fcntl(fd, libc::F_GETFL) };
|
||||
if mode == -1 {
|
||||
return None;
|
||||
}
|
||||
match mode & libc::O_ACCMODE {
|
||||
libc::O_RDONLY => Some((true, false)),
|
||||
libc::O_RDWR => Some((true, true)),
|
||||
libc::O_WRONLY => Some((false, true)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
let fd = self.as_raw_fd();
|
||||
let mut b = f.debug_struct("File");
|
||||
b.field("fd", &fd);
|
||||
if let Some(path) = get_path(fd) {
|
||||
b.field("path", &path);
|
||||
}
|
||||
if let Some((read, write)) = get_mode(fd) {
|
||||
b.field("read", &read).field("write", &write);
|
||||
}
|
||||
let mut b = debug_path_fd(fd, f, "File");
|
||||
b.finish()
|
||||
}
|
||||
}
|
||||
@@ -2033,7 +2068,7 @@ pub fn readdir(path: &Path) -> io::Result<ReadDir> {
|
||||
Err(Error::last_os_error())
|
||||
} else {
|
||||
let root = path.to_path_buf();
|
||||
let inner = InnerReadDir { dirp: Dir(ptr), root };
|
||||
let inner = InnerReadDir { dirp: DirStream(ptr), root };
|
||||
Ok(ReadDir::new(inner))
|
||||
}
|
||||
}
|
||||
@@ -2493,7 +2528,8 @@ mod remove_dir_impl {
|
||||
use libc::{fdopendir, openat64 as openat, unlinkat};
|
||||
|
||||
use super::{
|
||||
AsRawFd, Dir, DirEntry, FromRawFd, InnerReadDir, IntoRawFd, OwnedFd, RawFd, ReadDir, lstat,
|
||||
AsRawFd, DirEntry, DirStream, FromRawFd, InnerReadDir, IntoRawFd, OwnedFd, RawFd, ReadDir,
|
||||
lstat,
|
||||
};
|
||||
use crate::ffi::CStr;
|
||||
use crate::io;
|
||||
@@ -2517,7 +2553,7 @@ mod remove_dir_impl {
|
||||
if ptr.is_null() {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
let dirp = Dir(ptr);
|
||||
let dirp = DirStream(ptr);
|
||||
// file descriptor is automatically closed by libc::closedir() now, so give up ownership
|
||||
let new_parent_fd = dir_fd.into_raw_fd();
|
||||
// a valid root is not needed because we do not call any functions involving the full path
|
||||
|
||||
114
library/std/src/sys/fs/unix/dir.rs
Normal file
114
library/std/src/sys/fs/unix/dir.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use libc::c_int;
|
||||
|
||||
cfg_select! {
|
||||
not(
|
||||
any(
|
||||
all(target_os = "linux", not(target_env = "musl")),
|
||||
target_os = "l4re",
|
||||
target_os = "android",
|
||||
target_os = "hurd",
|
||||
)
|
||||
) => {
|
||||
use libc::{open as open64, openat as openat64};
|
||||
}
|
||||
_ => {
|
||||
use libc::{open64, openat64};
|
||||
}
|
||||
}
|
||||
|
||||
use crate::ffi::CStr;
|
||||
use crate::os::fd::{AsFd, BorrowedFd, IntoRawFd, OwnedFd, RawFd};
|
||||
#[cfg(target_family = "unix")]
|
||||
use crate::os::unix::io::{AsRawFd, FromRawFd};
|
||||
#[cfg(target_os = "wasi")]
|
||||
use crate::os::wasi::io::{AsRawFd, FromRawFd};
|
||||
use crate::path::Path;
|
||||
use crate::sys::fd::FileDesc;
|
||||
use crate::sys::fs::OpenOptions;
|
||||
use crate::sys::fs::unix::{File, debug_path_fd};
|
||||
use crate::sys::helpers::run_path_with_cstr;
|
||||
use crate::sys::{AsInner, FromInner, IntoInner, cvt_r};
|
||||
use crate::{fmt, fs, io};
|
||||
|
||||
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_file(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> {
|
||||
run_path_with_cstr(path.as_ref(), &|path| self.open_file_c(path, &opts))
|
||||
}
|
||||
|
||||
pub fn open_with_c(path: &CStr, opts: &OpenOptions) -> io::Result<Self> {
|
||||
let flags = libc::O_CLOEXEC
|
||||
| libc::O_DIRECTORY
|
||||
| opts.get_access_mode()?
|
||||
| opts.get_creation_mode()?
|
||||
| (opts.custom_flags as c_int & !libc::O_ACCMODE);
|
||||
let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode as c_int) })?;
|
||||
Ok(Self(unsafe { OwnedFd::from_raw_fd(fd) }))
|
||||
}
|
||||
|
||||
fn open_file_c(&self, path: &CStr, opts: &OpenOptions) -> io::Result<File> {
|
||||
let flags = libc::O_CLOEXEC
|
||||
| opts.get_access_mode()?
|
||||
| opts.get_creation_mode()?
|
||||
| (opts.custom_flags as c_int & !libc::O_ACCMODE);
|
||||
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) }))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Dir {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let fd = self.0.as_raw_fd();
|
||||
let mut b = debug_path_fd(fd, f, "Dir");
|
||||
b.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "dirfd", issue = "120426")]
|
||||
impl AsRawFd for fs::Dir {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.as_inner().0.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "dirfd", issue = "120426")]
|
||||
impl IntoRawFd for fs::Dir {
|
||||
fn into_raw_fd(self) -> RawFd {
|
||||
self.into_inner().0.into_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "dirfd", issue = "120426")]
|
||||
impl FromRawFd for fs::Dir {
|
||||
unsafe fn from_raw_fd(fd: RawFd) -> Self {
|
||||
Self::from_inner(Dir(unsafe { FromRawFd::from_raw_fd(fd) }))
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "dirfd", issue = "120426")]
|
||||
impl AsFd for fs::Dir {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
self.as_inner().0.as_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "dirfd", issue = "120426")]
|
||||
impl From<fs::Dir> for OwnedFd {
|
||||
fn from(value: fs::Dir) -> Self {
|
||||
value.into_inner().0
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "dirfd", issue = "120426")]
|
||||
impl From<OwnedFd> for fs::Dir {
|
||||
fn from(value: OwnedFd) -> Self {
|
||||
Self::from_inner(Dir(value))
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ use crate::fs::TryLockError;
|
||||
use crate::hash::{Hash, Hasher};
|
||||
use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom};
|
||||
use crate::path::{Path, PathBuf};
|
||||
pub use crate::sys::fs::common::Dir;
|
||||
use crate::sys::time::SystemTime;
|
||||
use crate::sys::unsupported;
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ use crate::sys::time::SystemTime;
|
||||
use crate::sys::{Align8, AsInner, FromInner, IntoInner, c, cvt};
|
||||
use crate::{fmt, ptr, slice};
|
||||
|
||||
mod dir;
|
||||
pub use dir::Dir;
|
||||
mod remove_dir_all;
|
||||
use remove_dir_all::remove_dir_all_iterative;
|
||||
|
||||
@@ -274,7 +276,7 @@ impl OpenOptions {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_creation_mode(&self) -> io::Result<u32> {
|
||||
fn get_cmode_disposition(&self) -> io::Result<(u32, u32)> {
|
||||
match (self.write, self.append) {
|
||||
(true, false) => {}
|
||||
(false, false) => {
|
||||
@@ -296,16 +298,24 @@ impl OpenOptions {
|
||||
}
|
||||
|
||||
Ok(match (self.create, self.truncate, self.create_new) {
|
||||
(false, false, false) => c::OPEN_EXISTING,
|
||||
(true, false, false) => c::OPEN_ALWAYS,
|
||||
(false, true, false) => c::TRUNCATE_EXISTING,
|
||||
(false, false, false) => (c::OPEN_EXISTING, c::FILE_OPEN),
|
||||
(true, false, false) => (c::OPEN_ALWAYS, c::FILE_OPEN_IF),
|
||||
(false, true, false) => (c::TRUNCATE_EXISTING, c::FILE_OVERWRITE),
|
||||
// `CREATE_ALWAYS` has weird semantics so we emulate it using
|
||||
// `OPEN_ALWAYS` and a manual truncation step. See #115745.
|
||||
(true, true, false) => c::OPEN_ALWAYS,
|
||||
(_, _, true) => c::CREATE_NEW,
|
||||
(true, true, false) => (c::OPEN_ALWAYS, c::FILE_OVERWRITE_IF),
|
||||
(_, _, true) => (c::CREATE_NEW, c::FILE_CREATE),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_creation_mode(&self) -> io::Result<u32> {
|
||||
self.get_cmode_disposition().map(|(mode, _)| mode)
|
||||
}
|
||||
|
||||
fn get_disposition(&self) -> io::Result<u32> {
|
||||
self.get_cmode_disposition().map(|(_, mode)| mode)
|
||||
}
|
||||
|
||||
fn get_flags_and_attributes(&self) -> u32 {
|
||||
self.custom_flags
|
||||
| self.attributes
|
||||
@@ -1019,14 +1029,23 @@ impl FromRawHandle for File {
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_path_handle<'a, 'b>(
|
||||
handle: BorrowedHandle<'a>,
|
||||
f: &'a mut fmt::Formatter<'b>,
|
||||
name: &str,
|
||||
) -> fmt::DebugStruct<'a, 'b> {
|
||||
// FIXME(#24570): add more info here (e.g., mode)
|
||||
let mut b = f.debug_struct(name);
|
||||
b.field("handle", &handle.as_raw_handle());
|
||||
if let Ok(path) = get_path(handle) {
|
||||
b.field("path", &path);
|
||||
}
|
||||
b
|
||||
}
|
||||
|
||||
impl fmt::Debug for File {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// FIXME(#24570): add more info here (e.g., mode)
|
||||
let mut b = f.debug_struct("File");
|
||||
b.field("handle", &self.handle.as_raw_handle());
|
||||
if let Ok(path) = get_path(self) {
|
||||
b.field("path", &path);
|
||||
}
|
||||
let mut b = debug_path_handle(self.handle.as_handle(), f, "File");
|
||||
b.finish()
|
||||
}
|
||||
}
|
||||
@@ -1292,8 +1311,8 @@ pub fn rename(old: &WCStr, new: &WCStr) -> io::Result<()> {
|
||||
Layout::from_size_align(struct_size as usize, align_of::<c::FILE_RENAME_INFO>())
|
||||
.unwrap();
|
||||
|
||||
// SAFETY: We allocate enough memory for a full FILE_RENAME_INFO struct and a filename.
|
||||
let file_rename_info;
|
||||
// SAFETY: We allocate enough memory for a full FILE_RENAME_INFO struct and a filename.
|
||||
unsafe {
|
||||
file_rename_info = alloc(layout).cast::<c::FILE_RENAME_INFO>();
|
||||
if file_rename_info.is_null() {
|
||||
@@ -1530,10 +1549,10 @@ pub fn set_times_nofollow(p: &WCStr, times: FileTimes) -> io::Result<()> {
|
||||
file.set_times(times)
|
||||
}
|
||||
|
||||
fn get_path(f: &File) -> io::Result<PathBuf> {
|
||||
fn get_path(f: impl AsRawHandle) -> io::Result<PathBuf> {
|
||||
fill_utf16_buf(
|
||||
|buf, sz| unsafe {
|
||||
c::GetFinalPathNameByHandleW(f.handle.as_raw_handle(), buf, sz, c::VOLUME_NAME_DOS)
|
||||
c::GetFinalPathNameByHandleW(f.as_raw_handle(), buf, sz, c::VOLUME_NAME_DOS)
|
||||
},
|
||||
|buf| PathBuf::from(OsString::from_wide(buf)),
|
||||
)
|
||||
@@ -1546,7 +1565,7 @@ pub fn canonicalize(p: &WCStr) -> io::Result<PathBuf> {
|
||||
// This flag is so we can open directories too
|
||||
opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS);
|
||||
let f = File::open_native(p, &opts)?;
|
||||
get_path(&f)
|
||||
get_path(f.handle)
|
||||
}
|
||||
|
||||
pub fn copy(from: &WCStr, to: &WCStr) -> io::Result<u64> {
|
||||
|
||||
153
library/std/src/sys/fs/windows/dir.rs
Normal file
153
library/std/src/sys/fs/windows/dir.rs
Normal file
@@ -0,0 +1,153 @@
|
||||
use crate::os::windows::io::{
|
||||
AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, HandleOrInvalid, IntoRawHandle,
|
||||
OwnedHandle, RawHandle,
|
||||
};
|
||||
use crate::path::Path;
|
||||
use crate::sys::api::{UnicodeStrRef, WinError};
|
||||
use crate::sys::fs::windows::debug_path_handle;
|
||||
use crate::sys::fs::{File, OpenOptions};
|
||||
use crate::sys::handle::Handle;
|
||||
use crate::sys::path::{WCStr, with_native_path};
|
||||
use crate::sys::{AsInner, FromInner, IntoInner, IoResult, c, to_u16s};
|
||||
use crate::{fmt, fs, io, ptr};
|
||||
|
||||
pub struct Dir {
|
||||
handle: Handle,
|
||||
}
|
||||
|
||||
/// A wrapper around a raw NtCreateFile call.
|
||||
///
|
||||
/// This isn't completely safe because `OBJECT_ATTRIBUTES` contains raw pointers.
|
||||
unsafe fn nt_create_file(
|
||||
opts: &OpenOptions,
|
||||
object_attributes: &c::OBJECT_ATTRIBUTES,
|
||||
create_options: c::NTCREATEFILE_CREATE_OPTIONS,
|
||||
) -> io::Result<Handle> {
|
||||
let mut handle = ptr::null_mut();
|
||||
let mut io_status = c::IO_STATUS_BLOCK::PENDING;
|
||||
// SYNCHRONIZE is included in FILE_GENERIC_READ, but not GENERIC_READ, so we add it manually
|
||||
let access = opts.get_access_mode()? | c::SYNCHRONIZE;
|
||||
// one of FILE_SYNCHRONOUS_IO_{,NON}ALERT is required for later operations to succeed.
|
||||
let options = create_options | c::FILE_SYNCHRONOUS_IO_NONALERT;
|
||||
let status = unsafe {
|
||||
c::NtCreateFile(
|
||||
&mut handle,
|
||||
access,
|
||||
object_attributes,
|
||||
&mut io_status,
|
||||
ptr::null(),
|
||||
c::FILE_ATTRIBUTE_NORMAL,
|
||||
opts.share_mode,
|
||||
opts.get_disposition()?,
|
||||
options,
|
||||
ptr::null(),
|
||||
0,
|
||||
)
|
||||
};
|
||||
if c::nt_success(status) {
|
||||
// SAFETY: nt_success guarantees that handle is no longer null
|
||||
unsafe { Ok(Handle::from_raw_handle(handle)) }
|
||||
} else {
|
||||
Err(WinError::new(unsafe { c::RtlNtStatusToDosError(status) })).io_result()
|
||||
}
|
||||
}
|
||||
|
||||
impl Dir {
|
||||
pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<Self> {
|
||||
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() {
|
||||
return File::open(path, opts);
|
||||
}
|
||||
let path = to_u16s(path)?;
|
||||
let path = &path[..path.len() - 1]; // trim 0 byte
|
||||
self.open_file_native(&path, opts).map(|handle| File { handle })
|
||||
}
|
||||
|
||||
fn open_with_native(path: &WCStr, opts: &OpenOptions) -> io::Result<Self> {
|
||||
let creation = opts.get_creation_mode()?;
|
||||
let sa = c::SECURITY_ATTRIBUTES {
|
||||
nLength: size_of::<c::SECURITY_ATTRIBUTES>() as u32,
|
||||
lpSecurityDescriptor: ptr::null_mut(),
|
||||
bInheritHandle: opts.inherit_handle as c::BOOL,
|
||||
};
|
||||
let handle = unsafe {
|
||||
c::CreateFileW(
|
||||
path.as_ptr(),
|
||||
opts.get_access_mode()?,
|
||||
opts.share_mode,
|
||||
&raw const sa,
|
||||
creation,
|
||||
// FILE_FLAG_BACKUP_SEMANTICS is required to open a directory
|
||||
opts.get_flags_and_attributes() | c::FILE_FLAG_BACKUP_SEMANTICS,
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
match OwnedHandle::try_from(unsafe { HandleOrInvalid::from_raw_handle(handle) }) {
|
||||
Ok(handle) => Ok(Self { handle: Handle::from_inner(handle) }),
|
||||
Err(_) => Err(io::Error::last_os_error()),
|
||||
}
|
||||
}
|
||||
|
||||
fn open_file_native(&self, path: &[u16], opts: &OpenOptions) -> io::Result<Handle> {
|
||||
let name = UnicodeStrRef::from_slice(path);
|
||||
let object_attributes = c::OBJECT_ATTRIBUTES {
|
||||
RootDirectory: self.handle.as_raw_handle(),
|
||||
ObjectName: name.as_ptr(),
|
||||
..c::OBJECT_ATTRIBUTES::with_length()
|
||||
};
|
||||
unsafe { nt_create_file(opts, &object_attributes, c::FILE_NON_DIRECTORY_FILE) }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Dir {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut b = debug_path_handle(self.handle.as_handle(), f, "Dir");
|
||||
b.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "dirfd", issue = "120426")]
|
||||
impl AsRawHandle for fs::Dir {
|
||||
fn as_raw_handle(&self) -> RawHandle {
|
||||
self.as_inner().handle.as_raw_handle()
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "dirhandle", issue = "120426")]
|
||||
impl IntoRawHandle for fs::Dir {
|
||||
fn into_raw_handle(self) -> RawHandle {
|
||||
self.into_inner().handle.into_raw_handle()
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "dirhandle", issue = "120426")]
|
||||
impl FromRawHandle for fs::Dir {
|
||||
unsafe fn from_raw_handle(handle: RawHandle) -> Self {
|
||||
Self::from_inner(Dir { handle: unsafe { FromRawHandle::from_raw_handle(handle) } })
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "dirhandle", issue = "120426")]
|
||||
impl AsHandle for fs::Dir {
|
||||
fn as_handle(&self) -> BorrowedHandle<'_> {
|
||||
self.as_inner().handle.as_handle()
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "dirhandle", issue = "120426")]
|
||||
impl From<fs::Dir> for OwnedHandle {
|
||||
fn from(value: fs::Dir) -> Self {
|
||||
value.into_inner().handle.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "dirhandle", issue = "120426")]
|
||||
impl From<OwnedHandle> for fs::Dir {
|
||||
fn from(value: OwnedHandle) -> Self {
|
||||
Self::from_inner(Dir { handle: Handle::from_inner(value) })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user