Skip to content

dirfd: initial quick and dirty implementation for unix #139514

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
26 changes: 26 additions & 0 deletions library/std/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
///
Expand Down
2 changes: 2 additions & 0 deletions library/std/src/sys/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
230 changes: 152 additions & 78 deletions library/std/src/sys/fs/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")),
Expand All @@ -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 _};
Expand Down Expand Up @@ -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 {}
Expand Down Expand Up @@ -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 {
Expand All @@ -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) {
Expand Down
Loading