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 {}