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