Skip to content

Commit 80ab2d4

Browse files
committed
dirfd: initial quick and dirty implementation for unix
1 parent d7df5bd commit 80ab2d4

File tree

3 files changed

+180
-78
lines changed

3 files changed

+180
-78
lines changed

library/std/src/fs.rs

+26
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,13 @@ pub enum TryLockError {
132132
WouldBlock,
133133
}
134134

135+
#[unstable(feature = "dirfd", issue = "120426")]
136+
#[cfg(target_family = "unix")]
137+
/// An object providing access to a directory on the filesystem.
138+
pub struct Dir {
139+
inner: fs_imp::Dir,
140+
}
141+
135142
/// Metadata information about a file.
136143
///
137144
/// This structure is returned from the [`metadata`] or
@@ -1402,6 +1409,25 @@ impl Seek for Arc<File> {
14021409
}
14031410
}
14041411

1412+
#[unstable(feature = "dirfd", issue = "120426")]
1413+
impl Dir {
1414+
/// Opens a file relative to this directory.
1415+
pub fn open<P: AsRef<Path>>(&self, path: P) -> io::Result<File> {
1416+
self.inner.open(path).map(|f| File { inner: f })
1417+
}
1418+
/// Opens a file relative to this directory with the specified options.
1419+
pub fn open_with<P: AsRef<Path>>(&self, path: P, opts: &OpenOptions) -> io::Result<File> {
1420+
self.inner.open_with(path, &opts.0).map(|f| File { inner: f })
1421+
}
1422+
}
1423+
1424+
#[unstable(feature = "dirfd", issue = "120426")]
1425+
impl fmt::Debug for Dir {
1426+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1427+
self.inner.fmt(f)
1428+
}
1429+
}
1430+
14051431
impl OpenOptions {
14061432
/// Creates a blank new set of options ready for configuration.
14071433
///

library/std/src/sys/fs/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ pub fn with_native_path<T>(path: &Path, f: &dyn Fn(&Path) -> io::Result<T>) -> i
4646
f(path)
4747
}
4848

49+
#[cfg(target_family = "unix")]
50+
pub use imp::Dir;
4951
pub use imp::{
5052
DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions,
5153
ReadDir,

library/std/src/sys/fs/unix.rs

+152-78
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ use libc::{c_int, mode_t};
5454
#[cfg(target_os = "android")]
5555
use libc::{
5656
dirent as dirent64, fstat as fstat64, fstatat as fstatat64, ftruncate64, lseek64,
57-
lstat as lstat64, off64_t, open as open64, stat as stat64,
57+
lstat as lstat64, off64_t, open as open64, openat as openat64, stat as stat64,
5858
};
5959
#[cfg(not(any(
6060
all(target_os = "linux", not(target_env = "musl")),
@@ -64,14 +64,14 @@ use libc::{
6464
)))]
6565
use libc::{
6666
dirent as dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64,
67-
lstat as lstat64, off_t as off64_t, open as open64, stat as stat64,
67+
lstat as lstat64, off_t as off64_t, open as open64, openat as openat64, stat as stat64,
6868
};
6969
#[cfg(any(
7070
all(target_os = "linux", not(target_env = "musl")),
7171
target_os = "l4re",
7272
target_os = "hurd"
7373
))]
74-
use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64};
74+
use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, openat64, stat64};
7575

7676
use crate::ffi::{CStr, OsStr, OsString};
7777
use crate::fmt::{self, Write as _};
@@ -264,7 +264,154 @@ impl ReadDir {
264264
}
265265
}
266266

267-
struct Dir(*mut libc::DIR);
267+
pub struct Dir(*mut libc::DIR);
268+
269+
// dirfd isn't supported everywhere
270+
#[cfg(not(any(
271+
miri,
272+
target_os = "redox",
273+
target_os = "nto",
274+
target_os = "vita",
275+
target_os = "hurd",
276+
target_os = "espidf",
277+
target_os = "horizon",
278+
target_os = "vxworks",
279+
target_os = "rtems",
280+
target_os = "nuttx",
281+
)))]
282+
impl Dir {
283+
pub fn open<P: AsRef<Path>>(&self, path: P) -> io::Result<File> {
284+
let mut opts = OpenOptions::new();
285+
opts.read(true);
286+
run_path_with_cstr(path.as_ref(), &|path| self.open_c(path, &opts))
287+
}
288+
289+
pub fn open_with<P: AsRef<Path>>(&self, path: P, opts: &OpenOptions) -> io::Result<File> {
290+
run_path_with_cstr(path.as_ref(), &|path| self.open_c(path, opts))
291+
}
292+
293+
pub fn open_c(&self, path: &CStr, opts: &OpenOptions) -> io::Result<File> {
294+
let flags = libc::O_CLOEXEC
295+
| opts.get_access_mode()?
296+
| opts.get_creation_mode()?
297+
| (opts.custom_flags as c_int & !libc::O_ACCMODE);
298+
let fd = cvt_r(|| unsafe {
299+
openat64(libc::dirfd(self.0), path.as_ptr(), flags, opts.mode as c_int)
300+
})?;
301+
Ok(File(unsafe { FileDesc::from_raw_fd(fd) }))
302+
}
303+
304+
// pub fn create_dir<P: AsRef<Path>>(&self, path: P) -> Result<()>
305+
// pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(&self, from: P, to_dir: &Self, to: Q) -> Result<()>
306+
// pub fn remove_file<P: AsRef<Path>>(&self, path: P) -> Result<()>
307+
// pub fn remove_dir<P: AsRef<Path>>(&self, path: P) -> Result<()>
308+
// pub fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, original: P, link: Q)
309+
}
310+
311+
fn get_path_from_fd(fd: c_int) -> Option<PathBuf> {
312+
#[cfg(any(target_os = "linux", target_os = "illumos", target_os = "solaris"))]
313+
fn get_path(fd: c_int) -> Option<PathBuf> {
314+
let mut p = PathBuf::from("/proc/self/fd");
315+
p.push(&fd.to_string());
316+
run_path_with_cstr(&p, &readlink).ok()
317+
}
318+
319+
#[cfg(any(target_vendor = "apple", target_os = "netbsd"))]
320+
fn get_path(fd: c_int) -> Option<PathBuf> {
321+
// FIXME: The use of PATH_MAX is generally not encouraged, but it
322+
// is inevitable in this case because Apple targets and NetBSD define `fcntl`
323+
// with `F_GETPATH` in terms of `MAXPATHLEN`, and there are no
324+
// alternatives. If a better method is invented, it should be used
325+
// instead.
326+
let mut buf = vec![0; libc::PATH_MAX as usize];
327+
let n = unsafe { libc::fcntl(fd, libc::F_GETPATH, buf.as_ptr()) };
328+
if n == -1 {
329+
cfg_if::cfg_if! {
330+
if #[cfg(target_os = "netbsd")] {
331+
// fallback to procfs as last resort
332+
let mut p = PathBuf::from("/proc/self/fd");
333+
p.push(&fd.to_string());
334+
return run_path_with_cstr(&p, &readlink).ok()
335+
} else {
336+
return None;
337+
}
338+
}
339+
}
340+
let l = buf.iter().position(|&c| c == 0).unwrap();
341+
buf.truncate(l as usize);
342+
buf.shrink_to_fit();
343+
Some(PathBuf::from(OsString::from_vec(buf)))
344+
}
345+
346+
#[cfg(target_os = "freebsd")]
347+
fn get_path(fd: c_int) -> Option<PathBuf> {
348+
let info = Box::<libc::kinfo_file>::new_zeroed();
349+
let mut info = unsafe { info.assume_init() };
350+
info.kf_structsize = size_of::<libc::kinfo_file>() as libc::c_int;
351+
let n = unsafe { libc::fcntl(fd, libc::F_KINFO, &mut *info) };
352+
if n == -1 {
353+
return None;
354+
}
355+
let buf = unsafe { CStr::from_ptr(info.kf_path.as_mut_ptr()).to_bytes().to_vec() };
356+
Some(PathBuf::from(OsString::from_vec(buf)))
357+
}
358+
359+
#[cfg(target_os = "vxworks")]
360+
fn get_path(fd: c_int) -> Option<PathBuf> {
361+
let mut buf = vec![0; libc::PATH_MAX as usize];
362+
let n = unsafe { libc::ioctl(fd, libc::FIOGETNAME, buf.as_ptr()) };
363+
if n == -1 {
364+
return None;
365+
}
366+
let l = buf.iter().position(|&c| c == 0).unwrap();
367+
buf.truncate(l as usize);
368+
Some(PathBuf::from(OsString::from_vec(buf)))
369+
}
370+
371+
#[cfg(not(any(
372+
target_os = "linux",
373+
target_os = "vxworks",
374+
target_os = "freebsd",
375+
target_os = "netbsd",
376+
target_os = "illumos",
377+
target_os = "solaris",
378+
target_vendor = "apple",
379+
)))]
380+
fn get_path(_fd: c_int) -> Option<PathBuf> {
381+
// FIXME(#24570): implement this for other Unix platforms
382+
None
383+
}
384+
385+
get_path(fd)
386+
}
387+
388+
impl fmt::Debug for Dir {
389+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390+
fn get_mode(fd: c_int) -> Option<(bool, bool)> {
391+
let mode = unsafe { libc::fcntl(fd, libc::F_GETFL) };
392+
if mode == -1 {
393+
return None;
394+
}
395+
match mode & libc::O_ACCMODE {
396+
libc::O_RDONLY => Some((true, false)),
397+
libc::O_RDWR => Some((true, true)),
398+
libc::O_WRONLY => Some((false, true)),
399+
_ => None,
400+
}
401+
}
402+
403+
let fd = unsafe { dirfd(self.0) };
404+
let mut b = f.debug_struct("Dir");
405+
b.field("fd", &fd);
406+
if let Some(path) = get_path_from_fd(fd) {
407+
b.field("path", &path);
408+
}
409+
if let Some((read, write)) = get_mode(fd) {
410+
b.field("read", &read).field("write", &write);
411+
}
412+
b.finish()
413+
}
414+
}
268415

269416
unsafe impl Send for Dir {}
270417
unsafe impl Sync for Dir {}
@@ -1653,79 +1800,6 @@ impl FromRawFd for File {
16531800

16541801
impl fmt::Debug for File {
16551802
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1656-
#[cfg(any(target_os = "linux", target_os = "illumos", target_os = "solaris"))]
1657-
fn get_path(fd: c_int) -> Option<PathBuf> {
1658-
let mut p = PathBuf::from("/proc/self/fd");
1659-
p.push(&fd.to_string());
1660-
run_path_with_cstr(&p, &readlink).ok()
1661-
}
1662-
1663-
#[cfg(any(target_vendor = "apple", target_os = "netbsd"))]
1664-
fn get_path(fd: c_int) -> Option<PathBuf> {
1665-
// FIXME: The use of PATH_MAX is generally not encouraged, but it
1666-
// is inevitable in this case because Apple targets and NetBSD define `fcntl`
1667-
// with `F_GETPATH` in terms of `MAXPATHLEN`, and there are no
1668-
// alternatives. If a better method is invented, it should be used
1669-
// instead.
1670-
let mut buf = vec![0; libc::PATH_MAX as usize];
1671-
let n = unsafe { libc::fcntl(fd, libc::F_GETPATH, buf.as_ptr()) };
1672-
if n == -1 {
1673-
cfg_if::cfg_if! {
1674-
if #[cfg(target_os = "netbsd")] {
1675-
// fallback to procfs as last resort
1676-
let mut p = PathBuf::from("/proc/self/fd");
1677-
p.push(&fd.to_string());
1678-
return run_path_with_cstr(&p, &readlink).ok()
1679-
} else {
1680-
return None;
1681-
}
1682-
}
1683-
}
1684-
let l = buf.iter().position(|&c| c == 0).unwrap();
1685-
buf.truncate(l as usize);
1686-
buf.shrink_to_fit();
1687-
Some(PathBuf::from(OsString::from_vec(buf)))
1688-
}
1689-
1690-
#[cfg(target_os = "freebsd")]
1691-
fn get_path(fd: c_int) -> Option<PathBuf> {
1692-
let info = Box::<libc::kinfo_file>::new_zeroed();
1693-
let mut info = unsafe { info.assume_init() };
1694-
info.kf_structsize = size_of::<libc::kinfo_file>() as libc::c_int;
1695-
let n = unsafe { libc::fcntl(fd, libc::F_KINFO, &mut *info) };
1696-
if n == -1 {
1697-
return None;
1698-
}
1699-
let buf = unsafe { CStr::from_ptr(info.kf_path.as_mut_ptr()).to_bytes().to_vec() };
1700-
Some(PathBuf::from(OsString::from_vec(buf)))
1701-
}
1702-
1703-
#[cfg(target_os = "vxworks")]
1704-
fn get_path(fd: c_int) -> Option<PathBuf> {
1705-
let mut buf = vec![0; libc::PATH_MAX as usize];
1706-
let n = unsafe { libc::ioctl(fd, libc::FIOGETNAME, buf.as_ptr()) };
1707-
if n == -1 {
1708-
return None;
1709-
}
1710-
let l = buf.iter().position(|&c| c == 0).unwrap();
1711-
buf.truncate(l as usize);
1712-
Some(PathBuf::from(OsString::from_vec(buf)))
1713-
}
1714-
1715-
#[cfg(not(any(
1716-
target_os = "linux",
1717-
target_os = "vxworks",
1718-
target_os = "freebsd",
1719-
target_os = "netbsd",
1720-
target_os = "illumos",
1721-
target_os = "solaris",
1722-
target_vendor = "apple",
1723-
)))]
1724-
fn get_path(_fd: c_int) -> Option<PathBuf> {
1725-
// FIXME(#24570): implement this for other Unix platforms
1726-
None
1727-
}
1728-
17291803
fn get_mode(fd: c_int) -> Option<(bool, bool)> {
17301804
let mode = unsafe { libc::fcntl(fd, libc::F_GETFL) };
17311805
if mode == -1 {
@@ -1742,7 +1816,7 @@ impl fmt::Debug for File {
17421816
let fd = self.as_raw_fd();
17431817
let mut b = f.debug_struct("File");
17441818
b.field("fd", &fd);
1745-
if let Some(path) = get_path(fd) {
1819+
if let Some(path) = get_path_from_fd(fd) {
17461820
b.field("path", &path);
17471821
}
17481822
if let Some((read, write)) = get_mode(fd) {

0 commit comments

Comments
 (0)