close_fds/iterfds/mod.rs
1mod fditer;
2pub use fditer::FdIter;
3
4#[cfg(any(
5 target_os = "linux",
6 target_os = "macos",
7 target_os = "ios",
8 target_os = "freebsd",
9 target_os = "netbsd",
10 target_os = "solaris",
11 target_os = "illumos",
12))]
13mod dirfd;
14
15/// A "builder" to construct an [`FdIter`] with custom parameters.
16///
17/// # Warnings
18///
19/// **TL;DR**: Don't use `FdIter`/`FdIterBuilder` in multithreaded programs unless you know what
20/// you're doing, and avoid opening/closing file descriptors while consuming an `FdIter`.
21///
22/// 1. File descriptors that are opened *during* iteration may or may not be included in the results
23/// (exact behavior is platform-specific and depends on several factors).
24///
25/// 2. **IMPORTANT**: On some platforms, if other threads open file descriptors at very specific
26/// times during a call to `FdIter::next()`, that may result in other file descriptors being
27/// skipped. Use with caution. (If this is a problem for you, set `.threadsafe(true)`, which
28/// avoids this issue).
29///
30/// 3. *Closing* file descriptors during iteration (in the same thread or in another thread) will
31/// not affect the iterator's ability to list other open file descriptors (if it does, that is a
32/// bug). However, in most cases you should use
33/// [`CloseFdsBuilder`](./struct.CloseFdsBuilder.html) to do this.
34///
35/// 4. Some of the file descriptors yielded by this iterator may be in active use by other sections
36/// of code. Be very careful about which operations you perform on them.
37///
38/// If your program is multi-threaded, this is especially true, since a file descriptor returned
39/// by this iterator may have been closed by the time your code tries to do something with it.
40#[derive(Clone, Debug)]
41pub struct FdIterBuilder {
42 possible: bool,
43 #[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
44 skip_nfds: bool,
45 #[cfg(any(
46 target_os = "linux",
47 target_os = "macos",
48 target_os = "ios",
49 target_os = "freebsd",
50 target_os = "netbsd",
51 target_os = "solaris",
52 target_os = "illumos",
53 ))]
54 dirfd: bool,
55}
56
57impl FdIterBuilder {
58 /// Create a new builder.
59 ///
60 /// `minfd` specifies the number of the file descriptor at which iteration will begin.
61 #[inline]
62 pub fn new() -> Self {
63 Self {
64 possible: false,
65 #[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
66 skip_nfds: false,
67 #[cfg(any(
68 target_os = "linux",
69 target_os = "macos",
70 target_os = "ios",
71 target_os = "freebsd",
72 target_os = "netbsd",
73 target_os = "solaris",
74 target_os = "illumos",
75 ))]
76 dirfd: true,
77 }
78 }
79
80 /// Set whether the returned `FdIter` is allowed to yield invalid file descriptors for
81 /// efficiency (default is `false`).
82 ///
83 /// If this flag is set, the caller is responsible for checking if the returned file descriptors
84 /// are valid.
85 ///
86 /// # Proper usage
87 ///
88 /// You should only use this flag if you immediately perform an operation on each file
89 /// descriptor that implicitly checks if the file descriptor is valid.
90 #[inline]
91 pub fn possible(&mut self, possible: bool) -> &mut Self {
92 self.possible = possible;
93 self
94 }
95
96 /// Set whether the returned `FdIter` needs to behave reliably in multithreaded programs
97 /// (default is `false`).
98 ///
99 /// If other threads open file descriptors at specific times, an `FdIter` may skip over other
100 /// file descriptors. Setting `.threadsafe(true)` prevents this, but may come at the cost of
101 /// significantly increased performance on some platforms (because the code which may behave
102 /// strangely in the presence of threads provides a potential performance improvement).
103 ///
104 /// Currently, setting this flag will only affect performance on 1) OpenBSD and 2) FreeBSD
105 /// without an `fdescfs` mounted on `/dev/fd`.
106 #[allow(unused_variables)]
107 #[inline]
108 pub fn threadsafe(&mut self, threadsafe: bool) -> &mut Self {
109 #[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
110 {
111 self.skip_nfds = threadsafe;
112 }
113 self
114 }
115
116 /// Set whether returned `FdIter` is allowed to look at special files for speedups (default is
117 /// `true`).
118 ///
119 /// On some systems, `/dev/fd` and/or `/proc/self/fd` provide an accurate view of the file
120 /// descriptors that the current process has open; if this flag is set to `true` then those
121 /// may be examined as an optimization.
122 ///
123 /// It may be desirable to set this to `false` e.g. if `chroot()`ing into an environment where
124 /// untrusted code may be able to replace `/proc` or `/dev`. However, on some platforms (such
125 /// as Linux<5.9 and macOS) setting this to `false` may significantly decrease performance.
126 #[allow(unused_variables)]
127 #[inline]
128 pub fn allow_filesystem(&mut self, fs: bool) -> &mut Self {
129 #[cfg(any(
130 target_os = "linux",
131 target_os = "macos",
132 target_os = "ios",
133 target_os = "freebsd",
134 target_os = "netbsd",
135 target_os = "solaris",
136 target_os = "illumos",
137 ))]
138 {
139 self.dirfd = fs;
140 }
141 self
142 }
143
144 /// Create an `FdIter` that iterates over the open file descriptors starting at `minfd`.
145 pub fn iter_from(&self, mut minfd: libc::c_int) -> FdIter {
146 if minfd < 0 {
147 minfd = 0;
148 }
149
150 FdIter {
151 curfd: minfd,
152 possible: self.possible,
153 maxfd: None,
154 #[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
155 skip_nfds: self.skip_nfds,
156 #[cfg(any(
157 target_os = "linux",
158 target_os = "macos",
159 target_os = "ios",
160 target_os = "freebsd",
161 target_os = "netbsd",
162 target_os = "solaris",
163 target_os = "illumos",
164 ))]
165 dirfd_iter: if self.dirfd {
166 dirfd::DirFdIter::open(minfd)
167 } else {
168 None
169 },
170 }
171 }
172}
173
174impl Default for FdIterBuilder {
175 #[inline]
176 fn default() -> Self {
177 Self::new()
178 }
179}
180
181/// Iterate over all open file descriptors for the current process, starting at `minfd`. The file
182/// descriptors are guaranteed to be returned in ascending order.
183///
184/// This is equivalent to `FdIterBuilder::new().iter_from(minfd)`.
185///
186/// See the warnings for [`FdIterBuilder`].
187#[inline]
188pub fn iter_open_fds(minfd: libc::c_int) -> FdIter {
189 FdIterBuilder::new().iter_from(minfd)
190}
191
192/// Equivalent to [`iter_open_fds()`], but behaves more reliably in multithreaded programs (at the
193/// cost of decreased performance on some platforms).
194///
195/// This is equivalent to `FdIterBuilder::new().threadsafe(true).iter_from(minfd)`.
196///
197/// See [`FdIterBuilder::threadsafe()`] for more information.
198#[inline]
199pub fn iter_open_fds_threadsafe(minfd: libc::c_int) -> FdIter {
200 FdIterBuilder::new().threadsafe(true).iter_from(minfd)
201}
202
203/// Identical to `iter_open_fds()`, but may -- for efficiency -- yield invalid file descriptors.
204///
205/// This is equivalent to `FdIterBuilder::new().possible(true).iter_from(minfd)`.
206///
207/// See [`FdIterBuilder::possible()`] for more information.
208#[inline]
209pub fn iter_possible_fds(minfd: libc::c_int) -> FdIter {
210 FdIterBuilder::new().possible(true).iter_from(minfd)
211}
212
213/// Identical to `iter_open_fds_threadsafe()`, but may -- for efficiency -- yield invalid file
214/// descriptors.
215///
216/// This is equivalent to `FdIterBuilder::new().possible(true).threadafe(true).iter_from(minfd)`.
217///
218/// See [`FdIterBuilder::possible()`] and [`FdIterBuilder::threadsafe()`] for more information.
219#[inline]
220pub fn iter_possible_fds_threadsafe(minfd: libc::c_int) -> FdIter {
221 FdIterBuilder::new()
222 .possible(true)
223 .threadsafe(true)
224 .iter_from(minfd)
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 fn open_files() -> [libc::c_int; 10] {
232 let mut fds = [-1; 10];
233 for cur_fd in fds.iter_mut() {
234 *cur_fd = unsafe { libc::open("/\0".as_ptr() as *const libc::c_char, libc::O_RDONLY) };
235 assert!(*cur_fd >= 0);
236 }
237 fds
238 }
239
240 unsafe fn close_files(fds: &[libc::c_int]) {
241 for &fd in fds {
242 libc::close(fd);
243 }
244 }
245
246 #[test]
247 fn test_size_hint_open() {
248 test_size_hint_generic(FdIterBuilder::new().threadsafe(false).iter_from(0));
249 test_size_hint_generic(FdIterBuilder::new().threadsafe(true).iter_from(0));
250
251 let fds = open_files();
252 test_size_hint_generic(FdIterBuilder::new().threadsafe(false).iter_from(0));
253 test_size_hint_generic(FdIterBuilder::new().threadsafe(true).iter_from(0));
254 unsafe {
255 close_files(&fds);
256 }
257 }
258
259 #[test]
260 fn test_size_hint_possible() {
261 test_size_hint_generic(
262 FdIterBuilder::new()
263 .possible(true)
264 .threadsafe(false)
265 .iter_from(0),
266 );
267 test_size_hint_generic(
268 FdIterBuilder::new()
269 .possible(true)
270 .threadsafe(true)
271 .iter_from(0),
272 );
273
274 let fds = open_files();
275 test_size_hint_generic(
276 FdIterBuilder::new()
277 .possible(true)
278 .threadsafe(false)
279 .iter_from(0),
280 );
281 test_size_hint_generic(
282 FdIterBuilder::new()
283 .possible(true)
284 .threadsafe(true)
285 .iter_from(0),
286 );
287 unsafe {
288 close_files(&fds);
289 }
290 }
291
292 fn test_size_hint_generic(mut fditer: FdIter) {
293 let (mut init_low, mut init_high) = fditer.size_hint();
294 if let Some(init_high) = init_high {
295 // Sanity check
296 assert!(init_high >= init_low);
297 }
298
299 let mut i = 0;
300 while let Some(_fd) = fditer.next() {
301 let (cur_low, cur_high) = fditer.size_hint();
302
303 // Adjust them so they're comparable to init_low and init_high
304 let adj_low = cur_low + i + 1;
305 let adj_high = if let Some(cur_high) = cur_high {
306 // Sanity check
307 assert!(cur_high >= cur_low);
308
309 Some(cur_high + i + 1)
310 } else {
311 None
312 };
313
314 // Now we adjust init_low and init_high to be the most restrictive limits that we've
315 // received so far.
316 if adj_low > init_low {
317 init_low = adj_low;
318 }
319
320 if let Some(adj_high) = adj_high {
321 if let Some(ihigh) = init_high {
322 if adj_high < ihigh {
323 init_high = Some(adj_high);
324 }
325 } else {
326 init_high = Some(adj_high);
327 }
328 }
329
330 i += 1;
331 }
332
333 // At the end, the lower boundary should be 0. The upper boundary can be anything.
334 let (final_low, _) = fditer.size_hint();
335 assert_eq!(final_low, 0);
336
337 // Now make sure that the actual count falls within the boundaries we were given
338 assert!(i >= init_low);
339 if let Some(init_high) = init_high {
340 assert!(i <= init_high);
341 }
342 }
343
344 #[test]
345 fn test_fused_open() {
346 test_fused_generic(FdIterBuilder::new().threadsafe(false).iter_from(0));
347 test_fused_generic(FdIterBuilder::new().threadsafe(true).iter_from(0));
348
349 let fds = open_files();
350 test_fused_generic(FdIterBuilder::new().threadsafe(false).iter_from(0));
351 test_fused_generic(FdIterBuilder::new().threadsafe(true).iter_from(0));
352 unsafe {
353 close_files(&fds);
354 }
355 }
356
357 #[test]
358 fn test_fused_possible() {
359 test_fused_generic(
360 FdIterBuilder::new()
361 .possible(true)
362 .threadsafe(false)
363 .iter_from(0),
364 );
365 test_fused_generic(
366 FdIterBuilder::new()
367 .possible(true)
368 .threadsafe(true)
369 .iter_from(0),
370 );
371
372 let fds = open_files();
373 test_fused_generic(
374 FdIterBuilder::new()
375 .possible(true)
376 .threadsafe(false)
377 .iter_from(0),
378 );
379 test_fused_generic(
380 FdIterBuilder::new()
381 .possible(true)
382 .threadsafe(true)
383 .iter_from(0),
384 );
385
386 unsafe {
387 close_files(&fds);
388 }
389 }
390
391 fn test_fused_generic(mut fditer: FdIter) {
392 // Exhaust the iterator
393 fditer.by_ref().count();
394 assert_eq!(fditer.next(), None);
395 }
396}