Skip to content

Commit 20ff465

Browse files
committed
dirfd: initial quick and dirty implementation for unix
1 parent c6c1796 commit 20ff465

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
@@ -116,6 +116,13 @@ pub struct File {
116116
inner: fs_imp::File,
117117
}
118118

119+
#[unstable(feature = "dirfd", issue = "120426")]
120+
#[cfg(target_family = "unix")]
121+
/// An object providing access to a directory on the filesystem.
122+
pub struct Dir {
123+
inner: fs_imp::Dir,
124+
}
125+
119126
/// Metadata information about a file.
120127
///
121128
/// This structure is returned from the [`metadata`] or
@@ -1353,6 +1360,25 @@ impl Seek for Arc<File> {
13531360
}
13541361
}
13551362

1363+
#[unstable(feature = "dirfd", issue = "120426")]
1364+
impl Dir {
1365+
/// Opens a file relative to this directory.
1366+
pub fn open<P: AsRef<Path>>(&self, path: P) -> io::Result<File> {
1367+
self.inner.open(path).map(|f| File { inner: f })
1368+
}
1369+
/// Opens a file relative to this directory with the specified options.
1370+
pub fn open_with<P: AsRef<Path>>(&self, path: P, opts: &OpenOptions) -> io::Result<File> {
1371+
self.inner.open_with(path, &opts.0).map(|f| File { inner: f })
1372+
}
1373+
}
1374+
1375+
#[unstable(feature = "dirfd", issue = "120426")]
1376+
impl fmt::Debug for Dir {
1377+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1378+
self.inner.fmt(f)
1379+
}
1380+
}
1381+
13561382
impl OpenOptions {
13571383
/// Creates a blank new set of options ready for configuration.
13581384
///

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

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

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

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

+152-78
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ use libc::{c_int, mode_t};
5353
#[cfg(target_os = "android")]
5454
use libc::{
5555
dirent as dirent64, fstat as fstat64, fstatat as fstatat64, ftruncate64, lseek64,
56-
lstat as lstat64, off64_t, open as open64, stat as stat64,
56+
lstat as lstat64, off64_t, open as open64, openat as openat64, stat as stat64,
5757
};
5858
#[cfg(not(any(
5959
all(target_os = "linux", not(target_env = "musl")),
@@ -63,14 +63,14 @@ use libc::{
6363
)))]
6464
use libc::{
6565
dirent as dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64,
66-
lstat as lstat64, off_t as off64_t, open as open64, stat as stat64,
66+
lstat as lstat64, off_t as off64_t, open as open64, openat as openat64, stat as stat64,
6767
};
6868
#[cfg(any(
6969
all(target_os = "linux", not(target_env = "musl")),
7070
target_os = "l4re",
7171
target_os = "hurd"
7272
))]
73-
use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64};
73+
use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, openat64, stat64};
7474

7575
use crate::ffi::{CStr, OsStr, OsString};
7676
use crate::fmt::{self, Write as _};
@@ -262,7 +262,154 @@ impl ReadDir {
262262
}
263263
}
264264

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

267414
unsafe impl Send for Dir {}
268415
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)