Skip to content
Merged
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
56 changes: 53 additions & 3 deletions library/std/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1637,6 +1637,8 @@ impl Dir {

/// Queries metadata about the underlying directory.
///
/// This is equivalent to `dir.metadata(".")` but can be more efficient.
///
/// # Examples
///
/// ```no_run
Expand All @@ -1645,13 +1647,13 @@ impl Dir {
///
/// fn main() -> std::io::Result<()> {
/// let dir = Dir::open("foo")?;
/// let metadata = dir.metadata()?;
/// let metadata = dir.self_metadata()?;
/// Ok(())
/// }
/// ```
#[unstable(feature = "dirfd", issue = "120426")]
pub fn metadata(&self) -> io::Result<Metadata> {
self.inner.metadata().map(Metadata)
pub fn self_metadata(&self) -> io::Result<Metadata> {
self.inner.self_metadata().map(Metadata)
}

/// Attempts to open a file in read-only mode relative to this directory.
Expand Down Expand Up @@ -1891,6 +1893,54 @@ impl Dir {
pub fn try_clone(&self) -> io::Result<Self> {
Ok(Dir { inner: self.inner.duplicate()? })
}

/// Queries the file system to get information about a file, directory, etc. relative to this
/// directory.
///
/// This function will traverse symbolic links to query information about the destination file.
/// To query metadata about the path itself without following symbolic links, use
/// [`symlink_metadata`][Self::symlink_metadata].
///
/// # Examples
///
/// ```no_run
/// #![feature(dirfd)]
/// use std::fs::Dir;
///
/// fn main() -> std::io::Result<()> {
/// let dir = Dir::open("foo")?;
/// let metadata = dir.metadata("subdir/file.txt")?;
/// Ok(())
/// }
/// ```
#[unstable(feature = "dirfd", issue = "120426")]
pub fn metadata<P: AsRef<Path>>(&self, path: P) -> io::Result<Metadata> {
self.inner.metadata(path.as_ref()).map(Metadata)
}

/// Queries the file system to get information about a file, directory, etc. relative to this
/// directory.
///
/// This function will return the [`Metadata`] of the exact path without traversing symbolic
/// links to a resolved destination file. Using this function on a path that is a file or
/// directory (not a symbolic link) will behave the same as [`metadata`][Self::metadata].
///
/// # Examples
///
/// ```no_run
/// #![feature(dirfd)]
/// use std::fs::Dir;
///
/// fn main() -> std::io::Result<()> {
/// let dir = Dir::open("foo")?;
/// let metadata = dir.symlink_metadata("subdir/file.txt")?;
/// Ok(())
/// }
/// ```
#[unstable(feature = "dirfd", issue = "120426")]
pub fn symlink_metadata<P: AsRef<Path>>(&self, path: P) -> io::Result<Metadata> {
self.inner.symlink_metadata(path.as_ref()).map(Metadata)
}
}

impl AsInner<fs_imp::Dir> for Dir {
Expand Down
35 changes: 27 additions & 8 deletions library/std/src/fs/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::os::unix::fs::symlink as symlink_file;
use crate::os::unix::fs::symlink as junction_point;
#[cfg(windows)]
use crate::os::windows::fs::{OpenOptionsExt, junction_point, symlink_dir, symlink_file};
use crate::path::Path;
use crate::path::{Path, PathBuf};
use crate::sync::Arc;
use crate::test_helpers::{TempDir, tmpdir};
use crate::time::{Duration, Instant, SystemTime};
Expand Down Expand Up @@ -655,11 +655,6 @@ fn set_get_permissions_nofollows() {
not(any(target_os = "espidf", target_os = "horizon", target_os = "wasi"))
))]
fn set_get_permissions_nofollows_symlink() {
#[cfg(not(windows))]
use crate::os::unix::fs::symlink as symlink_file;
#[cfg(windows)]
use crate::os::windows::fs::symlink_file;

let tmpdir = tmpdir();
let filename = tmpdir.join("set_get_unix_permissions_file");
let symlink_name = tmpdir.join("set_get_unix_permissions");
Expand Down Expand Up @@ -3015,10 +3010,10 @@ fn test_dir_clone() {
}

#[test]
fn test_dir_metadata() {
fn test_dir_self_metadata() {
let tmpdir = tmpdir();
let dir = check!(Dir::open(tmpdir.path()));
let metadata = check!(dir.metadata());
let metadata = check!(dir.self_metadata());
assert!(metadata.is_dir());
}

Expand Down Expand Up @@ -3106,3 +3101,27 @@ fn test_dir_open_dir() {
check!(f.read_exact(&mut buf));
assert_eq!(b"baz", &buf);
}

#[test]
fn test_dir_metadata() {
let tmpdir = tmpdir();
let dir = check!(Dir::open(tmpdir.path()));
check!(dir.create_dir("subdir"));
// FIXME: `/` does not work as path separator on Windows.
let barpath = PathBuf::from("subdir").join("bar.txt");
drop(check!(dir.open_file_with(&barpath, &OpenOptions::new().create(true).write(true))));
check!(symlink_file(&tmpdir.join("subdir/bar.txt"), &tmpdir.join("link")));

let metadata = check!(dir.metadata(&barpath));
assert!(metadata.is_file());
let metadata = check!(dir.metadata("subdir"));
assert!(metadata.is_dir());
dir.metadata("does-not-exist").unwrap_err();

let metadata = check!(dir.metadata("link"));
assert!(metadata.is_file());
assert!(!metadata.is_symlink());
let metadata = check!(dir.symlink_metadata("link"));
assert!(!metadata.is_file());
assert!(metadata.is_symlink());
}
10 changes: 9 additions & 1 deletion library/std/src/sys/fs/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ impl Dir {
File::open(&self.path.join(path), opts)
}

pub fn metadata(&self) -> io::Result<FileAttr> {
pub fn self_metadata(&self) -> io::Result<FileAttr> {
self.path.metadata().map(|m| m.into_inner())
}

Expand All @@ -108,6 +108,14 @@ impl Dir {
pub fn remove_dir(&self, path: &Path) -> io::Result<()> {
remove_dir(self.path.join(path))
}

pub fn metadata(&self, path: &Path) -> io::Result<FileAttr> {
self.path.join(path).metadata().map(|m| m.into_inner())
}

pub fn symlink_metadata(&self, path: &Path) -> io::Result<FileAttr> {
self.path.join(path).symlink_metadata().map(|m| m.into_inner())
}
}

impl fmt::Debug for Dir {
Expand Down
179 changes: 79 additions & 100 deletions library/std/src/sys/fs/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,39 @@
#[cfg(test)]
mod tests;

// Import the file operations under consistent names.
cfg_select! {
not(any(
all(target_os = "linux", not(target_env = "musl")),
target_os = "android",
target_os = "hurd",
target_os = "l4re",
)) => {
use libc::{
dirent as dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64,
lstat as lstat64, off_t as off64_t, open as open64, stat as stat64,
};
}
target_os = "android" => {
// Android's `stat`, `dirent`, and related functions are always 64-bit LFS compatible, and
// `open` already implies `O_LARGEFILE`, so all those don't need to follow Linux.
// However, we still need off64_t, ftruncate64, and lseek64.
use libc::{
dirent as dirent64, fstat as fstat64, ftruncate64, lseek64, lstat as lstat64, off64_t,
open as open64, stat as stat64,
};
}
target_os = "l4re" => {
use libc::{
dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64,
lstat as lstat64, off_t as off64_t, open as open64, stat as stat64,
};
}
_ => {
use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64};
}
}

#[cfg(all(target_os = "linux", target_env = "gnu"))]
use libc::c_char;
#[cfg(any(
Expand All @@ -17,33 +50,7 @@ use libc::c_char;
target_vendor = "apple",
))]
use libc::dirfd;
#[cfg(any(target_os = "fuchsia", target_os = "illumos", target_vendor = "apple"))]
use libc::fstatat as fstatat64;
#[cfg(any(all(target_os = "linux", not(target_env = "musl")), target_os = "hurd"))]
use libc::fstatat64;
use libc::{c_int, mode_t};
#[cfg(target_os = "android")]
use libc::{
dirent as dirent64, fstat as fstat64, fstatat as fstatat64, ftruncate64, lseek64,
lstat as lstat64, off64_t, open as open64, stat as stat64,
};
#[cfg(not(any(
all(target_os = "linux", not(target_env = "musl")),
target_os = "android",
target_os = "hurd",
target_os = "l4re",
)))]
use libc::{
dirent as dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64,
lstat as lstat64, off_t as off64_t, open as open64, stat as stat64,
};
#[cfg(target_os = "l4re")]
use libc::{
dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64, lstat as lstat64,
off_t as off64_t, open as open64, stat as stat64,
};
#[cfg(any(all(target_os = "linux", not(target_env = "musl")), target_os = "hurd"))]
use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64};

use crate::ffi::{CStr, OsStr, OsString};
use crate::fmt::{self, Write as _};
Expand Down Expand Up @@ -1016,89 +1023,61 @@ impl DirEntry {
self.file_name_os_str().to_os_string()
}

#[cfg(all(
any(
all(target_os = "linux", not(target_env = "musl")),
target_os = "android",
target_os = "fuchsia",
target_os = "hurd",
target_os = "illumos",
target_vendor = "apple",
),
not(miri) // no dirfd on Miri
))]
pub fn metadata(&self) -> io::Result<FileAttr> {
let fd = cvt(unsafe { dirfd(self.dir.dirp.0) })?;
let name = self.name.as_ptr();

cfg_has_statx! {
if let Some(ret) = unsafe { try_statx(
fd,
name,
libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
libc::STATX_BASIC_STATS | libc::STATX_BTIME,
) } {
return ret;
}
}
cfg_select! {
// Use directory handle where possible
all(
any(
all(target_os = "linux", not(target_env = "musl")),
target_os = "android",
target_os = "fuchsia",
target_os = "hurd",
target_os = "illumos",
target_vendor = "apple",
),
not(miri) // no dirfd on Miri
) => {
let fd = cvt(unsafe { dirfd(self.dir.dirp.0) })?;

let mut stat: stat64 = unsafe { mem::zeroed() };
cvt(unsafe { fstatat64(fd, name, &mut stat, libc::AT_SYMLINK_NOFOLLOW) })?;
Ok(FileAttr::from_stat64(stat))
}
// Make this FD into a directory handle. We don't actually drop it,
// so having an `OwnedFd` is fine.
let dir_handle =
mem::ManuallyDrop::new(dir::Dir(unsafe { OwnedFd::from_raw_fd(fd) }));

#[cfg(any(
not(any(
all(target_os = "linux", not(target_env = "musl")),
target_os = "android",
target_os = "fuchsia",
target_os = "hurd",
target_os = "illumos",
target_vendor = "apple",
)),
miri // no dirfd on Miri
))]
pub fn metadata(&self) -> io::Result<FileAttr> {
run_path_with_cstr(&self.path(), &lstat)
}
dir_handle.metadata_c(&self.name, /* symlink_nofollow */ true)
}

#[cfg(any(
target_os = "solaris",
target_os = "illumos",
target_os = "haiku",
target_os = "vxworks",
target_os = "aix",
target_os = "nto",
target_os = "qnx",
target_os = "vita",
target_os = "l4re",
))]
pub fn file_type(&self) -> io::Result<FileType> {
self.metadata().map(|m| m.file_type())
// Fallback based on path
_ => run_path_with_cstr(&self.path(), &lstat),
}
}

#[cfg(not(any(
target_os = "solaris",
target_os = "illumos",
target_os = "haiku",
target_os = "vxworks",
target_os = "aix",
target_os = "nto",
target_os = "qnx",
target_os = "vita",
target_os = "l4re",
)))]
pub fn file_type(&self) -> io::Result<FileType> {
// Use `entry.d_type` if available.
#[cfg(not(any(
target_os = "solaris",
target_os = "illumos",
target_os = "haiku",
target_os = "vxworks",
target_os = "aix",
target_os = "nto",
target_os = "qnx",
target_os = "vita",
target_os = "l4re",
)))]
match self.entry.d_type {
libc::DT_CHR => Ok(FileType { mode: libc::S_IFCHR }),
libc::DT_FIFO => Ok(FileType { mode: libc::S_IFIFO }),
libc::DT_LNK => Ok(FileType { mode: libc::S_IFLNK }),
libc::DT_REG => Ok(FileType { mode: libc::S_IFREG }),
libc::DT_SOCK => Ok(FileType { mode: libc::S_IFSOCK }),
libc::DT_DIR => Ok(FileType { mode: libc::S_IFDIR }),
libc::DT_BLK => Ok(FileType { mode: libc::S_IFBLK }),
_ => self.metadata().map(|m| m.file_type()),
libc::DT_CHR => return Ok(FileType { mode: libc::S_IFCHR }),
libc::DT_FIFO => return Ok(FileType { mode: libc::S_IFIFO }),
libc::DT_LNK => return Ok(FileType { mode: libc::S_IFLNK }),
libc::DT_REG => return Ok(FileType { mode: libc::S_IFREG }),
libc::DT_SOCK => return Ok(FileType { mode: libc::S_IFSOCK }),
libc::DT_DIR => return Ok(FileType { mode: libc::S_IFDIR }),
libc::DT_BLK => return Ok(FileType { mode: libc::S_IFBLK }),
_ => {}
}

// Fall back to loading the metadata.
self.metadata().map(|m| m.file_type())
}

pub fn ino(&self) -> u64 {
Expand Down
Loading
Loading