nix/dir.rs
1use crate::{Error, NixPath, Result};
2use crate::errno::Errno;
3use crate::fcntl::{self, OFlag};
4use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
5use std::ptr;
6use std::ffi;
7use crate::sys;
8use cfg_if::cfg_if;
9
10#[cfg(target_os = "linux")]
11use libc::{dirent64 as dirent, readdir64_r as readdir_r};
12
13#[cfg(not(target_os = "linux"))]
14use libc::{dirent, readdir_r};
15
16/// An open directory.
17///
18/// This is a lower-level interface than `std::fs::ReadDir`. Notable differences:
19/// * can be opened from a file descriptor (as returned by `openat`, perhaps before knowing
20/// if the path represents a file or directory).
21/// * implements `AsRawFd`, so it can be passed to `fstat`, `openat`, etc.
22/// The file descriptor continues to be owned by the `Dir`, so callers must not keep a `RawFd`
23/// after the `Dir` is dropped.
24/// * can be iterated through multiple times without closing and reopening the file
25/// descriptor. Each iteration rewinds when finished.
26/// * returns entries for `.` (current directory) and `..` (parent directory).
27/// * returns entries' names as a `CStr` (no allocation or conversion beyond whatever libc
28/// does).
29#[derive(Debug, Eq, Hash, PartialEq)]
30pub struct Dir(
31 ptr::NonNull<libc::DIR>
32);
33
34impl Dir {
35 /// Opens the given path as with `fcntl::open`.
36 pub fn open<P: ?Sized + NixPath>(path: &P, oflag: OFlag,
37 mode: sys::stat::Mode) -> Result<Self> {
38 let fd = fcntl::open(path, oflag, mode)?;
39 Dir::from_fd(fd)
40 }
41
42 /// Opens the given path as with `fcntl::openat`.
43 pub fn openat<P: ?Sized + NixPath>(dirfd: RawFd, path: &P, oflag: OFlag,
44 mode: sys::stat::Mode) -> Result<Self> {
45 let fd = fcntl::openat(dirfd, path, oflag, mode)?;
46 Dir::from_fd(fd)
47 }
48
49 /// Converts from a descriptor-based object, closing the descriptor on success or failure.
50 #[inline]
51 pub fn from<F: IntoRawFd>(fd: F) -> Result<Self> {
52 Dir::from_fd(fd.into_raw_fd())
53 }
54
55 /// Converts from a file descriptor, closing it on success or failure.
56 pub fn from_fd(fd: RawFd) -> Result<Self> {
57 let d = ptr::NonNull::new(unsafe { libc::fdopendir(fd) }).ok_or_else(|| {
58 let e = Error::last();
59 unsafe { libc::close(fd) };
60 e
61 })?;
62 Ok(Dir(d))
63 }
64
65 /// Returns an iterator of `Result<Entry>` which rewinds when finished.
66 pub fn iter(&mut self) -> Iter {
67 Iter(self)
68 }
69}
70
71// `Dir` is not `Sync`. With the current implementation, it could be, but according to
72// https://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html,
73// future versions of POSIX are likely to obsolete `readdir_r` and specify that it's unsafe to
74// call `readdir` simultaneously from multiple threads.
75//
76// `Dir` is safe to pass from one thread to another, as it's not reference-counted.
77unsafe impl Send for Dir {}
78
79impl AsRawFd for Dir {
80 fn as_raw_fd(&self) -> RawFd {
81 unsafe { libc::dirfd(self.0.as_ptr()) }
82 }
83}
84
85impl Drop for Dir {
86 fn drop(&mut self) {
87 let e = Errno::result(unsafe { libc::closedir(self.0.as_ptr()) });
88 if !std::thread::panicking() && e == Err(Errno::EBADF) {
89 panic!("Closing an invalid file descriptor!");
90 };
91 }
92}
93
94fn next(dir: &mut Dir) -> Option<Result<Entry>> {
95 unsafe {
96 // Note: POSIX specifies that portable applications should dynamically allocate a
97 // buffer with room for a `d_name` field of size `pathconf(..., _PC_NAME_MAX)` plus 1
98 // for the NUL byte. It doesn't look like the std library does this; it just uses
99 // fixed-sized buffers (and libc's dirent seems to be sized so this is appropriate).
100 // Probably fine here too then.
101 let mut ent = std::mem::MaybeUninit::<dirent>::uninit();
102 let mut result = ptr::null_mut();
103 if let Err(e) = Errno::result(
104 readdir_r(dir.0.as_ptr(), ent.as_mut_ptr(), &mut result))
105 {
106 return Some(Err(e));
107 }
108 if result.is_null() {
109 return None;
110 }
111 assert_eq!(result, ent.as_mut_ptr());
112 Some(Ok(Entry(ent.assume_init())))
113 }
114}
115
116#[derive(Debug, Eq, Hash, PartialEq)]
117pub struct Iter<'d>(&'d mut Dir);
118
119impl<'d> Iterator for Iter<'d> {
120 type Item = Result<Entry>;
121
122 fn next(&mut self) -> Option<Self::Item> {
123 next(self.0)
124 }
125}
126
127impl<'d> Drop for Iter<'d> {
128 fn drop(&mut self) {
129 unsafe { libc::rewinddir((self.0).0.as_ptr()) }
130 }
131}
132
133/// The return type of [Dir::into_iter]
134#[derive(Debug, Eq, Hash, PartialEq)]
135pub struct OwningIter(Dir);
136
137impl Iterator for OwningIter {
138 type Item = Result<Entry>;
139
140 fn next(&mut self) -> Option<Self::Item> {
141 next(&mut self.0)
142 }
143}
144
145/// The file descriptor continues to be owned by the `OwningIter`,
146/// so callers must not keep a `RawFd` after the `OwningIter` is dropped.
147impl AsRawFd for OwningIter {
148 fn as_raw_fd(&self) -> RawFd {
149 self.0.as_raw_fd()
150 }
151}
152
153impl IntoIterator for Dir {
154 type Item = Result<Entry>;
155 type IntoIter = OwningIter;
156
157 /// Creates a owning iterator, that is, one that takes ownership of the
158 /// `Dir`. The `Dir` cannot be used after calling this. This can be useful
159 /// when you have a function that both creates a `Dir` instance and returns
160 /// an `Iterator`.
161 ///
162 /// Example:
163 ///
164 /// ```
165 /// use nix::{dir::Dir, fcntl::OFlag, sys::stat::Mode};
166 /// use std::{iter::Iterator, string::String};
167 ///
168 /// fn ls_upper(dirname: &str) -> impl Iterator<Item=String> {
169 /// let d = Dir::open(dirname, OFlag::O_DIRECTORY, Mode::S_IXUSR).unwrap();
170 /// d.into_iter().map(|x| x.unwrap().file_name().as_ref().to_string_lossy().to_ascii_uppercase())
171 /// }
172 /// ```
173 fn into_iter(self) -> Self::IntoIter {
174 OwningIter(self)
175 }
176}
177
178/// A directory entry, similar to `std::fs::DirEntry`.
179///
180/// Note that unlike the std version, this may represent the `.` or `..` entries.
181#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
182#[repr(transparent)]
183pub struct Entry(dirent);
184
185#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
186pub enum Type {
187 Fifo,
188 CharacterDevice,
189 Directory,
190 BlockDevice,
191 File,
192 Symlink,
193 Socket,
194}
195
196impl Entry {
197 /// Returns the inode number (`d_ino`) of the underlying `dirent`.
198 #[allow(clippy::useless_conversion)] // Not useless on all OSes
199 // The cast is not unnecessary on all platforms.
200 #[allow(clippy::unnecessary_cast)]
201 pub fn ino(&self) -> u64 {
202 cfg_if! {
203 if #[cfg(any(target_os = "android",
204 target_os = "emscripten",
205 target_os = "fuchsia",
206 target_os = "haiku",
207 target_os = "illumos",
208 target_os = "ios",
209 target_os = "l4re",
210 target_os = "linux",
211 target_os = "macos",
212 target_os = "solaris"))] {
213 self.0.d_ino as u64
214 } else {
215 u64::from(self.0.d_fileno)
216 }
217 }
218 }
219
220 /// Returns the bare file name of this directory entry without any other leading path component.
221 pub fn file_name(&self) -> &ffi::CStr {
222 unsafe { ::std::ffi::CStr::from_ptr(self.0.d_name.as_ptr()) }
223 }
224
225 /// Returns the type of this directory entry, if known.
226 ///
227 /// See platform `readdir(3)` or `dirent(5)` manpage for when the file type is known;
228 /// notably, some Linux filesystems don't implement this. The caller should use `stat` or
229 /// `fstat` if this returns `None`.
230 pub fn file_type(&self) -> Option<Type> {
231 #[cfg(not(any(target_os = "illumos", target_os = "solaris")))]
232 match self.0.d_type {
233 libc::DT_FIFO => Some(Type::Fifo),
234 libc::DT_CHR => Some(Type::CharacterDevice),
235 libc::DT_DIR => Some(Type::Directory),
236 libc::DT_BLK => Some(Type::BlockDevice),
237 libc::DT_REG => Some(Type::File),
238 libc::DT_LNK => Some(Type::Symlink),
239 libc::DT_SOCK => Some(Type::Socket),
240 /* libc::DT_UNKNOWN | */ _ => None,
241 }
242
243 // illumos and Solaris systems do not have the d_type member at all:
244 #[cfg(any(target_os = "illumos", target_os = "solaris"))]
245 None
246 }
247}