detcore/host_timed_producers.rs
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! Signal producers whose signal Linux sends at a moment host timing
10//! decides, which an in-guest run refuses (A5 step 3c slice 0b; design
11//! sections 16.30, D25, D28 and D28a of
12//! <https://github.com/rrnewton/hermit/issues/4485>).
13//!
14//! While the in-guest runtime runs its own code, ordinary signals are blocked,
15//! and Linux keeps a blocked signal pending even when the guest ignores it, so
16//! a signal the kernel sends at a host-timed moment could become pending
17//! whatever the guest's action and mask. So on the in-guest backends' default
18//! route (`Config::in_guest_refuses_host_timed_producers`) Detcore refuses the
19//! one operation that completes such a producer, and Hermit refuses a launch
20//! whose guest would inherit one already complete.
21//!
22//! A descriptor's SIGIO and SIGURG producers (Linux v6.12):
23//! - SIGIO reaches a descriptor's owner only through the descriptor's fasync
24//! list (`kill_fasync_rcu`, `fs/fcntl.c:1116-1142`, sends it with `send_sigio`),
25//! which setting `O_ASYNC` populates: `F_SETFL` calls the file's `->fasync`
26//! when `O_ASYNC` changes (`setfl`, `fs/fcntl.c:76-82`), and so does
27//! `FIOASYNC` (`ioctl_fioasync`, `fs/ioctl.c:365-385`). `open(O_ASYNC)` keeps
28//! the flag but never calls `->fasync` (`do_dentry_open`, `fs/open.c:974`),
29//! and a later `F_SETFL` that leaves the flag set calls nothing either. A
30//! descriptor whose status flags show `O_ASYNC` is treated as armed here
31//! anyway, which can only refuse more.
32//! - A socket's owner gets SIGURG for out-of-band data without `O_ASYNC`
33//! (`sk_send_sigurg`, `net/core/sock.c:3503-3508`, calls `send_sigurg`,
34//! `fs/fcntl.c:941-978`, which reads only the owner).
35//! - `F_SETLEASE` with a read or write lease, and `F_NOTIFY` with a nonzero
36//! mask, make the caller the owner themselves (`lease_setup`,
37//! `fs/locks.c:563-577`; `fcntl_dirnotify`, `fs/notify/dnotify/dnotify.c:372`)
38//! and are refused outright.
39//!
40//! An owner is named by `F_SETOWN` (`f_setown`, `fs/fcntl.c:154-189`),
41//! `F_SETOWN_EX` (`f_setown_ex`, `fs/fcntl.c:217-259`), and on a socket
42//! `FIOSETOWN` and `SIOCSPGRP` (`sock_ioctl`, `net/socket.c:1284-1289`); an
43//! owner of 0 (a pid of 0 for `F_SETOWN_EX`) removes it.
44
45use std::os::fd::RawFd;
46
47use nix::errno::Errno;
48
49/// `F_SETOWN_EX`'s owner types (Linux's `include/uapi/asm-generic/fcntl.h`;
50/// the pinned libc lacks them).
51pub const F_OWNER_TID: libc::c_int = 0;
52/// See [`F_OWNER_TID`].
53pub const F_OWNER_PID: libc::c_int = 1;
54/// See [`F_OWNER_TID`].
55pub const F_OWNER_PGRP: libc::c_int = 2;
56/// `fcntl`'s command that reads a descriptor's owner and its type (Linux's
57/// `include/uapi/asm-generic/fcntl.h`; the pinned libc lacks it).
58pub const F_GETOWN_EX: libc::c_int = 16;
59
60/// Whether `F_GETOWN_EX`'s answer, owner type `kind` and pid `pid`, names an
61/// owner the guest could receive a signal from.
62///
63/// A nonzero pid is an owner. `f_getown_ex` reports pid 0 both for no owner
64/// and for an owner the reader's PID namespace cannot see (`pid_vnr`,
65/// `fs/fcntl.c:261-301`). An unseen process or thread is never a guest
66/// process, since every guest process is a descendant of the reader in its
67/// namespace. That holds only while the reader sees every guest process:
68/// Hermit's launch reads from the container's PID namespace, where every
69/// guest process lives, and Detcore reads from the caller's, which on the
70/// in-guest backends (the only ones that read this) is that same namespace,
71/// since they admit no nested PID namespace (they refuse every `clone` but a
72/// plain fork, and Detcore refuses `unshare` and `setns` on every backend,
73/// `syscall_classification::is_mount_ns_admin_refused_syscall`). A backend that admitted one would have to read from the
74/// container's namespace instead. `f_getown_ex` also reports pid 0 when
75/// the owner's pid has no task of the owner's type (`pid_task`), and one case
76/// of that is a live owner: an `F_SETOWN` or `F_OWNER_PID` owner naming a
77/// thread that is not its process's leader, which `send_sigio` still
78/// signals. That needs a guest process with a second thread, and the
79/// in-guest backends refuse thread creation (`clone_args_are_plain_fork`), so
80/// no guest process has one: a second precondition beside the PID
81/// namespace's, and one a backend that admitted threads would have to meet
82/// another way. An unseen process group can still hold
83/// the guest, which keeps
84/// the group it starts in, so an owner of type `F_OWNER_PGRP` counts as an
85/// owner whatever its pid: that can only refuse more (the one ambiguous
86/// disarm is `F_SETOWN_EX` of a process group with pid 0).
87pub fn names_an_owner(kind: libc::c_int, pid: libc::c_int) -> bool {
88 pid != 0 || kind == F_OWNER_PGRP
89}
90
91/// The owner an owner-setting call names, as the kernel looks it up: `who`,
92/// a pid number, and whether it names a process group (`group`): a negative
93/// `F_SETOWN`, `FIOSETOWN` or `SIOCSPGRP` owner, or `F_OWNER_PGRP`.
94#[derive(Clone, Copy, Debug, PartialEq, Eq)]
95pub struct OwnerLookup {
96 /// The pid number the kernel looks up (`find_vpid`).
97 pub who: libc::c_int,
98 /// Whether the owner is a process group.
99 pub group: bool,
100}
101
102impl OwnerLookup {
103 /// The owner `f_setown` looks up for `owner`, the argument of `F_SETOWN`,
104 /// `FIOSETOWN` or `SIOCSPGRP`: a negative one names the process group
105 /// `-owner` (`fs/fcntl.c:162-170`). `owner` is neither 0 nor `INT_MIN`.
106 pub fn of_setown(owner: libc::c_int) -> Self {
107 Self {
108 who: owner.abs(),
109 group: owner < 0,
110 }
111 }
112}
113
114/// The line Hermit logs once a run's launch check has passed (D25: the
115/// guest's physical `RLIMIT_CPU`), kept until the run ends
116/// ([`defer_run_end_log`]).
117type RunEndLog = Box<dyn FnOnce() + Send>;
118
119static RUN_END_LOG: std::sync::Mutex<Option<RunEndLog>> = std::sync::Mutex::new(None);
120
121/// Keeps `log` to run once at the end of this process's run
122/// ([`emit_run_end_log`]): after the scheduler shuts down on the ordinary
123/// path, and just before any of the scheduler's stopping refusals exits the
124/// process, so the line is logged on every exit path, at the same point of
125/// each path in every run. A later call replaces an earlier one.
126pub fn defer_run_end_log(log: impl FnOnce() + Send + 'static) {
127 *RUN_END_LOG
128 .lock()
129 .unwrap_or_else(|poisoned| poisoned.into_inner()) = Some(Box::new(log));
130}
131
132/// Runs the line [`defer_run_end_log`] kept, if any, once.
133pub fn emit_run_end_log() {
134 let log = RUN_END_LOG
135 .lock()
136 .unwrap_or_else(|poisoned| poisoned.into_inner())
137 .take();
138 if let Some(log) = log {
139 log();
140 }
141}
142
143/// What a descriptor already arms toward a host-timed signal producer.
144#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
145pub struct ProducerState {
146 /// Its status flags show `O_ASYNC`.
147 pub o_async: bool,
148 /// It has an owner ([`names_an_owner`]).
149 pub owner: bool,
150 /// It is a socket.
151 pub socket: bool,
152}
153
154impl ProducerState {
155 /// Whether the descriptor is already a complete producer: an owner with
156 /// `O_ASYNC` (SIGIO) or on a socket (SIGURG).
157 pub fn is_complete(self) -> bool {
158 self.owner && (self.o_async || self.socket)
159 }
160}
161
162/// An operation on a descriptor that can complete a host-timed signal
163/// producer (D28). `call` names the call and command, as the WARN does.
164#[derive(Clone, Copy, Debug, PartialEq, Eq)]
165pub enum ProducerArming {
166 /// A nonzero owner by `F_SETOWN` or `F_SETOWN_EX`.
167 SetOwner(&'static str),
168 /// A nonzero owner by `FIOSETOWN` or `SIOCSPGRP`, which only a socket
169 /// handles (`sock_ioctl`, `net/socket.c:1284-1289`); on any other file
170 /// the call reaches no handler and fails with ENOTTY, arming nothing.
171 SetSocketOwner(&'static str),
172 /// `O_ASYNC` set: `F_SETFL`, `FIOASYNC`.
173 SetAsync(&'static str),
174 /// `F_SETLEASE` with `F_RDLCK` or `F_WRLCK`.
175 Lease,
176 /// `F_NOTIFY` with a nonzero mask.
177 Notify,
178}
179
180impl ProducerArming {
181 /// The call and command, as the WARN names them.
182 pub fn call(self) -> &'static str {
183 match self {
184 Self::SetOwner(call) | Self::SetSocketOwner(call) | Self::SetAsync(call) => call,
185 Self::Lease => "fcntl(F_SETLEASE)",
186 Self::Notify => "fcntl(F_NOTIFY)",
187 }
188 }
189
190 /// Whether this operation on a descriptor in `state` completes a
191 /// producer, so is refused: an owner for a socket or for a descriptor
192 /// with `O_ASYNC`, `O_ASYNC` for a descriptor with an owner, and every
193 /// lease or directory notification.
194 pub fn completes(self, state: ProducerState) -> bool {
195 match self {
196 Self::SetOwner(_) => state.socket || state.o_async,
197 Self::SetSocketOwner(_) => state.socket,
198 Self::SetAsync(_) => state.owner,
199 Self::Lease | Self::Notify => true,
200 }
201 }
202
203 /// What the kernel would then send, and why, as the WARN says it.
204 fn producer(self, state: ProducerState) -> &'static str {
205 match self {
206 Self::SetOwner(_) | Self::SetSocketOwner(_) if state.socket => {
207 "the descriptor is a socket, whose owner Linux sends SIGURG when out-of-band data \
208 arrives"
209 }
210 Self::SetOwner(_) | Self::SetSocketOwner(_) => {
211 "the descriptor has O_ASYNC, so Linux would send its owner SIGIO when it becomes \
212 ready"
213 }
214 Self::SetAsync(_) => {
215 "the descriptor has an owner, which Linux would send SIGIO when it becomes ready"
216 }
217 Self::Lease => {
218 "a lease makes the caller the owner, which Linux sends SIGIO when another process \
219 opens the file"
220 }
221 Self::Notify => {
222 "a directory notification makes the caller the owner, which Linux sends SIGIO when \
223 the directory changes"
224 }
225 }
226 }
227}
228
229/// The WARN for an operation that would complete a host-timed signal
230/// producer on descriptor `fd`, which Detcore refused with EPERM.
231pub(crate) fn producer_refused_warning(
232 arming: ProducerArming,
233 state: ProducerState,
234 fd: RawFd,
235 caller: i32,
236) -> String {
237 format!(
238 "HERMIT_HOST_TIMED_PRODUCER_REFUSED call={} fd={fd} caller={caller}: received EPERM and \
239 nothing was armed: {}, at a moment host timing decides. A named refusal, not Linux's \
240 behaviour (A5 step 3c, https://github.com/rrnewton/hermit/issues/4485).",
241 arming.call(),
242 arming.producer(state),
243 )
244}
245
246/// The WARN for a parent-death signal Detcore cannot hold, which it refused
247/// with EPERM: a stop signal, SIGALRM or a realtime signal.
248pub(crate) fn parent_death_signal_refused_warning(signum: i32, caller: i32) -> String {
249 format!(
250 "HERMIT_HOST_TIMED_PRODUCER_REFUSED call=prctl(PR_SET_PDEATHSIG) signum={signum} \
251 caller={caller}: received EPERM and nothing was armed: Detcore holds only a standard \
252 signal other than SIGKILL, SIGSTOP and SIGALRM, and the kernel would send any other when \
253 the parent thread exits, at a moment host timing decides. A named refusal, not Linux's \
254 behaviour (A5 step 3c, https://github.com/rrnewton/hermit/issues/4485)."
255 )
256}
257
258/// The WARN for a change to the CPU-time limit, which Detcore refused with
259/// EPERM as it does on every backend: the limit Detcore reports is virtual,
260/// and a physical one would send SIGXCPU at a moment host CPU time decides.
261pub(crate) fn rlimit_cpu_refused_warning(call: &str, caller: i32) -> String {
262 format!(
263 "HERMIT_HOST_TIMED_PRODUCER_REFUSED call={call}(RLIMIT_CPU) caller={caller}: received \
264 EPERM and nothing was changed: the CPU-time limit is virtual and stays unlimited, \
265 because a physical one would send SIGXCPU at a moment host CPU time decides \
266 (A5 step 3c, https://github.com/rrnewton/hermit/issues/4485)."
267 )
268}
269
270/// The state of descriptor `fd` of the calling process, read from the host,
271/// with whether it holds a lease (`F_GETLEASE`): what Hermit checks before an
272/// in-guest launch in each descriptor the guest would inherit (D25). An
273/// `O_PATH` descriptor arms nothing (`check_fcntl_cmd` refuses every
274/// arming command on one), so it reads as arming nothing. A directory
275/// notification cannot be read back (there is no `F_GETNOTIFY`, and fdinfo
276/// does not show dnotify), so it is not reported.
277pub fn host_descriptor_state(fd: RawFd) -> Result<(ProducerState, bool), Errno> {
278 // SAFETY: fcntl and fstat on a descriptor number write only the
279 // structures passed to them.
280 let flags = Errno::result(unsafe { libc::fcntl(fd, libc::F_GETFL) })?;
281 if flags & libc::O_PATH != 0 {
282 return Ok((ProducerState::default(), false));
283 }
284 let mut owner = [0 as libc::c_int; 2];
285 Errno::result(unsafe { libc::fcntl(fd, F_GETOWN_EX, owner.as_mut_ptr()) })?;
286 let mut stat: libc::stat = unsafe { std::mem::zeroed() };
287 Errno::result(unsafe { libc::fstat(fd, &mut stat) })?;
288 let lease = Errno::result(unsafe { libc::fcntl(fd, libc::F_GETLEASE) })?;
289 Ok((
290 ProducerState {
291 o_async: flags & libc::O_ASYNC != 0,
292 owner: names_an_owner(owner[0], owner[1]),
293 socket: stat.st_mode & libc::S_IFMT == libc::S_IFSOCK,
294 },
295 lease != libc::F_UNLCK,
296 ))
297}
298
299#[cfg(test)]
300mod tests {
301 use std::os::fd::AsRawFd;
302
303 use super::*;
304
305 const ALL_STATES: [ProducerState; 8] = {
306 let mut states = [ProducerState {
307 o_async: false,
308 owner: false,
309 socket: false,
310 }; 8];
311 let mut i = 0;
312 while i < 8 {
313 states[i] = ProducerState {
314 o_async: i & 1 != 0,
315 owner: i & 2 != 0,
316 socket: i & 4 != 0,
317 };
318 i += 1;
319 }
320 states
321 };
322
323 /// A pid names an owner; pid 0 does only for a process group, which an
324 /// unseen group's owner reads as.
325 #[test]
326 fn an_owner_is_a_pid_or_a_process_group() {
327 for kind in [F_OWNER_TID, F_OWNER_PID, F_OWNER_PGRP] {
328 assert!(names_an_owner(kind, 7), "{kind}");
329 assert!(names_an_owner(kind, -7), "{kind}");
330 }
331 assert!(!names_an_owner(F_OWNER_TID, 0));
332 assert!(!names_an_owner(F_OWNER_PID, 0));
333 assert!(names_an_owner(F_OWNER_PGRP, 0));
334 }
335
336 /// Each refused operation (D28): an owner on a socket or on an O_ASYNC
337 /// descriptor, O_ASYNC on a descriptor with an owner, every lease and
338 /// notification. Each allowed one: an owner on a descriptor that is
339 /// neither, and O_ASYNC with no owner.
340 #[test]
341 fn only_the_operation_that_completes_a_producer_is_refused() {
342 for state in ALL_STATES {
343 let owner = ProducerArming::SetOwner("fcntl(F_SETOWN)");
344 assert_eq!(
345 owner.completes(state),
346 state.socket || state.o_async,
347 "{state:?}"
348 );
349 let socket_owner = ProducerArming::SetSocketOwner("ioctl(FIOSETOWN)");
350 assert_eq!(socket_owner.completes(state), state.socket, "{state:?}");
351 let o_async = ProducerArming::SetAsync("fcntl(F_SETFL)");
352 assert_eq!(o_async.completes(state), state.owner, "{state:?}");
353 assert!(ProducerArming::Lease.completes(state), "{state:?}");
354 assert!(ProducerArming::Notify.completes(state), "{state:?}");
355 }
356 // The cases the brief names, one by one.
357 let pipe = ProducerState::default();
358 let socket = ProducerState {
359 socket: true,
360 ..pipe
361 };
362 let o_async = ProducerState {
363 o_async: true,
364 ..pipe
365 };
366 let owned = ProducerState {
367 owner: true,
368 ..pipe
369 };
370 let set_owner = ProducerArming::SetOwner("fcntl(F_SETOWN)");
371 let set_async = ProducerArming::SetAsync("fcntl(F_SETFL)");
372 assert!(!set_owner.completes(pipe), "an owner on a plain pipe");
373 assert!(set_owner.completes(socket), "an owner on a socket");
374 assert!(set_owner.completes(o_async), "an owner with O_ASYNC");
375 assert!(!set_async.completes(pipe), "O_ASYNC with no owner");
376 assert!(!set_async.completes(socket), "O_ASYNC on an unowned socket");
377 assert!(set_async.completes(owned), "O_ASYNC with an owner");
378 let set_socket_owner = ProducerArming::SetSocketOwner("ioctl(SIOCSPGRP)");
379 assert!(set_socket_owner.completes(socket), "SIOCSPGRP on a socket");
380 assert!(
381 !set_socket_owner.completes(o_async),
382 "SIOCSPGRP on an O_ASYNC pipe is ENOTTY"
383 );
384 }
385
386 /// A complete producer (D25): an owner with O_ASYNC or on a socket.
387 #[test]
388 fn a_complete_producer_has_an_owner_and_a_way_to_signal_it() {
389 for state in ALL_STATES {
390 assert_eq!(
391 state.is_complete(),
392 state.owner && (state.o_async || state.socket),
393 "{state:?}"
394 );
395 }
396 }
397
398 /// `F_SETOWN`'s owner names a process when positive and the process
399 /// group `-owner` when negative, as `f_setown` looks it up.
400 #[test]
401 fn a_negative_setown_owner_is_a_process_group() {
402 assert_eq!(
403 OwnerLookup::of_setown(7),
404 OwnerLookup {
405 who: 7,
406 group: false
407 }
408 );
409 assert_eq!(
410 OwnerLookup::of_setown(-7),
411 OwnerLookup {
412 who: 7,
413 group: true
414 }
415 );
416 assert_eq!(
417 OwnerLookup::of_setown(libc::c_int::MAX).who,
418 libc::c_int::MAX
419 );
420 assert_eq!(
421 OwnerLookup::of_setown(-libc::c_int::MAX).who,
422 libc::c_int::MAX
423 );
424 }
425
426 /// The WARNs name the call, the descriptor or signal, EPERM and the
427 /// issue, and never a backend.
428 #[test]
429 fn the_warnings_name_the_call_and_the_descriptor() {
430 let socket = ProducerState {
431 socket: true,
432 ..ProducerState::default()
433 };
434 let text = producer_refused_warning(
435 ProducerArming::SetSocketOwner("ioctl(SIOCSPGRP)"),
436 socket,
437 5,
438 3,
439 );
440 assert!(
441 text.starts_with(
442 "HERMIT_HOST_TIMED_PRODUCER_REFUSED call=ioctl(SIOCSPGRP) fd=5 caller=3: \
443 received EPERM"
444 ),
445 "{text}"
446 );
447 assert!(text.contains("SIGURG"), "{text}");
448 let lease = producer_refused_warning(ProducerArming::Lease, socket, 4, 3);
449 assert!(lease.contains("call=fcntl(F_SETLEASE) fd=4"), "{lease}");
450 let notify = producer_refused_warning(ProducerArming::Notify, socket, 4, 3);
451 assert!(notify.contains("call=fcntl(F_NOTIFY) fd=4"), "{notify}");
452 let pdeath = parent_death_signal_refused_warning(libc::SIGSTOP, 3);
453 assert!(
454 pdeath.starts_with(
455 "HERMIT_HOST_TIMED_PRODUCER_REFUSED call=prctl(PR_SET_PDEATHSIG) signum=19 \
456 caller=3: received EPERM"
457 ),
458 "{pdeath}"
459 );
460 let cpu = rlimit_cpu_refused_warning("setrlimit", 3);
461 assert!(
462 cpu.starts_with(
463 "HERMIT_HOST_TIMED_PRODUCER_REFUSED call=setrlimit(RLIMIT_CPU) caller=3: \
464 received EPERM"
465 ),
466 "{cpu}"
467 );
468 for text in [&text, &lease, ¬ify, &pdeath, &cpu] {
469 assert!(
470 text.contains("https://github.com/rrnewton/hermit/issues/4485"),
471 "{text}"
472 );
473 for backend in ["liteinst", "LiteInst", "in-guest-trap", "ptrace"] {
474 assert!(!text.contains(backend), "{text}");
475 }
476 }
477 }
478
479 /// The host read sees what this process set: a pipe with an owner and
480 /// O_ASYNC is complete, the same pipe without O_ASYNC is not, a socket
481 /// with an owner is, and an O_PATH descriptor arms nothing.
482 #[test]
483 fn the_host_read_sees_owner_flags_and_sockets() {
484 let (read, write) = nix::unistd::pipe().unwrap();
485 let fd = read.as_raw_fd();
486 let (state, lease) = host_descriptor_state(fd).unwrap();
487 assert_eq!(state, ProducerState::default());
488 assert!(!lease);
489 // SAFETY: plain fcntl calls on a descriptor this test owns.
490 let pid = unsafe { libc::getpid() };
491 assert_eq!(unsafe { libc::fcntl(fd, libc::F_SETOWN, pid) }, 0);
492 let (state, _) = host_descriptor_state(fd).unwrap();
493 assert!(state.owner && !state.o_async && !state.socket, "{state:?}");
494 assert!(!state.is_complete());
495 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
496 assert_eq!(
497 unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_ASYNC) },
498 0
499 );
500 let (state, _) = host_descriptor_state(fd).unwrap();
501 assert!(state.is_complete(), "{state:?}");
502 // Disarm before the pipe could ever be written.
503 assert_eq!(unsafe { libc::fcntl(fd, libc::F_SETFL, flags) }, 0);
504 assert_eq!(unsafe { libc::fcntl(fd, libc::F_SETOWN, 0) }, 0);
505 assert_eq!(
506 host_descriptor_state(fd).unwrap().0,
507 ProducerState::default()
508 );
509 drop(write);
510
511 let (left, _right) = nix::sys::socket::socketpair(
512 nix::sys::socket::AddressFamily::Unix,
513 nix::sys::socket::SockType::Stream,
514 None,
515 nix::sys::socket::SockFlag::SOCK_CLOEXEC,
516 )
517 .unwrap();
518 let fd = left.as_raw_fd();
519 assert_eq!(
520 host_descriptor_state(fd).unwrap().0,
521 ProducerState {
522 socket: true,
523 ..ProducerState::default()
524 }
525 );
526 assert_eq!(unsafe { libc::fcntl(fd, libc::F_SETOWN, pid) }, 0);
527 assert!(host_descriptor_state(fd).unwrap().0.is_complete());
528 assert_eq!(unsafe { libc::fcntl(fd, libc::F_SETOWN, 0) }, 0);
529
530 let path = std::ffi::CString::new("/").unwrap();
531 let o_path = unsafe { libc::open(path.as_ptr(), libc::O_PATH | libc::O_CLOEXEC) };
532 assert!(o_path >= 0);
533 assert_eq!(
534 host_descriptor_state(o_path).unwrap(),
535 (ProducerState::default(), false)
536 );
537 unsafe { libc::close(o_path) };
538 }
539}