Skip to content

Commit df275b6

Browse files
committed
dirfd: initial quick and dirty implementation for unix
1 parent e8a792d commit df275b6

File tree

3 files changed

+180
-5
lines changed

3 files changed

+180
-5
lines changed

library/std/src/fs.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,13 @@ pub enum TryLockError {
153153
WouldBlock,
154154
}
155155

156+
#[unstable(feature = "dirfd", issue = "120426")]
157+
#[cfg(target_family = "unix")]
158+
/// An object providing access to a directory on the filesystem.
159+
pub struct Dir {
160+
inner: fs_imp::Dir,
161+
}
162+
156163
/// Metadata information about a file.
157164
///
158165
/// This structure is returned from the [`metadata`] or
@@ -1474,6 +1481,25 @@ impl Seek for Arc<File> {
14741481
}
14751482
}
14761483

1484+
#[unstable(feature = "dirfd", issue = "120426")]
1485+
impl Dir {
1486+
/// Opens a file relative to this directory.
1487+
pub fn open<P: AsRef<Path>>(&self, path: P) -> io::Result<File> {
1488+
self.inner.open(path).map(|f| File { inner: f })
1489+
}
1490+
/// Opens a file relative to this directory with the specified options.
1491+
pub fn open_with<P: AsRef<Path>>(&self, path: P, opts: &OpenOptions) -> io::Result<File> {
1492+
self.inner.open_with(path, &opts.0).map(|f| File { inner: f })
1493+
}
1494+
}
1495+
1496+
#[unstable(feature = "dirfd", issue = "120426")]
1497+
impl fmt::Debug for Dir {
1498+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1499+
self.inner.fmt(f)
1500+
}
1501+
}
1502+
14771503
impl OpenOptions {
14781504
/// Creates a blank new set of options ready for configuration.
14791505
///

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ pub fn with_native_path<T>(path: &Path, f: &dyn Fn(&Path) -> io::Result<T>) -> i
5252
f(path)
5353
}
5454

55+
#[cfg(target_family = "unix")]
56+
pub use imp::Dir;
5557
pub use imp::{
5658
DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions,
5759
ReadDir,

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

Lines changed: 152 additions & 5 deletions
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 _};
@@ -265,7 +265,154 @@ impl ReadDir {
265265
}
266266
}
267267

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

270417
unsafe impl Send for Dir {}
271418
unsafe impl Sync for Dir {}
@@ -1764,7 +1911,7 @@ impl fmt::Debug for File {
17641911
let fd = self.as_raw_fd();
17651912
let mut b = f.debug_struct("File");
17661913
b.field("fd", &fd);
1767-
if let Some(path) = get_path(fd) {
1914+
if let Some(path) = get_path_from_fd(fd) {
17681915
b.field("path", &path);
17691916
}
17701917
if let Some((read, write)) = get_mode(fd) {

0 commit comments

Comments
 (0)