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