Skip to main content

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, &notify, &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}