Skip to content

Commit 2e4f70e

Browse files
authored
Add Dir::new function, constructing a Dir from an owned fd. (#950)
Add `Dir::new` function, which takes ownership of an `OwnedFd`. This incurs undefined behavior from the libc `readdir` function if there are any copies of the file descriptor live somewhere, so this function is `unsafe`.
1 parent 797a457 commit 2e4f70e

File tree

3 files changed

+114
-6
lines changed

3 files changed

+114
-6
lines changed

src/backend/libc/fs/dir.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
use super::types::FileType;
33
use crate::backend::c;
44
use crate::backend::conv::owned_fd;
5-
use crate::fd::{AsFd, BorrowedFd};
5+
use crate::fd::{AsFd, BorrowedFd, OwnedFd};
66
use crate::ffi::{CStr, CString};
77
use crate::fs::{fcntl_getfl, openat, Mode, OFlags};
88
#[cfg(not(target_os = "vita"))]
@@ -48,8 +48,34 @@ pub struct Dir {
4848
}
4949

5050
impl Dir {
51-
/// Construct a `Dir` that reads entries from the given directory
52-
/// file descriptor.
51+
/// Take ownership of `fd` and construct a `Dir` that reads entries from
52+
/// the given directory file descriptor.
53+
#[inline]
54+
pub fn new<Fd: Into<OwnedFd>>(fd: Fd) -> io::Result<Self> {
55+
Self::_new(fd.into())
56+
}
57+
58+
#[inline]
59+
fn _new(fd: OwnedFd) -> io::Result<Self> {
60+
let raw = owned_fd(fd);
61+
unsafe {
62+
let libc_dir = c::fdopendir(raw);
63+
64+
if let Some(libc_dir) = NonNull::new(libc_dir) {
65+
Ok(Self {
66+
libc_dir,
67+
any_errors: false,
68+
})
69+
} else {
70+
let err = io::Errno::last_os_error();
71+
let _ = c::close(raw);
72+
Err(err)
73+
}
74+
}
75+
}
76+
77+
/// Borrow `fd` and construct a `Dir` that reads entries from the given
78+
/// directory file descriptor.
5379
#[inline]
5480
pub fn read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self> {
5581
Self::_read_from(fd.as_fd())

src/backend/linux_raw/fs/dir.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,26 @@ pub struct Dir {
3232
}
3333

3434
impl Dir {
35-
/// Construct a `Dir` that reads entries from the given directory
36-
/// file descriptor.
35+
/// Take ownership of `fd` and construct a `Dir` that reads entries from
36+
/// the given directory file descriptor.
37+
#[inline]
38+
pub fn new<Fd: Into<OwnedFd>>(fd: Fd) -> io::Result<Self> {
39+
Self::_new(fd.into())
40+
}
41+
42+
#[inline]
43+
fn _new(fd: OwnedFd) -> io::Result<Self> {
44+
Ok(Self {
45+
fd,
46+
any_errors: false,
47+
rewind: false,
48+
buf: Vec::new(),
49+
pos: 0,
50+
})
51+
}
52+
53+
/// Borrow `fd` and construct a `Dir` that reads entries from the given
54+
/// directory file descriptor.
3755
#[inline]
3856
pub fn read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self> {
3957
Self::_read_from(fd.as_fd())

tests/fs/dir.rs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#[test]
2-
fn test_dir() {
2+
fn test_dir_read_from() {
33
let t = rustix::fs::openat(
44
rustix::fs::CWD,
55
rustix::cstr!("."),
@@ -62,6 +62,70 @@ fn test_dir() {
6262
assert!(saw_cargo_toml);
6363
}
6464

65+
#[test]
66+
fn test_dir_new() {
67+
let t = rustix::fs::openat(
68+
rustix::fs::CWD,
69+
rustix::cstr!("."),
70+
rustix::fs::OFlags::RDONLY | rustix::fs::OFlags::CLOEXEC,
71+
rustix::fs::Mode::empty(),
72+
)
73+
.unwrap();
74+
75+
let _file = rustix::fs::openat(
76+
&t,
77+
rustix::cstr!("Cargo.toml"),
78+
rustix::fs::OFlags::RDONLY | rustix::fs::OFlags::CLOEXEC,
79+
rustix::fs::Mode::empty(),
80+
)
81+
.unwrap();
82+
83+
let mut dir = rustix::fs::Dir::new(t).unwrap();
84+
85+
// Read the directory entries. We use `while let Some(entry)` so that we
86+
// don't consume the `Dir` so that we can run more tests on it.
87+
let mut saw_dot = false;
88+
let mut saw_dotdot = false;
89+
let mut saw_cargo_toml = false;
90+
while let Some(entry) = dir.read() {
91+
let entry = entry.unwrap();
92+
if entry.file_name() == rustix::cstr!(".") {
93+
saw_dot = true;
94+
} else if entry.file_name() == rustix::cstr!("..") {
95+
saw_dotdot = true;
96+
} else if entry.file_name() == rustix::cstr!("Cargo.toml") {
97+
saw_cargo_toml = true;
98+
}
99+
}
100+
assert!(saw_dot);
101+
assert!(saw_dotdot);
102+
assert!(saw_cargo_toml);
103+
104+
// Rewind the directory so we can iterate over the entries again.
105+
dir.rewind();
106+
107+
// For what comes next, we don't need `mut` anymore.
108+
let dir = dir;
109+
110+
// Read the directory entries, again. This time we use `for entry in dir`.
111+
let mut saw_dot = false;
112+
let mut saw_dotdot = false;
113+
let mut saw_cargo_toml = false;
114+
for entry in dir {
115+
let entry = entry.unwrap();
116+
if entry.file_name() == rustix::cstr!(".") {
117+
saw_dot = true;
118+
} else if entry.file_name() == rustix::cstr!("..") {
119+
saw_dotdot = true;
120+
} else if entry.file_name() == rustix::cstr!("Cargo.toml") {
121+
saw_cargo_toml = true;
122+
}
123+
}
124+
assert!(saw_dot);
125+
assert!(saw_dotdot);
126+
assert!(saw_cargo_toml);
127+
}
128+
65129
// Test that `Dir` silently stops iterating if the directory has been removed.
66130
//
67131
// Except on FreeBSD and macOS, where apparently `readdir` just keeps reading.

0 commit comments

Comments
 (0)