This commit is contained in:
Jeremy Smart 2025-04-13 13:28:56 +02:00 committed by GitHub
commit fce9941cc5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 180 additions and 78 deletions

View File

@ -116,6 +116,13 @@ pub struct File {
inner: fs_imp::File,
}
#[unstable(feature = "dirfd", issue = "120426")]
#[cfg(target_family = "unix")]
/// An object providing access to a directory on the filesystem.
pub struct Dir {
inner: fs_imp::Dir,
}
/// Metadata information about a file.
///
/// This structure is returned from the [`metadata`] or
@ -1353,6 +1360,25 @@ impl Seek for Arc<File> {
}
}
#[unstable(feature = "dirfd", issue = "120426")]
impl Dir {
/// Opens a file relative to this directory.
pub fn open<P: AsRef<Path>>(&self, path: P) -> io::Result<File> {
self.inner.open(path).map(|f| File { inner: f })
}
/// Opens a file relative to this directory with the specified options.
pub fn open_with<P: AsRef<Path>>(&self, path: P, opts: &OpenOptions) -> io::Result<File> {
self.inner.open_with(path, &opts.0).map(|f| File { inner: f })
}
}
#[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.
///

View File

@ -45,6 +45,8 @@ pub fn with_native_path<T>(path: &Path, f: &dyn Fn(&Path) -> io::Result<T>) -> i
f(path)
}
#[cfg(target_family = "unix")]
pub use imp::Dir;
pub use imp::{
DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions,
ReadDir,

View File

@ -53,7 +53,7 @@ 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,
lstat as lstat64, off64_t, open as open64, openat as openat64, stat as stat64,
};
#[cfg(not(any(
all(target_os = "linux", not(target_env = "musl")),
@ -63,14 +63,14 @@ use libc::{
)))]
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,
lstat as lstat64, off_t as off64_t, open as open64, openat as openat64, stat as stat64,
};
#[cfg(any(
all(target_os = "linux", not(target_env = "musl")),
target_os = "l4re",
target_os = "hurd"
))]
use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64};
use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, openat64, stat64};
use crate::ffi::{CStr, OsStr, OsString};
use crate::fmt::{self, Write as _};
@ -262,7 +262,154 @@ impl ReadDir {
}
}
struct Dir(*mut libc::DIR);
pub struct Dir(*mut libc::DIR);
// dirfd isn't supported everywhere
#[cfg(not(any(
miri,
target_os = "redox",
target_os = "nto",
target_os = "vita",
target_os = "hurd",
target_os = "espidf",
target_os = "horizon",
target_os = "vxworks",
target_os = "rtems",
target_os = "nuttx",
)))]
impl Dir {
pub fn open<P: AsRef<Path>>(&self, path: P) -> io::Result<File> {
let mut opts = OpenOptions::new();
opts.read(true);
run_path_with_cstr(path.as_ref(), &|path| self.open_c(path, &opts))
}
pub fn open_with<P: AsRef<Path>>(&self, path: P, opts: &OpenOptions) -> io::Result<File> {
run_path_with_cstr(path.as_ref(), &|path| self.open_c(path, opts))
}
pub fn open_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(libc::dirfd(self.0), path.as_ptr(), flags, opts.mode as c_int)
})?;
Ok(File(unsafe { FileDesc::from_raw_fd(fd) }))
}
// pub fn create_dir<P: AsRef<Path>>(&self, path: P) -> Result<()>
// pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(&self, from: P, to_dir: &Self, to: Q) -> Result<()>
// pub fn remove_file<P: AsRef<Path>>(&self, path: P) -> Result<()>
// pub fn remove_dir<P: AsRef<Path>>(&self, path: P) -> Result<()>
// pub fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, original: P, link: Q)
}
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_if::cfg_if! {
if #[cfg(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()
} else {
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)
}
impl fmt::Debug for Dir {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
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 = unsafe { dirfd(self.0) };
let mut b = f.debug_struct("Dir");
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.finish()
}
}
unsafe impl Send for Dir {}
unsafe impl Sync for Dir {}
@ -1653,79 +1800,6 @@ 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_if::cfg_if! {
if #[cfg(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()
} else {
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 {
@ -1742,7 +1816,7 @@ impl fmt::Debug for File {
let fd = self.as_raw_fd();
let mut b = f.debug_struct("File");
b.field("fd", &fd);
if let Some(path) = get_path(fd) {
if let Some(path) = get_path_from_fd(fd) {
b.field("path", &path);
}
if let Some((read, write)) = get_mode(fd) {