Skip to main content

pidfd_util/
lowlevel.rs

1// SPDX-FileCopyrightText: 2026 The pidfd-util-rs authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4// TODO:
5// - split out name_to_handle_at into new crate
6
7use std::alloc::{Layout, alloc_zeroed, dealloc};
8use std::io;
9use std::os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd};
10use std::os::unix::ffi::OsStrExt;
11#[cfg(not(feature = "nightly"))]
12use std::os::unix::process::ExitStatusExt;
13#[cfg(not(feature = "nightly"))]
14use std::process::ExitStatus;
15use std::sync::atomic;
16
17const PID_FS_MAGIC: i64 = 0x50494446;
18
19#[repr(u8)]
20#[derive(PartialEq)]
21enum Supported {
22    Unknown = 0,
23    Yes = 1,
24    No = 2,
25}
26
27struct AtomicSupported(atomic::AtomicU8);
28
29impl AtomicSupported {
30    const fn new(supported: Supported) -> Self {
31        Self(atomic::AtomicU8::new(supported as u8))
32    }
33
34    fn load(&self) -> Supported {
35        match self.0.load(atomic::Ordering::Relaxed) {
36            0 => Supported::Unknown,
37            1 => Supported::Yes,
38            2 => Supported::No,
39            _ => panic!(),
40        }
41    }
42
43    fn store(&self, supported: Supported) {
44        self.0.store(supported as u8, atomic::Ordering::Relaxed);
45    }
46}
47
48trait IsMinusOne {
49    fn is_minus_one(&self) -> bool;
50}
51
52macro_rules! impl_is_minus_one {
53    ($($t:ident)*) => ($(impl IsMinusOne for $t {
54        fn is_minus_one(&self) -> bool {
55            *self == -1
56        }
57    })*)
58}
59
60impl_is_minus_one! { i8 i16 i32 i64 isize }
61
62fn cvt<T: IsMinusOne>(t: T) -> io::Result<T> {
63    if t.is_minus_one() {
64        Err(io::Error::last_os_error())
65    } else {
66        Ok(t)
67    }
68}
69
70fn ioctl_unsupported(e: nix::Error) -> nix::Error {
71    match e {
72        nix::Error::EOPNOTSUPP
73        | nix::Error::ENOTTY
74        | nix::Error::ENOSYS
75        | nix::Error::EAFNOSUPPORT
76        | nix::Error::EPFNOSUPPORT
77        | nix::Error::EPROTONOSUPPORT
78        | nix::Error::ESOCKTNOSUPPORT
79        | nix::Error::ENOPROTOOPT => nix::Error::EOPNOTSUPP,
80        e => e,
81    }
82}
83
84const PIDFS_IOCTL_MAGIC: u8 = 0xFF;
85
86const PIDFS_IOCTL_GET_CGROUP_NAMESPACE: u8 = 1;
87const PIDFS_IOCTL_GET_IPC_NAMESPACE: u8 = 2;
88const PIDFS_IOCTL_GET_MNT_NAMESPACE: u8 = 3;
89const PIDFS_IOCTL_GET_NET_NAMESPACE: u8 = 4;
90const PIDFS_IOCTL_GET_PID_NAMESPACE: u8 = 5;
91const PIDFS_IOCTL_GET_PID_FOR_CHILDREN_NAMESPACE: u8 = 6;
92const PIDFS_IOCTL_GET_TIME_NAMESPACE: u8 = 7;
93const PIDFS_IOCTL_GET_TIME_FOR_CHILDREN_NAMESPACE: u8 = 8;
94const PIDFS_IOCTL_GET_USER_NAMESPACE: u8 = 9;
95const PIDFS_IOCTL_GET_UTS_NAMESPACE: u8 = 10;
96const PIDFS_IOCTL_GET_INFO: u8 = 11;
97
98/// Linux namespace types that can be queried from a pidfd.
99///
100/// Used with [`PidFdExt::get_namespace`](crate::PidFdExt::get_namespace) to obtain
101/// a file descriptor to a specific namespace of a process.
102#[non_exhaustive]
103pub enum PidFdGetNamespace {
104    /// Control group namespace
105    Cgroup,
106    /// IPC namespace (System V IPC, POSIX message queues)
107    Ipc,
108    /// Mount namespace (filesystem mount points)
109    Mnt,
110    /// Network namespace (network devices, stacks, ports, etc.)
111    Net,
112    /// PID namespace
113    Pid,
114    /// PID namespace for child processes
115    PidForChildren,
116    /// Time namespace
117    Time,
118    /// Time namespace for child processes
119    TimeForChildren,
120    /// User namespace (user and group IDs)
121    User,
122    /// UTS namespace (hostname and NIS domain name)
123    Uts,
124}
125
126impl PidFdGetNamespace {
127    fn as_ioctl(&self) -> u8 {
128        match self {
129            PidFdGetNamespace::Cgroup => PIDFS_IOCTL_GET_CGROUP_NAMESPACE,
130            PidFdGetNamespace::Ipc => PIDFS_IOCTL_GET_IPC_NAMESPACE,
131            PidFdGetNamespace::Mnt => PIDFS_IOCTL_GET_MNT_NAMESPACE,
132            PidFdGetNamespace::Net => PIDFS_IOCTL_GET_NET_NAMESPACE,
133            PidFdGetNamespace::Pid => PIDFS_IOCTL_GET_PID_NAMESPACE,
134            PidFdGetNamespace::PidForChildren => PIDFS_IOCTL_GET_PID_FOR_CHILDREN_NAMESPACE,
135            PidFdGetNamespace::Time => PIDFS_IOCTL_GET_TIME_NAMESPACE,
136            PidFdGetNamespace::TimeForChildren => PIDFS_IOCTL_GET_TIME_FOR_CHILDREN_NAMESPACE,
137            PidFdGetNamespace::User => PIDFS_IOCTL_GET_USER_NAMESPACE,
138            PidFdGetNamespace::Uts => PIDFS_IOCTL_GET_UTS_NAMESPACE,
139        }
140    }
141}
142
143pub fn pidfd_get_namespace<Fd: AsFd>(pidfd: &Fd, ns: &PidFdGetNamespace) -> io::Result<OwnedFd> {
144    // SAFETY:
145    // The arguments of the ioctl depend on the ioctl number and the fd.
146    // The fd wrapped in a Pidfd is always a pidfd fd.
147    // The ioctl number is guarantteed to be as implemented by PidFdGetNamespace::as_ioctl.
148    // All those ioctl numbers have the same scheme and do not take any other argument.
149    // The result is either -1 with errno, or a valid fd.
150    unsafe {
151        let fd = cvt(libc::ioctl(
152            pidfd.as_fd().as_raw_fd(),
153            nix::request_code_none!(PIDFS_IOCTL_MAGIC, ns.as_ioctl()),
154        ))?;
155        Ok(OwnedFd::from_raw_fd(fd))
156    }
157}
158
159#[non_exhaustive]
160struct PidfdInfoFlags;
161
162impl PidfdInfoFlags {
163    /* Always returned, even if not requested */
164    pub const PID: u64 = 1 << 0;
165    /* Always returned, even if not requested */
166    #[allow(dead_code)]
167    pub const CREDS: u64 = 1 << 1;
168    /* Always returned if available, even if not requested */
169    #[allow(dead_code)]
170    pub const CGROUPID: u64 = 1 << 2;
171    /* Only returned if requested. */
172    #[allow(dead_code)]
173    pub const EXIT: u64 = 1 << 3;
174}
175
176#[derive(Debug, Default)]
177#[repr(C)]
178struct PidfdInfo {
179    mask: u64,
180    cgroupid: u64,
181    pid: u32,
182    tgid: u32,
183    ppid: u32,
184    ruid: u32,
185    rgid: u32,
186    euid: u32,
187    egid: u32,
188    suid: u32,
189    sgid: u32,
190    fsuid: u32,
191    fsgid: u32,
192    exit_code: i32,
193}
194
195nix::ioctl_readwrite!(
196    pidfd_get_info_ioctl,
197    PIDFS_IOCTL_MAGIC,
198    PIDFS_IOCTL_GET_INFO,
199    PidfdInfo
200);
201
202fn pidfd_get_info<Fd: AsFd>(pidfd: &Fd, flags: u64) -> io::Result<PidfdInfo> {
203    assert_eq!(64, std::mem::size_of::<PidfdInfo>());
204
205    static SUPPORTED: AtomicSupported = AtomicSupported::new(Supported::Unknown);
206
207    let supported = SUPPORTED.load();
208    if supported == Supported::No {
209        return Err(io::ErrorKind::Unsupported.into());
210    }
211
212    let mut info = PidfdInfo {
213        mask: flags,
214        ..Default::default()
215    };
216
217    // SAFETY:
218    // nix::ioctl_readwrite defines pidfd_get_info_ioctl.
219    // The fd wrapped in a Pidfd is always a pidfd fd.
220    // The GET_INFO ioctl takes a `struct pidfd_info` as argument, which is mirrored in `struct PidfdInfo`.
221    let r = unsafe { pidfd_get_info_ioctl(pidfd.as_fd().as_raw_fd(), &raw mut info) }
222        .map_err(ioctl_unsupported);
223
224    if let Err(e) = r {
225        if e == nix::Error::EOPNOTSUPP {
226            SUPPORTED.store(Supported::No);
227        }
228        return Err(io::Error::from_raw_os_error(e as i32));
229    } else if supported == Supported::Unknown {
230        SUPPORTED.store(Supported::Yes);
231    }
232
233    assert!(info.mask & flags == flags);
234    Ok(info)
235}
236
237pub fn pidfd_open(pid: libc::pid_t) -> io::Result<OwnedFd> {
238    // SAFETY:
239    // The pidfd_open syscall takes arguments which are mirrored here.
240    // The syscall handles arbitrary values of pid.
241    // The other arguments are static and valid.
242    // The result is either -1 with errno, or a valid fd.
243    unsafe {
244        let fd = cvt(libc::syscall(
245            libc::SYS_pidfd_open,
246            pid as libc::pid_t,
247            0 as libc::c_uint,
248        ))?;
249        Ok(OwnedFd::from_raw_fd(fd as libc::c_int))
250    }
251}
252
253pub fn pidfd_send_signal<Fd: AsFd>(pidfd: &Fd, signal: libc::c_int) -> io::Result<()> {
254    // SAFETY:
255    // The pidfd_send_signal syscall takes arguments which are mirrored here.
256    // The fd wrapped in a Pidfd is always a pidfd fd.
257    // The syscall handles arbitrary values of signal.
258    // The other arguments are static and valid.
259    cvt(unsafe {
260        libc::syscall(
261            libc::SYS_pidfd_send_signal,
262            pidfd.as_fd().as_raw_fd() as libc::c_int,
263            signal as libc::c_int,
264            std::ptr::null::<()>() as *const libc::siginfo_t,
265            0 as libc::c_uint,
266        )
267    })
268    .map(drop)
269}
270
271#[cfg(not(feature = "nightly"))]
272fn from_waitid_siginfo(siginfo: libc::siginfo_t) -> ExitStatus {
273    // SAFETY:
274    // FIXME
275    let status = unsafe { siginfo.si_status() };
276
277    match siginfo.si_code {
278        libc::CLD_EXITED => ExitStatus::from_raw((status & 0xff) << 8),
279        libc::CLD_KILLED => ExitStatus::from_raw(status),
280        libc::CLD_DUMPED => ExitStatus::from_raw(status | 0x80),
281        libc::CLD_CONTINUED => ExitStatus::from_raw(0xffff),
282        libc::CLD_STOPPED | libc::CLD_TRAPPED => {
283            ExitStatus::from_raw(((status & 0xff) << 8) | 0x7f)
284        }
285        _ => unreachable!("waitid() should only return the above codes"),
286    }
287}
288
289#[cfg(not(feature = "nightly"))]
290pub fn pidfd_wait<Fd: AsFd>(pidfd: &Fd) -> io::Result<ExitStatus> {
291    // SAFETY:
292    // FIXME I think all-zero byte-pattern is valid for libc::siginfo_t
293    let mut siginfo: libc::siginfo_t = unsafe { std::mem::zeroed() };
294
295    // SAFETY:
296    // FIXME not entirely sure what the safeye guarantees should be here, but...
297    // The libc::P_PIDFD constant tells means the second param must be a pidfd fd.
298    // The fd wrapped in a Pidfd is always a pidfd fd.
299    // The siginfo parameter is a zeroed struct which will be filled by the syscall.
300    // WEXITED is a valid constant for the last parameter.
301    cvt(unsafe {
302        libc::waitid(
303            libc::P_PIDFD,
304            pidfd.as_fd().as_raw_fd() as u32,
305            &raw mut siginfo,
306            libc::WEXITED,
307        )
308    })?;
309    Ok(from_waitid_siginfo(siginfo))
310}
311
312#[cfg(not(feature = "nightly"))]
313pub fn pidfd_try_wait<Fd: AsFd>(pidfd: &Fd) -> io::Result<Option<ExitStatus>> {
314    // SAFETY:
315    // FIXME I think all-zero byte-pattern is valid for libc::siginfo_t
316    let mut siginfo: libc::siginfo_t = unsafe { std::mem::zeroed() };
317
318    // SAFETY:
319    // FIXME not entirely sure what the safeye guarantees should be here, but...
320    // The libc::P_PIDFD constant tells means the second param must be a pidfd fd.
321    // The fd wrapped in a Pidfd is always a pidfd fd.
322    // The siginfo parameter is a zeroed struct which will be filled by the syscall.
323    // WEXITED|WNOHANG is a valid constant for the last parameter.
324    cvt(unsafe {
325        libc::waitid(
326            libc::P_PIDFD,
327            pidfd.as_fd().as_raw_fd() as u32,
328            &mut siginfo,
329            libc::WEXITED | libc::WNOHANG,
330        )
331    })?;
332
333    // SAFETY:
334    // The siginfo parameter was a zeroed struct which we made sure got successfully filled by the syscall.
335    if unsafe { siginfo.si_pid() } == 0 {
336        Ok(None)
337    } else {
338        Ok(Some(from_waitid_siginfo(siginfo)))
339    }
340}
341
342fn pidfd_get_pid_fdinfo<Fd: AsFd>(pidfd: &Fd) -> io::Result<i32> {
343    use std::fs::read_to_string;
344
345    let raw = pidfd.as_fd().as_raw_fd();
346    let fdinfo = read_to_string(format!("/proc/self/fdinfo/{raw}"))?;
347    let pidline = fdinfo
348        .split('\n')
349        .find(|s| s.starts_with("Pid:"))
350        .ok_or(io::ErrorKind::Unsupported)?;
351    Ok(pidline
352        .split('\t')
353        .next_back()
354        .ok_or(io::ErrorKind::Unsupported)?
355        .parse::<i32>()
356        .map_err(|_| io::ErrorKind::Unsupported)?)
357}
358
359pub fn pidfd_get_pid<Fd: AsFd>(pidfd: &Fd) -> io::Result<i32> {
360    match pidfd_get_info(pidfd, PidfdInfoFlags::PID) {
361        Ok(info) => Ok(info.pid as i32),
362        Err(e) if e.kind() == io::ErrorKind::Unsupported => pidfd_get_pid_fdinfo(pidfd),
363        Err(e) => Err(e),
364    }
365}
366
367pub fn pidfd_get_ppid<Fd: AsFd>(pidfd: &Fd) -> io::Result<i32> {
368    pidfd_get_info(pidfd, PidfdInfoFlags::PID).map(|info| info.ppid as i32)
369}
370
371/// Process credential information.
372///
373/// Contains the various user and group IDs associated with a process.
374/// Obtained via [`PidFdExt::get_creds`](crate::PidFdExt::get_creds).
375///
376#[derive(Debug, Clone, Copy, PartialEq, Eq)]
377pub struct PidFdCreds {
378    /// Real user ID
379    pub ruid: u32,
380    /// Real group ID
381    pub rgid: u32,
382    /// Effective user ID (used for permission checks)
383    pub euid: u32,
384    /// Effective group ID (used for permission checks)
385    pub egid: u32,
386    /// Saved user ID
387    pub suid: u32,
388    /// Saved group ID
389    pub sgid: u32,
390    /// Filesystem user ID (used for filesystem operations)
391    pub fsuid: u32,
392    /// Filesystem group ID (used for filesystem operations)
393    pub fsgid: u32,
394}
395
396pub fn pidfd_get_creds<Fd: AsFd>(pidfd: &Fd) -> io::Result<PidFdCreds> {
397    pidfd_get_info(pidfd, PidfdInfoFlags::CREDS).map(|info| PidFdCreds {
398        ruid: info.ruid,
399        rgid: info.rgid,
400        euid: info.euid,
401        egid: info.egid,
402        suid: info.suid,
403        sgid: info.sgid,
404        fsuid: info.fsuid,
405        fsgid: info.fsgid,
406    })
407}
408
409pub fn pidfd_get_cgroupid<Fd: AsFd>(pidfd: &Fd) -> io::Result<u64> {
410    pidfd_get_info(pidfd, PidfdInfoFlags::PID).map(|info| info.cgroupid)
411}
412
413pub fn pidfd_is_on_pidfs() -> io::Result<bool> {
414    use nix::sys::statfs::fstatfs;
415
416    static SUPPORTED: AtomicSupported = AtomicSupported::new(Supported::Unknown);
417
418    match SUPPORTED.load() {
419        Supported::Unknown => (),
420        Supported::Yes => return Ok(true),
421        Supported::No => return Ok(false),
422    }
423
424    let self_pidfd = pidfd_open(std::process::id().try_into().unwrap())?;
425
426    let fsstat = fstatfs(self_pidfd)?;
427    if fsstat.filesystem_type().0 == PID_FS_MAGIC {
428        SUPPORTED.store(Supported::Yes);
429        Ok(true)
430    } else {
431        SUPPORTED.store(Supported::No);
432        Ok(false)
433    }
434}
435
436#[allow(dead_code)]
437pub struct FileHandle {
438    pub mount_id: i32,
439    pub handle_type: i32,
440    pub handle: Vec<u8>,
441}
442
443pub fn name_to_handle_at<Fd: AsFd>(
444    fd: &Fd,
445    path: &std::path::Path,
446    flags: i32,
447) -> io::Result<FileHandle> {
448    #[repr(C)]
449    #[derive(Default)]
450    pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
451
452    #[repr(C)]
453    #[derive(Default)]
454    struct file_handle {
455        handle_bytes: libc::c_uint,
456        handle_type: libc::c_int,
457        f_handle: __IncompleteArrayField<libc::c_uchar>,
458    }
459
460    static SUPPORTED: AtomicSupported = AtomicSupported::new(Supported::Unknown);
461
462    let supported = SUPPORTED.load();
463    if supported == Supported::No {
464        return Err(io::ErrorKind::Unsupported.into());
465    }
466
467    let mut handle = file_handle::default();
468    let mut mount_id = 0;
469    let mut path = path.as_os_str().as_bytes().to_owned();
470    path.push(0);
471
472    // SAFETY:
473    // The name_to_handle_at syscall takes arguments which are mirrored here.
474    // The fd wrapped in a Pidfd is always a pidfd fd.
475    // The path is a valid, empty, zero-terminated path.
476    // The handle mirrors `struct file_handle` and is zero-initialized, this means file_handle.handle_bytes is also zero, so the allocated size for handle is correct.
477    // The mount_id is a valid pointer to an int.
478    // The syscall handles arbitrary values of flags.
479    #[allow(clippy::unnecessary_cast)]
480    let err = cvt(unsafe {
481        libc::syscall(
482            libc::SYS_name_to_handle_at,
483            fd.as_fd().as_raw_fd() as libc::c_int,
484            path.as_ptr() as *const libc::c_char,
485            &raw mut handle as *mut file_handle,
486            &raw mut mount_id as *mut libc::c_int,
487            flags,
488        ) as libc::c_int
489    })
490    .unwrap_err();
491
492    if err.raw_os_error().unwrap() == libc::EOPNOTSUPP {
493        SUPPORTED.store(Supported::No);
494    } else if supported == Supported::Unknown {
495        SUPPORTED.store(Supported::Yes);
496    }
497
498    if err.raw_os_error().unwrap() != libc::EOVERFLOW || handle.handle_bytes == 0 {
499        return Err(err);
500    }
501
502    loop {
503        let layout = Layout::new::<file_handle>();
504        let buf_layout =
505            Layout::array::<libc::c_uchar>(handle.handle_bytes.try_into().unwrap()).unwrap();
506        let (layout, buf_offset) = layout.extend(buf_layout).unwrap();
507        let layout = layout.pad_to_align();
508
509        // SAFETY:
510        // Layout has non-zero size because file_handle has non-zero size
511        let buf = unsafe { alloc_zeroed(layout) };
512        // SAFETY:
513        // Constructing a Box from the newly allocated, zeroed memory is valid
514        let mut new_handle: Box<file_handle> = unsafe { Box::from_raw(buf as _) };
515        new_handle.handle_bytes = handle.handle_bytes;
516        new_handle.handle_type = handle.handle_type;
517
518        // SAFETY:
519        // Same as the previous name_to_handle_at syscall, except...
520        // new_handle.handle_bytes is bigger than zero, so the memory allocated for new_handle must be bigger by that amount.
521        // The code above ensures we allocated the right size.
522        #[allow(clippy::unnecessary_cast)]
523        let res = cvt(unsafe {
524            libc::syscall(
525                libc::SYS_name_to_handle_at,
526                fd.as_fd().as_raw_fd() as libc::c_int,
527                path.as_ptr() as *const libc::c_char,
528                &raw mut *new_handle as *mut file_handle,
529                &raw mut mount_id as *mut libc::c_int,
530                flags,
531            ) as libc::c_int
532        });
533
534        handle.handle_bytes = new_handle.handle_bytes;
535        handle.handle_type = new_handle.handle_type;
536        // We leak this because the memory belongs to buf
537        Box::leak(new_handle);
538
539        match res {
540            Err(e) if e.raw_os_error().unwrap() == libc::EOVERFLOW => (),
541            Err(e) => {
542                // SAFETY:
543                // buf and layout are still valid, and we must deallocate the memory to avoid memory leaks
544                unsafe { dealloc(buf, layout) };
545                return Err(e);
546            }
547            Ok(_) => {
548                let h = {
549                    // SAFETY:
550                    // buf_offset was created from the layout to point at the char[].
551                    // We allocated enough size for handle_bytes via the layout.
552                    // The [u8] is thus properly aligned and non-zero.
553                    // f_handle goes out of scope before we deallocate.
554                    let f_handle = unsafe {
555                        std::slice::from_raw_parts(
556                            buf.offset(buf_offset.try_into().unwrap()),
557                            handle.handle_bytes.try_into().unwrap(),
558                        )
559                    };
560
561                    FileHandle {
562                        mount_id,
563                        handle_type: handle.handle_type,
564                        handle: f_handle.to_vec(),
565                    }
566                };
567
568                // SAFETY:
569                // buf and layout are still valid, and we must deallocate the memory to avoid memory leaks
570                unsafe { dealloc(buf, layout) };
571
572                return Ok(h);
573            }
574        }
575    }
576}
577
578pub fn pidfd_get_inode_id<Fd: AsFd>(pidfd: &Fd) -> io::Result<u64> {
579    use nix::sys::stat::fstat;
580
581    if !pidfd_is_on_pidfs()? {
582        return Err(io::ErrorKind::Unsupported.into());
583    }
584
585    match name_to_handle_at(
586        &pidfd.as_fd(),
587        std::path::Path::new(""),
588        libc::AT_EMPTY_PATH,
589    ) {
590        Err(e) if e.kind() == io::ErrorKind::Unsupported => (),
591        Err(e) => return Err(e),
592        Ok(h) => return Ok(u64::from_ne_bytes(h.handle.try_into().unwrap())),
593    }
594
595    let stat = fstat(pidfd)?;
596
597    // stat.st_ino can be 4 bytes in 32 bit systems
598    if std::mem::size_of_val(&stat.st_ino) != 8 {
599        return Err(io::ErrorKind::Unsupported.into());
600    }
601
602    Ok(stat.st_ino)
603}
604
605pub fn pidfd_getfd<Fd: AsFd>(pidfd: &Fd, targetfd: i32) -> io::Result<OwnedFd> {
606    // SAFETY:
607    // The pidfd_getfd syscall takes arguments which are mirrored here.
608    // The fd wrapped in a Pidfd is always a pidfd fd.
609    // The syscall handles arbitrary values of targetfd.
610    // The other arguments are static and valid.
611    // The result is either -1 with errno, or a valid fd.
612    unsafe {
613        let fd = cvt(libc::syscall(
614            libc::SYS_pidfd_getfd,
615            pidfd.as_fd().as_raw_fd() as libc::c_int,
616            targetfd as libc::c_int,
617            0 as libc::c_uint,
618        ) as libc::c_int)?;
619        Ok(OwnedFd::from_raw_fd(fd as libc::c_int))
620    }
621}