close_fds/iterfds/fditer.rs
1/// An iterator over the current process's file descriptors.
2///
3/// The recommended way to create an `FdIter` is with
4/// [`FdIterBuilder`](./struct.FdIterBuilder.html); however, the "iter"
5/// functions (such as [`iter_open_fds()`](./fn.iter_open_fds.html)) can also be used.
6///
7/// If this iterator is created with [`FdIterBuilder::possible()`](./struct.FdIterBuilder.html)
8/// set, or with one of the "possible" functions, then it may yield invalid file descriptors. This
9/// can be checked with [`Self::is_possible_iter()`].
10pub struct FdIter {
11 #[cfg(any(
12 target_os = "linux",
13 target_os = "macos",
14 target_os = "ios",
15 target_os = "freebsd",
16 target_os = "netbsd",
17 target_os = "solaris",
18 target_os = "illumos",
19 ))]
20 pub(crate) dirfd_iter: Option<super::dirfd::DirFdIter>,
21 pub(crate) curfd: libc::c_int,
22 pub(crate) possible: bool,
23 pub(crate) maxfd: Option<libc::c_int>,
24 /// If this is true, it essentially means "don't try the 'nfds' methods of finding the maximum
25 /// open file descriptor."
26 /// `close_open_fds()` passes this as true on some systems becaus the system has a working
27 /// closefrom() and at some point it can just close the rest of the file descriptors in one go.
28 /// Additionally, the "nfds" method is not thread-safe.
29 #[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
30 pub(crate) skip_nfds: bool,
31}
32
33impl FdIter {
34 fn get_maxfd_direct(&self) -> libc::c_int {
35 // This function can return -1 if no file descriptors are open. Otherwise it should return
36 // a nonnegative integer indicating the maximum file descriptor that might be open.
37
38 #[cfg(target_os = "netbsd")]
39 unsafe {
40 // NetBSD allows us to get the maximum open file descriptor
41
42 *libc::__errno() = 0;
43 let maxfd = libc::fcntl(0, libc::F_MAXFD);
44
45 if maxfd >= 0 {
46 return maxfd;
47 } else if maxfd == -1 && *libc::__errno() == 0 {
48 // fcntl(F_MAXFD) actually succeeded and returned -1, which means that no file
49 // descriptors are open.
50 return -1;
51 }
52 }
53
54 #[cfg(target_os = "freebsd")]
55 if !self.skip_nfds {
56 // On FreeBSD, we can get the *number* of open file descriptors. From that, we can use
57 // an is_fd_valid() loop to get the maximum open file descriptor.
58
59 let mib = [
60 libc::CTL_KERN,
61 libc::KERN_PROC,
62 crate::sys::KERN_PROC_NFDS,
63 0,
64 ];
65 let mut nfds: libc::c_int = 0;
66 let mut oldlen = core::mem::size_of::<libc::c_int>();
67
68 if unsafe {
69 libc::sysctl(
70 mib.as_ptr(),
71 mib.len() as libc::c_uint,
72 &mut nfds as *mut libc::c_int as *mut libc::c_void,
73 &mut oldlen,
74 core::ptr::null(),
75 0,
76 )
77 } == 0
78 {
79 if let Some(maxfd) = Self::nfds_to_maxfd(nfds) {
80 return maxfd;
81 }
82 }
83 }
84
85 #[cfg(target_os = "openbsd")]
86 if !self.skip_nfds {
87 // On OpenBSD, we have a similar situation as with FreeBSD -- we can get the *number*
88 // of open file descriptors, and perhaps work from that to get the maximum open file
89 // descriptor.
90
91 if let Some(maxfd) = Self::nfds_to_maxfd(unsafe { crate::sys::getdtablecount() }) {
92 return maxfd;
93 }
94 }
95
96 let fdlimit = unsafe { libc::sysconf(libc::_SC_OPEN_MAX) };
97
98 // Clamp it at 65536 because that's a LOT of file descriptors
99 // Also don't trust values below 1024
100
101 fdlimit.max(1024).min(65536) as libc::c_int - 1
102 }
103
104 #[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
105 #[inline]
106 fn nfds_to_maxfd(nfds: libc::c_int) -> Option<libc::c_int> {
107 // Given the number of open file descriptors, return the largest open file descriptor (or
108 // None if it can't be reasonably determined).
109
110 if nfds == 0 {
111 // No open file descriptors -- nothing to do!
112 return Some(-1);
113 } else if nfds < 0 {
114 // Probably failure of the underlying function
115 return None;
116 } else if nfds >= 100 {
117 // We're probably better off just iterating through
118 return None;
119 }
120
121 let mut nfds_found = 0;
122
123 // We know the number of open file descriptors; let's use that to try to find the largest
124 // open file descriptor.
125
126 for fd in 0..(nfds * 2) {
127 if crate::util::is_fd_valid(fd) {
128 // Valid file descriptor
129 nfds_found += 1;
130
131 if nfds_found >= nfds {
132 // We've found all the open file descriptors.
133 // We now know that the current `fd` is the largest open file descriptor
134 return Some(fd);
135 }
136 }
137 }
138
139 // We haven't found all of the open file descriptors yet, but it seems like we *should*
140 // have.
141 //
142 // This usually means one of two things:
143 //
144 // 1. The process opened a large number of file descriptors, then closed many of them.
145 // However, it left several of the high-numbered file descriptors open. (For example,
146 // consider the case where the open file descriptors are 0, 1, 2, 50, and 100. nfds=5,
147 // but the highest open file descriptor is actually 100!)
148 // 2. The 'nfds' method is vulnerable to a race condition: if a file descriptor is closed
149 // after the number of open file descriptors has been obtained, but before the fcntl()
150 // loop reaches that file descriptor, then the loop will never find all of the open file
151 // descriptors because it will be stuck at n_fds_found = nfds-1.
152 // If this happens, without this check the loop would essentially become an infinite
153 // loop.
154 // (For example, consider the case where the open file descriptors are 0, 1, 2, and 3. If
155 // file descriptor 3 is closed before the fd=3 iteration, then we will be stuck at
156 // n_fds_found=3 and will never be able to find the 4th file descriptor.)
157 //
158 // Error on the side of caution (case 2 is dangerous) and let the caller select another
159 // method.
160
161 None
162 }
163
164 #[inline]
165 fn get_maxfd(&mut self) -> libc::c_int {
166 match self.maxfd {
167 Some(maxfd) => maxfd,
168 None => {
169 let maxfd = self.get_maxfd_direct();
170 debug_assert!(maxfd >= -1);
171 self.maxfd = Some(maxfd);
172 maxfd
173 }
174 }
175 }
176
177 /// Returns whether this iterator was created with one of the "possible" iteration functions,
178 /// in which case it may yield invalid file descriptors and the caller is responsible for
179 /// checking their validity.
180 #[inline]
181 pub fn is_possible_iter(&self) -> bool {
182 self.possible
183 }
184}
185
186impl Iterator for FdIter {
187 type Item = libc::c_int;
188
189 fn next(&mut self) -> Option<Self::Item> {
190 #[cfg(any(
191 target_os = "linux",
192 target_os = "macos",
193 target_os = "ios",
194 target_os = "freebsd",
195 target_os = "netbsd",
196 target_os = "solaris",
197 target_os = "illumos",
198 ))]
199 if let Some(dfd_iter) = self.dirfd_iter.as_mut() {
200 // Try iterating using the directory file descriptor we opened
201
202 match dfd_iter.next() {
203 Ok(Some(fd)) => {
204 debug_assert!(fd >= self.curfd);
205
206 // We set self.curfd so that if something goes wrong we can switch to the maxfd
207 // loop without repeating file descriptors
208 self.curfd = fd + 1;
209
210 return Some(fd);
211 }
212
213 Ok(None) => return None,
214
215 // Something went wrong. Close the directory file descriptor and fall back on a
216 // maxfd loop
217 Err(_) => self.dirfd_iter = None,
218 }
219 }
220
221 let maxfd = self.get_maxfd();
222
223 while self.curfd <= maxfd {
224 // Get the current file descriptor
225 let fd = self.curfd;
226
227 // Increment it for next time
228 self.curfd += 1;
229
230 // If we weren't given the "possible" flag, we have to check that it's a valid file
231 // descriptor first.
232 if self.possible || crate::util::is_fd_valid(fd) {
233 return Some(fd);
234 }
235 }
236
237 // Exhausted the range
238 None
239 }
240
241 fn size_hint(&self) -> (usize, Option<usize>) {
242 #[cfg(any(
243 target_os = "linux",
244 target_os = "macos",
245 target_os = "ios",
246 target_os = "freebsd",
247 target_os = "netbsd",
248 target_os = "solaris",
249 target_os = "illumos",
250 ))]
251 if let Some(dfd_iter) = self.dirfd_iter.as_ref() {
252 // Delegate to the directory file descriptor
253 return dfd_iter.size_hint();
254 }
255
256 if let Some(maxfd) = self.maxfd {
257 if maxfd == -1 {
258 // No file descriptors open
259 return (0, Some(0));
260 }
261 debug_assert!(maxfd > 0);
262
263 // maxfd is set; we can give an upper bound by comparing to curfd
264 let diff = (maxfd as usize + 1).saturating_sub(self.curfd as usize);
265
266 // If we were given the "possible" flag, then this is also the lower limit.
267 (if self.possible { diff } else { 0 }, Some(diff))
268 } else {
269 // Unknown
270 (0, Some(libc::c_int::MAX as usize))
271 }
272 }
273
274 #[inline]
275 fn min(mut self) -> Option<Self::Item> {
276 self.next()
277 }
278
279 #[inline]
280 fn max(self) -> Option<Self::Item> {
281 self.last()
282 }
283}
284
285impl core::iter::FusedIterator for FdIter {}