diff options
Diffstat (limited to 'third_party/rust/nix/src/sys')
37 files changed, 19932 insertions, 0 deletions
diff --git a/third_party/rust/nix/src/sys/aio.rs b/third_party/rust/nix/src/sys/aio.rs new file mode 100644 index 0000000000..e2ce19b79d --- /dev/null +++ b/third_party/rust/nix/src/sys/aio.rs @@ -0,0 +1,1241 @@ +// vim: tw=80 +//! POSIX Asynchronous I/O +//! +//! The POSIX AIO interface is used for asynchronous I/O on files and disk-like +//! devices. It supports [`read`](struct.AioRead.html#method.new), +//! [`write`](struct.AioWrite.html#method.new), +//! [`fsync`](struct.AioFsync.html#method.new), +//! [`readv`](struct.AioReadv.html#method.new), and +//! [`writev`](struct.AioWritev.html#method.new), operations, subject to +//! platform support. Completion +//! notifications can optionally be delivered via +//! [signals](../signal/enum.SigevNotify.html#variant.SigevSignal), via the +//! [`aio_suspend`](fn.aio_suspend.html) function, or via polling. Some +//! platforms support other completion +//! notifications, such as +//! [kevent](../signal/enum.SigevNotify.html#variant.SigevKevent). +//! +//! Multiple operations may be submitted in a batch with +//! [`lio_listio`](fn.lio_listio.html), though the standard does not guarantee +//! that they will be executed atomically. +//! +//! Outstanding operations may be cancelled with +//! [`cancel`](trait.Aio.html#method.cancel) or +//! [`aio_cancel_all`](fn.aio_cancel_all.html), though the operating system may +//! not support this for all filesystems and devices. +#[cfg(target_os = "freebsd")] +use std::io::{IoSlice, IoSliceMut}; +use std::{ + convert::TryFrom, + fmt::{self, Debug}, + marker::{PhantomData, PhantomPinned}, + mem, + os::unix::io::RawFd, + pin::Pin, + ptr, thread, +}; + +use libc::{c_void, off_t}; +use pin_utils::unsafe_pinned; + +use crate::{ + errno::Errno, + sys::{signal::*, time::TimeSpec}, + Result, +}; + +libc_enum! { + /// Mode for `AioCb::fsync`. Controls whether only data or both data and + /// metadata are synced. + #[repr(i32)] + #[non_exhaustive] + pub enum AioFsyncMode { + /// do it like `fsync` + O_SYNC, + /// on supported operating systems only, do it like `fdatasync` + #[cfg(any(target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_DSYNC + } + impl TryFrom<i32> +} + +libc_enum! { + /// Mode for [`lio_listio`](fn.lio_listio.html) + #[repr(i32)] + pub enum LioMode { + /// Requests that [`lio_listio`](fn.lio_listio.html) block until all + /// requested operations have been completed + LIO_WAIT, + /// Requests that [`lio_listio`](fn.lio_listio.html) return immediately + LIO_NOWAIT, + } +} + +/// Return values for [`AioCb::cancel`](struct.AioCb.html#method.cancel) and +/// [`aio_cancel_all`](fn.aio_cancel_all.html) +#[repr(i32)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum AioCancelStat { + /// All outstanding requests were canceled + AioCanceled = libc::AIO_CANCELED, + /// Some requests were not canceled. Their status should be checked with + /// `AioCb::error` + AioNotCanceled = libc::AIO_NOTCANCELED, + /// All of the requests have already finished + AioAllDone = libc::AIO_ALLDONE, +} + +/// Newtype that adds Send and Sync to libc::aiocb, which contains raw pointers +#[repr(transparent)] +struct LibcAiocb(libc::aiocb); + +unsafe impl Send for LibcAiocb {} +unsafe impl Sync for LibcAiocb {} + +/// Base class for all AIO operations. Should only be used directly when +/// checking for completion. +// We could create some kind of AsPinnedMut trait, and implement it for all aio +// ops, allowing the crate's users to get pinned references to `AioCb`. That +// could save some code for things like polling methods. But IMHO it would +// provide polymorphism at the wrong level. Instead, the best place for +// polymorphism is at the level of `Futures`. +#[repr(C)] +struct AioCb { + aiocb: LibcAiocb, + /// Could this `AioCb` potentially have any in-kernel state? + // It would be really nice to perform the in-progress check entirely at + // compile time. But I can't figure out how, because: + // * Future::poll takes a `Pin<&mut self>` rather than `self`, and + // * Rust's lack of an equivalent of C++'s Guaranteed Copy Elision means + // that there's no way to write an AioCb constructor that neither boxes + // the object itself, nor moves it during return. + in_progress: bool, +} + +impl AioCb { + pin_utils::unsafe_unpinned!(aiocb: LibcAiocb); + + fn aio_return(mut self: Pin<&mut Self>) -> Result<usize> { + self.in_progress = false; + unsafe { + let p: *mut libc::aiocb = &mut self.aiocb.0; + Errno::result(libc::aio_return(p)) + } + .map(|r| r as usize) + } + + fn cancel(mut self: Pin<&mut Self>) -> Result<AioCancelStat> { + let r = unsafe { + libc::aio_cancel(self.aiocb.0.aio_fildes, &mut self.aiocb.0) + }; + match r { + libc::AIO_CANCELED => Ok(AioCancelStat::AioCanceled), + libc::AIO_NOTCANCELED => Ok(AioCancelStat::AioNotCanceled), + libc::AIO_ALLDONE => Ok(AioCancelStat::AioAllDone), + -1 => Err(Errno::last()), + _ => panic!("unknown aio_cancel return value"), + } + } + + fn common_init(fd: RawFd, prio: i32, sigev_notify: SigevNotify) -> Self { + // Use mem::zeroed instead of explicitly zeroing each field, because the + // number and name of reserved fields is OS-dependent. On some OSes, + // some reserved fields are used the kernel for state, and must be + // explicitly zeroed when allocated. + let mut a = unsafe { mem::zeroed::<libc::aiocb>() }; + a.aio_fildes = fd; + a.aio_reqprio = prio; + a.aio_sigevent = SigEvent::new(sigev_notify).sigevent(); + AioCb { + aiocb: LibcAiocb(a), + in_progress: false, + } + } + + fn error(self: Pin<&mut Self>) -> Result<()> { + let r = unsafe { libc::aio_error(&self.aiocb().0) }; + match r { + 0 => Ok(()), + num if num > 0 => Err(Errno::from_i32(num)), + -1 => Err(Errno::last()), + num => panic!("unknown aio_error return value {:?}", num), + } + } + + fn in_progress(&self) -> bool { + self.in_progress + } + + fn set_in_progress(mut self: Pin<&mut Self>) { + self.as_mut().in_progress = true; + } + + /// Update the notification settings for an existing AIO operation that has + /// not yet been submitted. + // Takes a normal reference rather than a pinned one because this method is + // normally called before the object needs to be pinned, that is, before + // it's been submitted to the kernel. + fn set_sigev_notify(&mut self, sigev_notify: SigevNotify) { + assert!( + !self.in_progress, + "Can't change notification settings for an in-progress operation" + ); + self.aiocb.0.aio_sigevent = SigEvent::new(sigev_notify).sigevent(); + } +} + +impl Debug for AioCb { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("AioCb") + .field("aiocb", &self.aiocb.0) + .field("in_progress", &self.in_progress) + .finish() + } +} + +impl Drop for AioCb { + /// If the `AioCb` has no remaining state in the kernel, just drop it. + /// Otherwise, dropping constitutes a resource leak, which is an error + fn drop(&mut self) { + assert!( + thread::panicking() || !self.in_progress, + "Dropped an in-progress AioCb" + ); + } +} + +/// Methods common to all AIO operations +pub trait Aio { + /// The return type of [`Aio::aio_return`]. + type Output; + + /// Retrieve return status of an asynchronous operation. + /// + /// Should only be called once for each operation, after [`Aio::error`] + /// indicates that it has completed. The result is the same as for the + /// synchronous `read(2)`, `write(2)`, of `fsync(2)` functions. + /// + /// # References + /// + /// [aio_return](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_return.html) + fn aio_return(self: Pin<&mut Self>) -> Result<Self::Output>; + + /// Cancels an outstanding AIO request. + /// + /// The operating system is not required to implement cancellation for all + /// file and device types. Even if it does, there is no guarantee that the + /// operation has not already completed. So the caller must check the + /// result and handle operations that were not canceled or that have already + /// completed. + /// + /// # Examples + /// + /// Cancel an outstanding aio operation. Note that we must still call + /// `aio_return` to free resources, even though we don't care about the + /// result. + /// + /// ``` + /// # use nix::errno::Errno; + /// # use nix::Error; + /// # use nix::sys::aio::*; + /// # use nix::sys::signal::SigevNotify; + /// # use std::{thread, time}; + /// # use std::io::Write; + /// # use std::os::unix::io::AsRawFd; + /// # use tempfile::tempfile; + /// let wbuf = b"CDEF"; + /// let mut f = tempfile().unwrap(); + /// let mut aiocb = Box::pin(AioWrite::new(f.as_raw_fd(), + /// 2, //offset + /// &wbuf[..], + /// 0, //priority + /// SigevNotify::SigevNone)); + /// aiocb.as_mut().submit().unwrap(); + /// let cs = aiocb.as_mut().cancel().unwrap(); + /// if cs == AioCancelStat::AioNotCanceled { + /// while (aiocb.as_mut().error() == Err(Errno::EINPROGRESS)) { + /// thread::sleep(time::Duration::from_millis(10)); + /// } + /// } + /// // Must call `aio_return`, but ignore the result + /// let _ = aiocb.as_mut().aio_return(); + /// ``` + /// + /// # References + /// + /// [aio_cancel](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_cancel.html) + fn cancel(self: Pin<&mut Self>) -> Result<AioCancelStat>; + + /// Retrieve error status of an asynchronous operation. + /// + /// If the request has not yet completed, returns `EINPROGRESS`. Otherwise, + /// returns `Ok` or any other error. + /// + /// # Examples + /// + /// Issue an aio operation and use `error` to poll for completion. Polling + /// is an alternative to `aio_suspend`, used by most of the other examples. + /// + /// ``` + /// # use nix::errno::Errno; + /// # use nix::Error; + /// # use nix::sys::aio::*; + /// # use nix::sys::signal::SigevNotify; + /// # use std::{thread, time}; + /// # use std::os::unix::io::AsRawFd; + /// # use tempfile::tempfile; + /// const WBUF: &[u8] = b"abcdef123456"; + /// let mut f = tempfile().unwrap(); + /// let mut aiocb = Box::pin(AioWrite::new(f.as_raw_fd(), + /// 2, //offset + /// WBUF, + /// 0, //priority + /// SigevNotify::SigevNone)); + /// aiocb.as_mut().submit().unwrap(); + /// while (aiocb.as_mut().error() == Err(Errno::EINPROGRESS)) { + /// thread::sleep(time::Duration::from_millis(10)); + /// } + /// assert_eq!(aiocb.as_mut().aio_return().unwrap(), WBUF.len()); + /// ``` + /// + /// # References + /// + /// [aio_error](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_error.html) + fn error(self: Pin<&mut Self>) -> Result<()>; + + /// Returns the underlying file descriptor associated with the operation. + fn fd(&self) -> RawFd; + + /// Does this operation currently have any in-kernel state? + /// + /// Dropping an operation that does have in-kernel state constitutes a + /// resource leak. + /// + /// # Examples + /// + /// ``` + /// # use nix::errno::Errno; + /// # use nix::Error; + /// # use nix::sys::aio::*; + /// # use nix::sys::signal::SigevNotify::SigevNone; + /// # use std::{thread, time}; + /// # use std::os::unix::io::AsRawFd; + /// # use tempfile::tempfile; + /// let f = tempfile().unwrap(); + /// let mut aiof = Box::pin(AioFsync::new(f.as_raw_fd(), AioFsyncMode::O_SYNC, + /// 0, SigevNone)); + /// assert!(!aiof.as_mut().in_progress()); + /// aiof.as_mut().submit().expect("aio_fsync failed early"); + /// assert!(aiof.as_mut().in_progress()); + /// while (aiof.as_mut().error() == Err(Errno::EINPROGRESS)) { + /// thread::sleep(time::Duration::from_millis(10)); + /// } + /// aiof.as_mut().aio_return().expect("aio_fsync failed late"); + /// assert!(!aiof.as_mut().in_progress()); + /// ``` + fn in_progress(&self) -> bool; + + /// Returns the priority of the `AioCb` + fn priority(&self) -> i32; + + /// Update the notification settings for an existing AIO operation that has + /// not yet been submitted. + fn set_sigev_notify(&mut self, sev: SigevNotify); + + /// Returns the `SigEvent` that will be used for notification. + fn sigevent(&self) -> SigEvent; + + /// Actually start the I/O operation. + /// + /// After calling this method and until [`Aio::aio_return`] returns `Ok`, + /// the structure may not be moved in memory. + fn submit(self: Pin<&mut Self>) -> Result<()>; +} + +macro_rules! aio_methods { + () => { + fn cancel(self: Pin<&mut Self>) -> Result<AioCancelStat> { + self.aiocb().cancel() + } + + fn error(self: Pin<&mut Self>) -> Result<()> { + self.aiocb().error() + } + + fn fd(&self) -> RawFd { + self.aiocb.aiocb.0.aio_fildes + } + + fn in_progress(&self) -> bool { + self.aiocb.in_progress() + } + + fn priority(&self) -> i32 { + self.aiocb.aiocb.0.aio_reqprio + } + + fn set_sigev_notify(&mut self, sev: SigevNotify) { + self.aiocb.set_sigev_notify(sev) + } + + fn sigevent(&self) -> SigEvent { + SigEvent::from(&self.aiocb.aiocb.0.aio_sigevent) + } + }; + ($func:ident) => { + aio_methods!(); + + fn aio_return(self: Pin<&mut Self>) -> Result<<Self as Aio>::Output> { + self.aiocb().aio_return() + } + + fn submit(mut self: Pin<&mut Self>) -> Result<()> { + let p: *mut libc::aiocb = &mut self.as_mut().aiocb().aiocb.0; + Errno::result({ unsafe { libc::$func(p) } }).map(|_| { + self.aiocb().set_in_progress(); + }) + } + }; +} + +/// An asynchronous version of `fsync(2)`. +/// +/// # References +/// +/// [aio_fsync](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_fsync.html) +/// # Examples +/// +/// ``` +/// # use nix::errno::Errno; +/// # use nix::Error; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify::SigevNone; +/// # use std::{thread, time}; +/// # use std::os::unix::io::AsRawFd; +/// # use tempfile::tempfile; +/// let f = tempfile().unwrap(); +/// let mut aiof = Box::pin(AioFsync::new(f.as_raw_fd(), AioFsyncMode::O_SYNC, +/// 0, SigevNone)); +/// aiof.as_mut().submit().expect("aio_fsync failed early"); +/// while (aiof.as_mut().error() == Err(Errno::EINPROGRESS)) { +/// thread::sleep(time::Duration::from_millis(10)); +/// } +/// aiof.as_mut().aio_return().expect("aio_fsync failed late"); +/// ``` +#[derive(Debug)] +#[repr(transparent)] +pub struct AioFsync { + aiocb: AioCb, + _pin: PhantomPinned, +} + +impl AioFsync { + unsafe_pinned!(aiocb: AioCb); + + /// Returns the operation's fsync mode: data and metadata or data only? + pub fn mode(&self) -> AioFsyncMode { + AioFsyncMode::try_from(self.aiocb.aiocb.0.aio_lio_opcode).unwrap() + } + + /// Create a new `AioFsync`. + /// + /// # Arguments + /// + /// * `fd`: File descriptor to sync. + /// * `mode`: Whether to sync file metadata too, or just data. + /// * `prio`: If POSIX Prioritized IO is supported, then the + /// operation will be prioritized at the process's + /// priority level minus `prio`. + /// * `sigev_notify`: Determines how you will be notified of event + /// completion. + pub fn new( + fd: RawFd, + mode: AioFsyncMode, + prio: i32, + sigev_notify: SigevNotify, + ) -> Self { + let mut aiocb = AioCb::common_init(fd, prio, sigev_notify); + // To save some memory, store mode in an unused field of the AioCb. + // True it isn't very much memory, but downstream creates will likely + // create an enum containing this and other AioCb variants and pack + // those enums into data structures like Vec, so it adds up. + aiocb.aiocb.0.aio_lio_opcode = mode as libc::c_int; + AioFsync { + aiocb, + _pin: PhantomPinned, + } + } +} + +impl Aio for AioFsync { + type Output = (); + + aio_methods!(); + + fn aio_return(self: Pin<&mut Self>) -> Result<()> { + self.aiocb().aio_return().map(drop) + } + + fn submit(mut self: Pin<&mut Self>) -> Result<()> { + let aiocb = &mut self.as_mut().aiocb().aiocb.0; + let mode = mem::replace(&mut aiocb.aio_lio_opcode, 0); + let p: *mut libc::aiocb = aiocb; + Errno::result(unsafe { libc::aio_fsync(mode, p) }).map(|_| { + self.aiocb().set_in_progress(); + }) + } +} + +// AioFsync does not need AsMut, since it can't be used with lio_listio + +impl AsRef<libc::aiocb> for AioFsync { + fn as_ref(&self) -> &libc::aiocb { + &self.aiocb.aiocb.0 + } +} + +/// Asynchronously reads from a file descriptor into a buffer +/// +/// # References +/// +/// [aio_read](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_read.html) +/// +/// # Examples +/// +/// +/// ``` +/// # use nix::errno::Errno; +/// # use nix::Error; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use std::{thread, time}; +/// # use std::io::Write; +/// # use std::os::unix::io::AsRawFd; +/// # use tempfile::tempfile; +/// const INITIAL: &[u8] = b"abcdef123456"; +/// const LEN: usize = 4; +/// let mut rbuf = vec![0; LEN]; +/// let mut f = tempfile().unwrap(); +/// f.write_all(INITIAL).unwrap(); +/// { +/// let mut aior = Box::pin( +/// AioRead::new( +/// f.as_raw_fd(), +/// 2, //offset +/// &mut rbuf, +/// 0, //priority +/// SigevNotify::SigevNone +/// ) +/// ); +/// aior.as_mut().submit().unwrap(); +/// while (aior.as_mut().error() == Err(Errno::EINPROGRESS)) { +/// thread::sleep(time::Duration::from_millis(10)); +/// } +/// assert_eq!(aior.as_mut().aio_return().unwrap(), LEN); +/// } +/// assert_eq!(rbuf, b"cdef"); +/// ``` +#[derive(Debug)] +#[repr(transparent)] +pub struct AioRead<'a> { + aiocb: AioCb, + _data: PhantomData<&'a [u8]>, + _pin: PhantomPinned, +} + +impl<'a> AioRead<'a> { + unsafe_pinned!(aiocb: AioCb); + + /// Returns the requested length of the aio operation in bytes + /// + /// This method returns the *requested* length of the operation. To get the + /// number of bytes actually read or written by a completed operation, use + /// `aio_return` instead. + pub fn nbytes(&self) -> usize { + self.aiocb.aiocb.0.aio_nbytes + } + + /// Create a new `AioRead`, placing the data in a mutable slice. + /// + /// # Arguments + /// + /// * `fd`: File descriptor to read from + /// * `offs`: File offset + /// * `buf`: A memory buffer. It must outlive the `AioRead`. + /// * `prio`: If POSIX Prioritized IO is supported, then the + /// operation will be prioritized at the process's + /// priority level minus `prio` + /// * `sigev_notify`: Determines how you will be notified of event + /// completion. + pub fn new( + fd: RawFd, + offs: off_t, + buf: &'a mut [u8], + prio: i32, + sigev_notify: SigevNotify, + ) -> Self { + let mut aiocb = AioCb::common_init(fd, prio, sigev_notify); + aiocb.aiocb.0.aio_nbytes = buf.len(); + aiocb.aiocb.0.aio_buf = buf.as_mut_ptr() as *mut c_void; + aiocb.aiocb.0.aio_lio_opcode = libc::LIO_READ; + aiocb.aiocb.0.aio_offset = offs; + AioRead { + aiocb, + _data: PhantomData, + _pin: PhantomPinned, + } + } + + /// Returns the file offset of the operation. + pub fn offset(&self) -> off_t { + self.aiocb.aiocb.0.aio_offset + } +} + +impl<'a> Aio for AioRead<'a> { + type Output = usize; + + aio_methods!(aio_read); +} + +impl<'a> AsMut<libc::aiocb> for AioRead<'a> { + fn as_mut(&mut self) -> &mut libc::aiocb { + &mut self.aiocb.aiocb.0 + } +} + +impl<'a> AsRef<libc::aiocb> for AioRead<'a> { + fn as_ref(&self) -> &libc::aiocb { + &self.aiocb.aiocb.0 + } +} + +/// Asynchronously reads from a file descriptor into a scatter/gather list of buffers. +/// +/// # References +/// +/// [aio_readv](https://www.freebsd.org/cgi/man.cgi?query=aio_readv) +/// +/// # Examples +/// +/// +#[cfg_attr(fbsd14, doc = " ```")] +#[cfg_attr(not(fbsd14), doc = " ```no_run")] +/// # use nix::errno::Errno; +/// # use nix::Error; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use std::{thread, time}; +/// # use std::io::{IoSliceMut, Write}; +/// # use std::os::unix::io::AsRawFd; +/// # use tempfile::tempfile; +/// const INITIAL: &[u8] = b"abcdef123456"; +/// let mut rbuf0 = vec![0; 4]; +/// let mut rbuf1 = vec![0; 2]; +/// let expected_len = rbuf0.len() + rbuf1.len(); +/// let mut rbufs = [IoSliceMut::new(&mut rbuf0), IoSliceMut::new(&mut rbuf1)]; +/// let mut f = tempfile().unwrap(); +/// f.write_all(INITIAL).unwrap(); +/// { +/// let mut aior = Box::pin( +/// AioReadv::new( +/// f.as_raw_fd(), +/// 2, //offset +/// &mut rbufs, +/// 0, //priority +/// SigevNotify::SigevNone +/// ) +/// ); +/// aior.as_mut().submit().unwrap(); +/// while (aior.as_mut().error() == Err(Errno::EINPROGRESS)) { +/// thread::sleep(time::Duration::from_millis(10)); +/// } +/// assert_eq!(aior.as_mut().aio_return().unwrap(), expected_len); +/// } +/// assert_eq!(rbuf0, b"cdef"); +/// assert_eq!(rbuf1, b"12"); +/// ``` +#[cfg(target_os = "freebsd")] +#[derive(Debug)] +#[repr(transparent)] +pub struct AioReadv<'a> { + aiocb: AioCb, + _data: PhantomData<&'a [&'a [u8]]>, + _pin: PhantomPinned, +} + +#[cfg(target_os = "freebsd")] +impl<'a> AioReadv<'a> { + unsafe_pinned!(aiocb: AioCb); + + /// Returns the number of buffers the operation will read into. + pub fn iovlen(&self) -> usize { + self.aiocb.aiocb.0.aio_nbytes + } + + /// Create a new `AioReadv`, placing the data in a list of mutable slices. + /// + /// # Arguments + /// + /// * `fd`: File descriptor to read from + /// * `offs`: File offset + /// * `bufs`: A scatter/gather list of memory buffers. They must + /// outlive the `AioReadv`. + /// * `prio`: If POSIX Prioritized IO is supported, then the + /// operation will be prioritized at the process's + /// priority level minus `prio` + /// * `sigev_notify`: Determines how you will be notified of event + /// completion. + pub fn new( + fd: RawFd, + offs: off_t, + bufs: &mut [IoSliceMut<'a>], + prio: i32, + sigev_notify: SigevNotify, + ) -> Self { + let mut aiocb = AioCb::common_init(fd, prio, sigev_notify); + // In vectored mode, aio_nbytes stores the length of the iovec array, + // not the byte count. + aiocb.aiocb.0.aio_nbytes = bufs.len(); + aiocb.aiocb.0.aio_buf = bufs.as_mut_ptr() as *mut c_void; + aiocb.aiocb.0.aio_lio_opcode = libc::LIO_READV; + aiocb.aiocb.0.aio_offset = offs; + AioReadv { + aiocb, + _data: PhantomData, + _pin: PhantomPinned, + } + } + + /// Returns the file offset of the operation. + pub fn offset(&self) -> off_t { + self.aiocb.aiocb.0.aio_offset + } +} + +#[cfg(target_os = "freebsd")] +impl<'a> Aio for AioReadv<'a> { + type Output = usize; + + aio_methods!(aio_readv); +} + +#[cfg(target_os = "freebsd")] +impl<'a> AsMut<libc::aiocb> for AioReadv<'a> { + fn as_mut(&mut self) -> &mut libc::aiocb { + &mut self.aiocb.aiocb.0 + } +} + +#[cfg(target_os = "freebsd")] +impl<'a> AsRef<libc::aiocb> for AioReadv<'a> { + fn as_ref(&self) -> &libc::aiocb { + &self.aiocb.aiocb.0 + } +} + +/// Asynchronously writes from a buffer to a file descriptor +/// +/// # References +/// +/// [aio_write](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_write.html) +/// +/// # Examples +/// +/// ``` +/// # use nix::errno::Errno; +/// # use nix::Error; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use std::{thread, time}; +/// # use std::os::unix::io::AsRawFd; +/// # use tempfile::tempfile; +/// const WBUF: &[u8] = b"abcdef123456"; +/// let mut f = tempfile().unwrap(); +/// let mut aiow = Box::pin( +/// AioWrite::new( +/// f.as_raw_fd(), +/// 2, //offset +/// WBUF, +/// 0, //priority +/// SigevNotify::SigevNone +/// ) +/// ); +/// aiow.as_mut().submit().unwrap(); +/// while (aiow.as_mut().error() == Err(Errno::EINPROGRESS)) { +/// thread::sleep(time::Duration::from_millis(10)); +/// } +/// assert_eq!(aiow.as_mut().aio_return().unwrap(), WBUF.len()); +/// ``` +#[derive(Debug)] +#[repr(transparent)] +pub struct AioWrite<'a> { + aiocb: AioCb, + _data: PhantomData<&'a [u8]>, + _pin: PhantomPinned, +} + +impl<'a> AioWrite<'a> { + unsafe_pinned!(aiocb: AioCb); + + /// Returns the requested length of the aio operation in bytes + /// + /// This method returns the *requested* length of the operation. To get the + /// number of bytes actually read or written by a completed operation, use + /// `aio_return` instead. + pub fn nbytes(&self) -> usize { + self.aiocb.aiocb.0.aio_nbytes + } + + /// Construct a new `AioWrite`. + /// + /// # Arguments + /// + /// * `fd`: File descriptor to write to + /// * `offs`: File offset + /// * `buf`: A memory buffer. It must outlive the `AioWrite`. + /// * `prio`: If POSIX Prioritized IO is supported, then the + /// operation will be prioritized at the process's + /// priority level minus `prio` + /// * `sigev_notify`: Determines how you will be notified of event + /// completion. + pub fn new( + fd: RawFd, + offs: off_t, + buf: &'a [u8], + prio: i32, + sigev_notify: SigevNotify, + ) -> Self { + let mut aiocb = AioCb::common_init(fd, prio, sigev_notify); + aiocb.aiocb.0.aio_nbytes = buf.len(); + // casting an immutable buffer to a mutable pointer looks unsafe, + // but technically its only unsafe to dereference it, not to create + // it. Type Safety guarantees that we'll never pass aiocb to + // aio_read or aio_readv. + aiocb.aiocb.0.aio_buf = buf.as_ptr() as *mut c_void; + aiocb.aiocb.0.aio_lio_opcode = libc::LIO_WRITE; + aiocb.aiocb.0.aio_offset = offs; + AioWrite { + aiocb, + _data: PhantomData, + _pin: PhantomPinned, + } + } + + /// Returns the file offset of the operation. + pub fn offset(&self) -> off_t { + self.aiocb.aiocb.0.aio_offset + } +} + +impl<'a> Aio for AioWrite<'a> { + type Output = usize; + + aio_methods!(aio_write); +} + +impl<'a> AsMut<libc::aiocb> for AioWrite<'a> { + fn as_mut(&mut self) -> &mut libc::aiocb { + &mut self.aiocb.aiocb.0 + } +} + +impl<'a> AsRef<libc::aiocb> for AioWrite<'a> { + fn as_ref(&self) -> &libc::aiocb { + &self.aiocb.aiocb.0 + } +} + +/// Asynchronously writes from a scatter/gather list of buffers to a file descriptor. +/// +/// # References +/// +/// [aio_writev](https://www.freebsd.org/cgi/man.cgi?query=aio_writev) +/// +/// # Examples +/// +#[cfg_attr(fbsd14, doc = " ```")] +#[cfg_attr(not(fbsd14), doc = " ```no_run")] +/// # use nix::errno::Errno; +/// # use nix::Error; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use std::{thread, time}; +/// # use std::io::IoSlice; +/// # use std::os::unix::io::AsRawFd; +/// # use tempfile::tempfile; +/// const wbuf0: &[u8] = b"abcdef"; +/// const wbuf1: &[u8] = b"123456"; +/// let len = wbuf0.len() + wbuf1.len(); +/// let wbufs = [IoSlice::new(wbuf0), IoSlice::new(wbuf1)]; +/// let mut f = tempfile().unwrap(); +/// let mut aiow = Box::pin( +/// AioWritev::new( +/// f.as_raw_fd(), +/// 2, //offset +/// &wbufs, +/// 0, //priority +/// SigevNotify::SigevNone +/// ) +/// ); +/// aiow.as_mut().submit().unwrap(); +/// while (aiow.as_mut().error() == Err(Errno::EINPROGRESS)) { +/// thread::sleep(time::Duration::from_millis(10)); +/// } +/// assert_eq!(aiow.as_mut().aio_return().unwrap(), len); +/// ``` +#[cfg(target_os = "freebsd")] +#[derive(Debug)] +#[repr(transparent)] +pub struct AioWritev<'a> { + aiocb: AioCb, + _data: PhantomData<&'a [&'a [u8]]>, + _pin: PhantomPinned, +} + +#[cfg(target_os = "freebsd")] +impl<'a> AioWritev<'a> { + unsafe_pinned!(aiocb: AioCb); + + /// Returns the number of buffers the operation will read into. + pub fn iovlen(&self) -> usize { + self.aiocb.aiocb.0.aio_nbytes + } + + /// Construct a new `AioWritev`. + /// + /// # Arguments + /// + /// * `fd`: File descriptor to write to + /// * `offs`: File offset + /// * `bufs`: A scatter/gather list of memory buffers. They must + /// outlive the `AioWritev`. + /// * `prio`: If POSIX Prioritized IO is supported, then the + /// operation will be prioritized at the process's + /// priority level minus `prio` + /// * `sigev_notify`: Determines how you will be notified of event + /// completion. + pub fn new( + fd: RawFd, + offs: off_t, + bufs: &[IoSlice<'a>], + prio: i32, + sigev_notify: SigevNotify, + ) -> Self { + let mut aiocb = AioCb::common_init(fd, prio, sigev_notify); + // In vectored mode, aio_nbytes stores the length of the iovec array, + // not the byte count. + aiocb.aiocb.0.aio_nbytes = bufs.len(); + // casting an immutable buffer to a mutable pointer looks unsafe, + // but technically its only unsafe to dereference it, not to create + // it. Type Safety guarantees that we'll never pass aiocb to + // aio_read or aio_readv. + aiocb.aiocb.0.aio_buf = bufs.as_ptr() as *mut c_void; + aiocb.aiocb.0.aio_lio_opcode = libc::LIO_WRITEV; + aiocb.aiocb.0.aio_offset = offs; + AioWritev { + aiocb, + _data: PhantomData, + _pin: PhantomPinned, + } + } + + /// Returns the file offset of the operation. + pub fn offset(&self) -> off_t { + self.aiocb.aiocb.0.aio_offset + } +} + +#[cfg(target_os = "freebsd")] +impl<'a> Aio for AioWritev<'a> { + type Output = usize; + + aio_methods!(aio_writev); +} + +#[cfg(target_os = "freebsd")] +impl<'a> AsMut<libc::aiocb> for AioWritev<'a> { + fn as_mut(&mut self) -> &mut libc::aiocb { + &mut self.aiocb.aiocb.0 + } +} + +#[cfg(target_os = "freebsd")] +impl<'a> AsRef<libc::aiocb> for AioWritev<'a> { + fn as_ref(&self) -> &libc::aiocb { + &self.aiocb.aiocb.0 + } +} + +/// Cancels outstanding AIO requests for a given file descriptor. +/// +/// # Examples +/// +/// Issue an aio operation, then cancel all outstanding operations on that file +/// descriptor. +/// +/// ``` +/// # use nix::errno::Errno; +/// # use nix::Error; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use std::{thread, time}; +/// # use std::io::Write; +/// # use std::os::unix::io::AsRawFd; +/// # use tempfile::tempfile; +/// let wbuf = b"CDEF"; +/// let mut f = tempfile().unwrap(); +/// let mut aiocb = Box::pin(AioWrite::new(f.as_raw_fd(), +/// 2, //offset +/// &wbuf[..], +/// 0, //priority +/// SigevNotify::SigevNone)); +/// aiocb.as_mut().submit().unwrap(); +/// let cs = aio_cancel_all(f.as_raw_fd()).unwrap(); +/// if cs == AioCancelStat::AioNotCanceled { +/// while (aiocb.as_mut().error() == Err(Errno::EINPROGRESS)) { +/// thread::sleep(time::Duration::from_millis(10)); +/// } +/// } +/// // Must call `aio_return`, but ignore the result +/// let _ = aiocb.as_mut().aio_return(); +/// ``` +/// +/// # References +/// +/// [`aio_cancel`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_cancel.html) +pub fn aio_cancel_all(fd: RawFd) -> Result<AioCancelStat> { + match unsafe { libc::aio_cancel(fd, ptr::null_mut()) } { + libc::AIO_CANCELED => Ok(AioCancelStat::AioCanceled), + libc::AIO_NOTCANCELED => Ok(AioCancelStat::AioNotCanceled), + libc::AIO_ALLDONE => Ok(AioCancelStat::AioAllDone), + -1 => Err(Errno::last()), + _ => panic!("unknown aio_cancel return value"), + } +} + +/// Suspends the calling process until at least one of the specified operations +/// have completed, a signal is delivered, or the timeout has passed. +/// +/// If `timeout` is `None`, `aio_suspend` will block indefinitely. +/// +/// # Examples +/// +/// Use `aio_suspend` to block until an aio operation completes. +/// +/// ``` +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use std::os::unix::io::AsRawFd; +/// # use tempfile::tempfile; +/// const WBUF: &[u8] = b"abcdef123456"; +/// let mut f = tempfile().unwrap(); +/// let mut aiocb = Box::pin(AioWrite::new(f.as_raw_fd(), +/// 2, //offset +/// WBUF, +/// 0, //priority +/// SigevNotify::SigevNone)); +/// aiocb.as_mut().submit().unwrap(); +/// aio_suspend(&[&*aiocb], None).expect("aio_suspend failed"); +/// assert_eq!(aiocb.as_mut().aio_return().unwrap() as usize, WBUF.len()); +/// ``` +/// # References +/// +/// [`aio_suspend`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_suspend.html) +pub fn aio_suspend( + list: &[&dyn AsRef<libc::aiocb>], + timeout: Option<TimeSpec>, +) -> Result<()> { + let p = list as *const [&dyn AsRef<libc::aiocb>] + as *const [*const libc::aiocb] as *const *const libc::aiocb; + let timep = match timeout { + None => ptr::null::<libc::timespec>(), + Some(x) => x.as_ref() as *const libc::timespec, + }; + Errno::result(unsafe { libc::aio_suspend(p, list.len() as i32, timep) }) + .map(drop) +} + +/// Submits multiple asynchronous I/O requests with a single system call. +/// +/// They are not guaranteed to complete atomically, and the order in which the +/// requests are carried out is not specified. Reads, and writes may be freely +/// mixed. +/// +/// # Examples +/// +/// Use `lio_listio` to submit an aio operation and wait for its completion. In +/// this case, there is no need to use aio_suspend to wait or `error` to poll. +/// This mode is useful for otherwise-synchronous programs that want to execute +/// a handful of I/O operations in parallel. +/// ``` +/// # use std::os::unix::io::AsRawFd; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use tempfile::tempfile; +/// const WBUF: &[u8] = b"abcdef123456"; +/// let mut f = tempfile().unwrap(); +/// let mut aiow = Box::pin(AioWrite::new( +/// f.as_raw_fd(), +/// 2, // offset +/// WBUF, +/// 0, // priority +/// SigevNotify::SigevNone +/// )); +/// lio_listio(LioMode::LIO_WAIT, &mut[aiow.as_mut()], SigevNotify::SigevNone) +/// .unwrap(); +/// // At this point, we are guaranteed that aiow is complete. +/// assert_eq!(aiow.as_mut().aio_return().unwrap(), WBUF.len()); +/// ``` +/// +/// Use `lio_listio` to submit multiple asynchronous operations with a single +/// syscall, but receive notification individually. This is an efficient +/// technique for reducing overall context-switch overhead, especially when +/// combined with kqueue. +/// ``` +/// # use std::os::unix::io::AsRawFd; +/// # use std::thread; +/// # use std::time; +/// # use nix::errno::Errno; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use tempfile::tempfile; +/// const WBUF: &[u8] = b"abcdef123456"; +/// let mut f = tempfile().unwrap(); +/// let mut aiow = Box::pin(AioWrite::new( +/// f.as_raw_fd(), +/// 2, // offset +/// WBUF, +/// 0, // priority +/// SigevNotify::SigevNone +/// )); +/// lio_listio(LioMode::LIO_NOWAIT, &mut[aiow.as_mut()], SigevNotify::SigevNone) +/// .unwrap(); +/// // We must wait for the completion of each individual operation +/// while (aiow.as_mut().error() == Err(Errno::EINPROGRESS)) { +/// thread::sleep(time::Duration::from_millis(10)); +/// } +/// assert_eq!(aiow.as_mut().aio_return().unwrap(), WBUF.len()); +/// ``` +/// +/// Use `lio_listio` to submit multiple operations, and receive notification +/// only when all of them are complete. This can be useful when there is some +/// logical relationship between the operations. But beware! Errors or system +/// resource limitations may cause `lio_listio` to return `EIO`, `EAGAIN`, or +/// `EINTR`, in which case some but not all operations may have been submitted. +/// In that case, you must check the status of each individual operation, and +/// possibly resubmit some. +/// ``` +/// # use libc::c_int; +/// # use std::os::unix::io::AsRawFd; +/// # use std::sync::atomic::{AtomicBool, Ordering}; +/// # use std::thread; +/// # use std::time; +/// # use lazy_static::lazy_static; +/// # use nix::errno::Errno; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::*; +/// # use tempfile::tempfile; +/// lazy_static! { +/// pub static ref SIGNALED: AtomicBool = AtomicBool::new(false); +/// } +/// +/// extern fn sigfunc(_: c_int) { +/// SIGNALED.store(true, Ordering::Relaxed); +/// } +/// let sa = SigAction::new(SigHandler::Handler(sigfunc), +/// SaFlags::SA_RESETHAND, +/// SigSet::empty()); +/// SIGNALED.store(false, Ordering::Relaxed); +/// unsafe { sigaction(Signal::SIGUSR2, &sa) }.unwrap(); +/// +/// const WBUF: &[u8] = b"abcdef123456"; +/// let mut f = tempfile().unwrap(); +/// let mut aiow = Box::pin(AioWrite::new( +/// f.as_raw_fd(), +/// 2, // offset +/// WBUF, +/// 0, // priority +/// SigevNotify::SigevNone +/// )); +/// let sev = SigevNotify::SigevSignal { signal: Signal::SIGUSR2, si_value: 0 }; +/// lio_listio(LioMode::LIO_NOWAIT, &mut[aiow.as_mut()], sev).unwrap(); +/// while !SIGNALED.load(Ordering::Relaxed) { +/// thread::sleep(time::Duration::from_millis(10)); +/// } +/// // At this point, since `lio_listio` returned success and delivered its +/// // notification, we know that all operations are complete. +/// assert_eq!(aiow.as_mut().aio_return().unwrap(), WBUF.len()); +/// ``` +pub fn lio_listio( + mode: LioMode, + list: &mut [Pin<&mut dyn AsMut<libc::aiocb>>], + sigev_notify: SigevNotify, +) -> Result<()> { + let p = list as *mut [Pin<&mut dyn AsMut<libc::aiocb>>] + as *mut [*mut libc::aiocb] as *mut *mut libc::aiocb; + let sigev = SigEvent::new(sigev_notify); + let sigevp = &mut sigev.sigevent() as *mut libc::sigevent; + Errno::result(unsafe { + libc::lio_listio(mode as i32, p, list.len() as i32, sigevp) + }) + .map(drop) +} + +#[cfg(test)] +mod t { + use super::*; + + /// aio_suspend relies on casting Rust Aio* struct pointers to libc::aiocb + /// pointers. This test ensures that such casts are valid. + #[test] + fn casting() { + let sev = SigevNotify::SigevNone; + let aiof = AioFsync::new(666, AioFsyncMode::O_SYNC, 0, sev); + assert_eq!( + aiof.as_ref() as *const libc::aiocb, + &aiof as *const AioFsync as *const libc::aiocb + ); + + let mut rbuf = []; + let aior = AioRead::new(666, 0, &mut rbuf, 0, sev); + assert_eq!( + aior.as_ref() as *const libc::aiocb, + &aior as *const AioRead as *const libc::aiocb + ); + + let wbuf = []; + let aiow = AioWrite::new(666, 0, &wbuf, 0, sev); + assert_eq!( + aiow.as_ref() as *const libc::aiocb, + &aiow as *const AioWrite as *const libc::aiocb + ); + } + + #[cfg(target_os = "freebsd")] + #[test] + fn casting_vectored() { + let sev = SigevNotify::SigevNone; + + let mut rbuf = []; + let mut rbufs = [IoSliceMut::new(&mut rbuf)]; + let aiorv = AioReadv::new(666, 0, &mut rbufs[..], 0, sev); + assert_eq!( + aiorv.as_ref() as *const libc::aiocb, + &aiorv as *const AioReadv as *const libc::aiocb + ); + + let wbuf = []; + let wbufs = [IoSlice::new(&wbuf)]; + let aiowv = AioWritev::new(666, 0, &wbufs, 0, sev); + assert_eq!( + aiowv.as_ref() as *const libc::aiocb, + &aiowv as *const AioWritev as *const libc::aiocb + ); + } +} diff --git a/third_party/rust/nix/src/sys/epoll.rs b/third_party/rust/nix/src/sys/epoll.rs new file mode 100644 index 0000000000..58def2e788 --- /dev/null +++ b/third_party/rust/nix/src/sys/epoll.rs @@ -0,0 +1,128 @@ +use crate::errno::Errno; +use crate::Result; +use libc::{self, c_int}; +use std::mem; +use std::os::unix::io::RawFd; +use std::ptr; + +libc_bitflags!( + pub struct EpollFlags: c_int { + EPOLLIN; + EPOLLPRI; + EPOLLOUT; + EPOLLRDNORM; + EPOLLRDBAND; + EPOLLWRNORM; + EPOLLWRBAND; + EPOLLMSG; + EPOLLERR; + EPOLLHUP; + EPOLLRDHUP; + EPOLLEXCLUSIVE; + #[cfg(not(target_arch = "mips"))] + EPOLLWAKEUP; + EPOLLONESHOT; + EPOLLET; + } +); + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(i32)] +#[non_exhaustive] +pub enum EpollOp { + EpollCtlAdd = libc::EPOLL_CTL_ADD, + EpollCtlDel = libc::EPOLL_CTL_DEL, + EpollCtlMod = libc::EPOLL_CTL_MOD, +} + +libc_bitflags! { + pub struct EpollCreateFlags: c_int { + EPOLL_CLOEXEC; + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(transparent)] +pub struct EpollEvent { + event: libc::epoll_event, +} + +impl EpollEvent { + pub fn new(events: EpollFlags, data: u64) -> Self { + EpollEvent { + event: libc::epoll_event { + events: events.bits() as u32, + u64: data, + }, + } + } + + pub fn empty() -> Self { + unsafe { mem::zeroed::<EpollEvent>() } + } + + pub fn events(&self) -> EpollFlags { + EpollFlags::from_bits(self.event.events as c_int).unwrap() + } + + pub fn data(&self) -> u64 { + self.event.u64 + } +} + +#[inline] +pub fn epoll_create() -> Result<RawFd> { + let res = unsafe { libc::epoll_create(1024) }; + + Errno::result(res) +} + +#[inline] +pub fn epoll_create1(flags: EpollCreateFlags) -> Result<RawFd> { + let res = unsafe { libc::epoll_create1(flags.bits()) }; + + Errno::result(res) +} + +#[inline] +pub fn epoll_ctl<'a, T>( + epfd: RawFd, + op: EpollOp, + fd: RawFd, + event: T, +) -> Result<()> +where + T: Into<Option<&'a mut EpollEvent>>, +{ + let mut event: Option<&mut EpollEvent> = event.into(); + if event.is_none() && op != EpollOp::EpollCtlDel { + Err(Errno::EINVAL) + } else { + let res = unsafe { + if let Some(ref mut event) = event { + libc::epoll_ctl(epfd, op as c_int, fd, &mut event.event) + } else { + libc::epoll_ctl(epfd, op as c_int, fd, ptr::null_mut()) + } + }; + Errno::result(res).map(drop) + } +} + +#[inline] +pub fn epoll_wait( + epfd: RawFd, + events: &mut [EpollEvent], + timeout_ms: isize, +) -> Result<usize> { + let res = unsafe { + libc::epoll_wait( + epfd, + events.as_mut_ptr() as *mut libc::epoll_event, + events.len() as c_int, + timeout_ms as c_int, + ) + }; + + Errno::result(res).map(|r| r as usize) +} diff --git a/third_party/rust/nix/src/sys/event.rs b/third_party/rust/nix/src/sys/event.rs new file mode 100644 index 0000000000..d8ad628ea2 --- /dev/null +++ b/third_party/rust/nix/src/sys/event.rs @@ -0,0 +1,374 @@ +/* TOOD: Implement for other kqueue based systems + */ + +use crate::{Errno, Result}; +#[cfg(not(target_os = "netbsd"))] +use libc::{c_int, c_long, intptr_t, time_t, timespec, uintptr_t}; +#[cfg(target_os = "netbsd")] +use libc::{c_long, intptr_t, size_t, time_t, timespec, uintptr_t}; +use std::convert::TryInto; +use std::mem; +use std::os::unix::io::RawFd; +use std::ptr; + +// Redefine kevent in terms of programmer-friendly enums and bitfields. +#[repr(C)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct KEvent { + kevent: libc::kevent, +} + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "openbsd" +))] +type type_of_udata = *mut libc::c_void; +#[cfg(any(target_os = "netbsd"))] +type type_of_udata = intptr_t; + +#[cfg(target_os = "netbsd")] +type type_of_event_filter = u32; +#[cfg(not(target_os = "netbsd"))] +type type_of_event_filter = i16; +libc_enum! { + #[cfg_attr(target_os = "netbsd", repr(u32))] + #[cfg_attr(not(target_os = "netbsd"), repr(i16))] + #[non_exhaustive] + pub enum EventFilter { + EVFILT_AIO, + /// Returns whenever there is no remaining data in the write buffer + #[cfg(target_os = "freebsd")] + EVFILT_EMPTY, + #[cfg(target_os = "dragonfly")] + EVFILT_EXCEPT, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos"))] + EVFILT_FS, + #[cfg(target_os = "freebsd")] + EVFILT_LIO, + #[cfg(any(target_os = "ios", target_os = "macos"))] + EVFILT_MACHPORT, + EVFILT_PROC, + /// Returns events associated with the process referenced by a given + /// process descriptor, created by `pdfork()`. The events to monitor are: + /// + /// - NOTE_EXIT: the process has exited. The exit status will be stored in data. + #[cfg(target_os = "freebsd")] + EVFILT_PROCDESC, + EVFILT_READ, + /// Returns whenever an asynchronous `sendfile()` call completes. + #[cfg(target_os = "freebsd")] + EVFILT_SENDFILE, + EVFILT_SIGNAL, + EVFILT_TIMER, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos"))] + EVFILT_USER, + #[cfg(any(target_os = "ios", target_os = "macos"))] + EVFILT_VM, + EVFILT_VNODE, + EVFILT_WRITE, + } + impl TryFrom<type_of_event_filter> +} + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "openbsd" +))] +pub type type_of_event_flag = u16; +#[cfg(any(target_os = "netbsd"))] +pub type type_of_event_flag = u32; +libc_bitflags! { + pub struct EventFlag: type_of_event_flag { + EV_ADD; + EV_CLEAR; + EV_DELETE; + EV_DISABLE; + #[cfg(any(target_os = "dragonfly", target_os = "freebsd", + target_os = "ios", target_os = "macos", + target_os = "netbsd", target_os = "openbsd"))] + EV_DISPATCH; + #[cfg(target_os = "freebsd")] + EV_DROP; + EV_ENABLE; + EV_EOF; + EV_ERROR; + #[cfg(any(target_os = "macos", target_os = "ios"))] + EV_FLAG0; + EV_FLAG1; + #[cfg(target_os = "dragonfly")] + EV_NODATA; + EV_ONESHOT; + #[cfg(any(target_os = "macos", target_os = "ios"))] + EV_OOBAND; + #[cfg(any(target_os = "macos", target_os = "ios"))] + EV_POLL; + #[cfg(any(target_os = "dragonfly", target_os = "freebsd", + target_os = "ios", target_os = "macos", + target_os = "netbsd", target_os = "openbsd"))] + EV_RECEIPT; + } +} + +libc_bitflags!( + pub struct FilterFlag: u32 { + #[cfg(any(target_os = "macos", target_os = "ios"))] + NOTE_ABSOLUTE; + NOTE_ATTRIB; + NOTE_CHILD; + NOTE_DELETE; + #[cfg(target_os = "openbsd")] + NOTE_EOF; + NOTE_EXEC; + NOTE_EXIT; + #[cfg(any(target_os = "macos", target_os = "ios"))] + NOTE_EXITSTATUS; + NOTE_EXTEND; + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + NOTE_FFAND; + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + NOTE_FFCOPY; + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + NOTE_FFCTRLMASK; + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + NOTE_FFLAGSMASK; + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + NOTE_FFNOP; + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + NOTE_FFOR; + NOTE_FORK; + NOTE_LINK; + NOTE_LOWAT; + #[cfg(target_os = "freebsd")] + NOTE_MSECONDS; + #[cfg(any(target_os = "macos", target_os = "ios"))] + NOTE_NONE; + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] + NOTE_NSECONDS; + #[cfg(target_os = "dragonfly")] + NOTE_OOB; + NOTE_PCTRLMASK; + NOTE_PDATAMASK; + NOTE_RENAME; + NOTE_REVOKE; + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] + NOTE_SECONDS; + #[cfg(any(target_os = "macos", target_os = "ios"))] + NOTE_SIGNAL; + NOTE_TRACK; + NOTE_TRACKERR; + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + NOTE_TRIGGER; + #[cfg(target_os = "openbsd")] + NOTE_TRUNCATE; + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] + NOTE_USECONDS; + #[cfg(any(target_os = "macos", target_os = "ios"))] + NOTE_VM_ERROR; + #[cfg(any(target_os = "macos", target_os = "ios"))] + NOTE_VM_PRESSURE; + #[cfg(any(target_os = "macos", target_os = "ios"))] + NOTE_VM_PRESSURE_SUDDEN_TERMINATE; + #[cfg(any(target_os = "macos", target_os = "ios"))] + NOTE_VM_PRESSURE_TERMINATE; + NOTE_WRITE; + } +); + +pub fn kqueue() -> Result<RawFd> { + let res = unsafe { libc::kqueue() }; + + Errno::result(res) +} + +// KEvent can't derive Send because on some operating systems, udata is defined +// as a void*. However, KEvent's public API always treats udata as an intptr_t, +// which is safe to Send. +unsafe impl Send for KEvent {} + +impl KEvent { + #[allow(clippy::needless_update)] // Not needless on all platforms. + pub fn new( + ident: uintptr_t, + filter: EventFilter, + flags: EventFlag, + fflags: FilterFlag, + data: intptr_t, + udata: intptr_t, + ) -> KEvent { + KEvent { + kevent: libc::kevent { + ident, + filter: filter as type_of_event_filter, + flags: flags.bits(), + fflags: fflags.bits(), + // data can be either i64 or intptr_t, depending on platform + data: data as _, + udata: udata as type_of_udata, + ..unsafe { mem::zeroed() } + }, + } + } + + pub fn ident(&self) -> uintptr_t { + self.kevent.ident + } + + pub fn filter(&self) -> Result<EventFilter> { + self.kevent.filter.try_into() + } + + pub fn flags(&self) -> EventFlag { + EventFlag::from_bits(self.kevent.flags).unwrap() + } + + pub fn fflags(&self) -> FilterFlag { + FilterFlag::from_bits(self.kevent.fflags).unwrap() + } + + pub fn data(&self) -> intptr_t { + self.kevent.data as intptr_t + } + + pub fn udata(&self) -> intptr_t { + self.kevent.udata as intptr_t + } +} + +pub fn kevent( + kq: RawFd, + changelist: &[KEvent], + eventlist: &mut [KEvent], + timeout_ms: usize, +) -> Result<usize> { + // Convert ms to timespec + let timeout = timespec { + tv_sec: (timeout_ms / 1000) as time_t, + tv_nsec: ((timeout_ms % 1000) * 1_000_000) as c_long, + }; + + kevent_ts(kq, changelist, eventlist, Some(timeout)) +} + +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd" +))] +type type_of_nchanges = c_int; +#[cfg(target_os = "netbsd")] +type type_of_nchanges = size_t; + +pub fn kevent_ts( + kq: RawFd, + changelist: &[KEvent], + eventlist: &mut [KEvent], + timeout_opt: Option<timespec>, +) -> Result<usize> { + let res = unsafe { + libc::kevent( + kq, + changelist.as_ptr() as *const libc::kevent, + changelist.len() as type_of_nchanges, + eventlist.as_mut_ptr() as *mut libc::kevent, + eventlist.len() as type_of_nchanges, + if let Some(ref timeout) = timeout_opt { + timeout as *const timespec + } else { + ptr::null() + }, + ) + }; + + Errno::result(res).map(|r| r as usize) +} + +#[inline] +pub fn ev_set( + ev: &mut KEvent, + ident: usize, + filter: EventFilter, + flags: EventFlag, + fflags: FilterFlag, + udata: intptr_t, +) { + ev.kevent.ident = ident as uintptr_t; + ev.kevent.filter = filter as type_of_event_filter; + ev.kevent.flags = flags.bits(); + ev.kevent.fflags = fflags.bits(); + ev.kevent.data = 0; + ev.kevent.udata = udata as type_of_udata; +} + +#[test] +fn test_struct_kevent() { + use std::mem; + + let udata: intptr_t = 12345; + + let actual = KEvent::new( + 0xdead_beef, + EventFilter::EVFILT_READ, + EventFlag::EV_ONESHOT | EventFlag::EV_ADD, + FilterFlag::NOTE_CHILD | FilterFlag::NOTE_EXIT, + 0x1337, + udata, + ); + assert_eq!(0xdead_beef, actual.ident()); + let filter = actual.kevent.filter; + assert_eq!(libc::EVFILT_READ, filter); + assert_eq!(libc::EV_ONESHOT | libc::EV_ADD, actual.flags().bits()); + assert_eq!(libc::NOTE_CHILD | libc::NOTE_EXIT, actual.fflags().bits()); + assert_eq!(0x1337, actual.data()); + assert_eq!(udata as type_of_udata, actual.udata() as type_of_udata); + assert_eq!(mem::size_of::<libc::kevent>(), mem::size_of::<KEvent>()); +} + +#[test] +fn test_kevent_filter() { + let udata: intptr_t = 12345; + + let actual = KEvent::new( + 0xdead_beef, + EventFilter::EVFILT_READ, + EventFlag::EV_ONESHOT | EventFlag::EV_ADD, + FilterFlag::NOTE_CHILD | FilterFlag::NOTE_EXIT, + 0x1337, + udata, + ); + assert_eq!(EventFilter::EVFILT_READ, actual.filter().unwrap()); +} diff --git a/third_party/rust/nix/src/sys/eventfd.rs b/third_party/rust/nix/src/sys/eventfd.rs new file mode 100644 index 0000000000..cd906720cd --- /dev/null +++ b/third_party/rust/nix/src/sys/eventfd.rs @@ -0,0 +1,17 @@ +use crate::errno::Errno; +use crate::Result; +use std::os::unix::io::RawFd; + +libc_bitflags! { + pub struct EfdFlags: libc::c_int { + EFD_CLOEXEC; // Since Linux 2.6.27 + EFD_NONBLOCK; // Since Linux 2.6.27 + EFD_SEMAPHORE; // Since Linux 2.6.30 + } +} + +pub fn eventfd(initval: libc::c_uint, flags: EfdFlags) -> Result<RawFd> { + let res = unsafe { libc::eventfd(initval, flags.bits()) }; + + Errno::result(res).map(|r| r as RawFd) +} diff --git a/third_party/rust/nix/src/sys/inotify.rs b/third_party/rust/nix/src/sys/inotify.rs new file mode 100644 index 0000000000..84356ec70f --- /dev/null +++ b/third_party/rust/nix/src/sys/inotify.rs @@ -0,0 +1,248 @@ +//! Monitoring API for filesystem events. +//! +//! Inotify is a Linux-only API to monitor filesystems events. +//! +//! For more documentation, please read [inotify(7)](https://man7.org/linux/man-pages/man7/inotify.7.html). +//! +//! # Examples +//! +//! Monitor all events happening in directory "test": +//! ```no_run +//! # use nix::sys::inotify::{AddWatchFlags,InitFlags,Inotify}; +//! # +//! // We create a new inotify instance. +//! let instance = Inotify::init(InitFlags::empty()).unwrap(); +//! +//! // We add a new watch on directory "test" for all events. +//! let wd = instance.add_watch("test", AddWatchFlags::IN_ALL_EVENTS).unwrap(); +//! +//! loop { +//! // We read from our inotify instance for events. +//! let events = instance.read_events().unwrap(); +//! println!("Events: {:?}", events); +//! } +//! ``` + +use crate::errno::Errno; +use crate::unistd::read; +use crate::NixPath; +use crate::Result; +use cfg_if::cfg_if; +use libc::{c_char, c_int}; +use std::ffi::{CStr, OsStr, OsString}; +use std::mem::{size_of, MaybeUninit}; +use std::os::unix::ffi::OsStrExt; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::ptr; + +libc_bitflags! { + /// Configuration options for [`inotify_add_watch`](fn.inotify_add_watch.html). + pub struct AddWatchFlags: u32 { + /// File was accessed. + IN_ACCESS; + /// File was modified. + IN_MODIFY; + /// Metadata changed. + IN_ATTRIB; + /// Writable file was closed. + IN_CLOSE_WRITE; + /// Nonwritable file was closed. + IN_CLOSE_NOWRITE; + /// File was opened. + IN_OPEN; + /// File was moved from X. + IN_MOVED_FROM; + /// File was moved to Y. + IN_MOVED_TO; + /// Subfile was created. + IN_CREATE; + /// Subfile was deleted. + IN_DELETE; + /// Self was deleted. + IN_DELETE_SELF; + /// Self was moved. + IN_MOVE_SELF; + + /// Backing filesystem was unmounted. + IN_UNMOUNT; + /// Event queue overflowed. + IN_Q_OVERFLOW; + /// File was ignored. + IN_IGNORED; + + /// Combination of `IN_CLOSE_WRITE` and `IN_CLOSE_NOWRITE`. + IN_CLOSE; + /// Combination of `IN_MOVED_FROM` and `IN_MOVED_TO`. + IN_MOVE; + + /// Only watch the path if it is a directory. + IN_ONLYDIR; + /// Don't follow symlinks. + IN_DONT_FOLLOW; + + /// Event occurred against directory. + IN_ISDIR; + /// Only send event once. + IN_ONESHOT; + /// All of the events. + IN_ALL_EVENTS; + } +} + +libc_bitflags! { + /// Configuration options for [`inotify_init1`](fn.inotify_init1.html). + pub struct InitFlags: c_int { + /// Set the `FD_CLOEXEC` flag on the file descriptor. + IN_CLOEXEC; + /// Set the `O_NONBLOCK` flag on the open file description referred to by the new file descriptor. + IN_NONBLOCK; + } +} + +/// An inotify instance. This is also a file descriptor, you can feed it to +/// other interfaces consuming file descriptors, epoll for example. +#[derive(Debug, Clone, Copy)] +pub struct Inotify { + fd: RawFd, +} + +/// This object is returned when you create a new watch on an inotify instance. +/// It is then returned as part of an event once triggered. It allows you to +/// know which watch triggered which event. +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct WatchDescriptor { + wd: i32, +} + +/// A single inotify event. +/// +/// For more documentation see, [inotify(7)](https://man7.org/linux/man-pages/man7/inotify.7.html). +#[derive(Debug)] +pub struct InotifyEvent { + /// Watch descriptor. This field corresponds to the watch descriptor you + /// were issued when calling add_watch. It allows you to know which watch + /// this event comes from. + pub wd: WatchDescriptor, + /// Event mask. This field is a bitfield describing the exact event that + /// occured. + pub mask: AddWatchFlags, + /// This cookie is a number that allows you to connect related events. For + /// now only IN_MOVED_FROM and IN_MOVED_TO can be connected. + pub cookie: u32, + /// Filename. This field exists only if the event was triggered for a file + /// inside the watched directory. + pub name: Option<OsString>, +} + +impl Inotify { + /// Initialize a new inotify instance. + /// + /// Returns a Result containing an inotify instance. + /// + /// For more information see, [inotify_init(2)](https://man7.org/linux/man-pages/man2/inotify_init.2.html). + pub fn init(flags: InitFlags) -> Result<Inotify> { + let res = Errno::result(unsafe { libc::inotify_init1(flags.bits()) }); + + res.map(|fd| Inotify { fd }) + } + + /// Adds a new watch on the target file or directory. + /// + /// Returns a watch descriptor. This is not a File Descriptor! + /// + /// For more information see, [inotify_add_watch(2)](https://man7.org/linux/man-pages/man2/inotify_add_watch.2.html). + pub fn add_watch<P: ?Sized + NixPath>( + self, + path: &P, + mask: AddWatchFlags, + ) -> Result<WatchDescriptor> { + let res = path.with_nix_path(|cstr| unsafe { + libc::inotify_add_watch(self.fd, cstr.as_ptr(), mask.bits()) + })?; + + Errno::result(res).map(|wd| WatchDescriptor { wd }) + } + + /// Removes an existing watch using the watch descriptor returned by + /// inotify_add_watch. + /// + /// Returns an EINVAL error if the watch descriptor is invalid. + /// + /// For more information see, [inotify_rm_watch(2)](https://man7.org/linux/man-pages/man2/inotify_rm_watch.2.html). + pub fn rm_watch(self, wd: WatchDescriptor) -> Result<()> { + cfg_if! { + if #[cfg(target_os = "linux")] { + let arg = wd.wd; + } else if #[cfg(target_os = "android")] { + let arg = wd.wd as u32; + } + } + let res = unsafe { libc::inotify_rm_watch(self.fd, arg) }; + + Errno::result(res).map(drop) + } + + /// Reads a collection of events from the inotify file descriptor. This call + /// can either be blocking or non blocking depending on whether IN_NONBLOCK + /// was set at initialization. + /// + /// Returns as many events as available. If the call was non blocking and no + /// events could be read then the EAGAIN error is returned. + pub fn read_events(self) -> Result<Vec<InotifyEvent>> { + let header_size = size_of::<libc::inotify_event>(); + const BUFSIZ: usize = 4096; + let mut buffer = [0u8; BUFSIZ]; + let mut events = Vec::new(); + let mut offset = 0; + + let nread = read(self.fd, &mut buffer)?; + + while (nread - offset) >= header_size { + let event = unsafe { + let mut event = MaybeUninit::<libc::inotify_event>::uninit(); + ptr::copy_nonoverlapping( + buffer.as_ptr().add(offset), + event.as_mut_ptr() as *mut u8, + (BUFSIZ - offset).min(header_size), + ); + event.assume_init() + }; + + let name = match event.len { + 0 => None, + _ => { + let ptr = unsafe { + buffer.as_ptr().add(offset + header_size) + as *const c_char + }; + let cstr = unsafe { CStr::from_ptr(ptr) }; + + Some(OsStr::from_bytes(cstr.to_bytes()).to_owned()) + } + }; + + events.push(InotifyEvent { + wd: WatchDescriptor { wd: event.wd }, + mask: AddWatchFlags::from_bits_truncate(event.mask), + cookie: event.cookie, + name, + }); + + offset += header_size + event.len as usize; + } + + Ok(events) + } +} + +impl AsRawFd for Inotify { + fn as_raw_fd(&self) -> RawFd { + self.fd + } +} + +impl FromRawFd for Inotify { + unsafe fn from_raw_fd(fd: RawFd) -> Self { + Inotify { fd } + } +} diff --git a/third_party/rust/nix/src/sys/ioctl/bsd.rs b/third_party/rust/nix/src/sys/ioctl/bsd.rs new file mode 100644 index 0000000000..307994cb96 --- /dev/null +++ b/third_party/rust/nix/src/sys/ioctl/bsd.rs @@ -0,0 +1,129 @@ +/// The datatype used for the ioctl number +#[doc(hidden)] +#[cfg(not(target_os = "illumos"))] +pub type ioctl_num_type = ::libc::c_ulong; + +#[doc(hidden)] +#[cfg(target_os = "illumos")] +pub type ioctl_num_type = ::libc::c_int; + +/// The datatype used for the 3rd argument +#[doc(hidden)] +pub type ioctl_param_type = ::libc::c_int; + +mod consts { + use crate::sys::ioctl::ioctl_num_type; + #[doc(hidden)] + pub const VOID: ioctl_num_type = 0x2000_0000; + #[doc(hidden)] + pub const OUT: ioctl_num_type = 0x4000_0000; + #[doc(hidden)] + #[allow(overflowing_literals)] + pub const IN: ioctl_num_type = 0x8000_0000; + #[doc(hidden)] + pub const INOUT: ioctl_num_type = IN | OUT; + #[doc(hidden)] + pub const IOCPARM_MASK: ioctl_num_type = 0x1fff; +} + +pub use self::consts::*; + +#[macro_export] +#[doc(hidden)] +macro_rules! ioc { + ($inout:expr, $group:expr, $num:expr, $len:expr) => { + $inout + | (($len as $crate::sys::ioctl::ioctl_num_type + & $crate::sys::ioctl::IOCPARM_MASK) + << 16) + | (($group as $crate::sys::ioctl::ioctl_num_type) << 8) + | ($num as $crate::sys::ioctl::ioctl_num_type) + }; +} + +/// Generate an ioctl request code for a command that passes no data. +/// +/// This is equivalent to the `_IO()` macro exposed by the C ioctl API. +/// +/// You should only use this macro directly if the `ioctl` you're working +/// with is "bad" and you cannot use `ioctl_none!()` directly. +/// +/// # Example +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// const KVMIO: u8 = 0xAE; +/// ioctl_write_int_bad!(kvm_create_vm, request_code_none!(KVMIO, 0x03)); +/// # fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! request_code_none { + ($g:expr, $n:expr) => { + ioc!($crate::sys::ioctl::VOID, $g, $n, 0) + }; +} + +/// Generate an ioctl request code for a command that passes an integer +/// +/// This is equivalent to the `_IOWINT()` macro exposed by the C ioctl API. +/// +/// You should only use this macro directly if the `ioctl` you're working +/// with is "bad" and you cannot use `ioctl_write_int!()` directly. +#[macro_export(local_inner_macros)] +macro_rules! request_code_write_int { + ($g:expr, $n:expr) => { + ioc!( + $crate::sys::ioctl::VOID, + $g, + $n, + ::std::mem::size_of::<$crate::libc::c_int>() + ) + }; +} + +/// Generate an ioctl request code for a command that reads. +/// +/// This is equivalent to the `_IOR()` macro exposed by the C ioctl API. +/// +/// You should only use this macro directly if the `ioctl` you're working +/// with is "bad" and you cannot use `ioctl_read!()` directly. +/// +/// The read/write direction is relative to userland, so this +/// command would be userland is reading and the kernel is +/// writing. +#[macro_export(local_inner_macros)] +macro_rules! request_code_read { + ($g:expr, $n:expr, $len:expr) => { + ioc!($crate::sys::ioctl::OUT, $g, $n, $len) + }; +} + +/// Generate an ioctl request code for a command that writes. +/// +/// This is equivalent to the `_IOW()` macro exposed by the C ioctl API. +/// +/// You should only use this macro directly if the `ioctl` you're working +/// with is "bad" and you cannot use `ioctl_write!()` directly. +/// +/// The read/write direction is relative to userland, so this +/// command would be userland is writing and the kernel is +/// reading. +#[macro_export(local_inner_macros)] +macro_rules! request_code_write { + ($g:expr, $n:expr, $len:expr) => { + ioc!($crate::sys::ioctl::IN, $g, $n, $len) + }; +} + +/// Generate an ioctl request code for a command that reads and writes. +/// +/// This is equivalent to the `_IOWR()` macro exposed by the C ioctl API. +/// +/// You should only use this macro directly if the `ioctl` you're working +/// with is "bad" and you cannot use `ioctl_readwrite!()` directly. +#[macro_export(local_inner_macros)] +macro_rules! request_code_readwrite { + ($g:expr, $n:expr, $len:expr) => { + ioc!($crate::sys::ioctl::INOUT, $g, $n, $len) + }; +} diff --git a/third_party/rust/nix/src/sys/ioctl/linux.rs b/third_party/rust/nix/src/sys/ioctl/linux.rs new file mode 100644 index 0000000000..0c0a209053 --- /dev/null +++ b/third_party/rust/nix/src/sys/ioctl/linux.rs @@ -0,0 +1,172 @@ +/// The datatype used for the ioctl number +#[cfg(any(target_os = "android", target_env = "musl"))] +#[doc(hidden)] +pub type ioctl_num_type = ::libc::c_int; +#[cfg(not(any(target_os = "android", target_env = "musl")))] +#[doc(hidden)] +pub type ioctl_num_type = ::libc::c_ulong; +/// The datatype used for the 3rd argument +#[doc(hidden)] +pub type ioctl_param_type = ::libc::c_ulong; + +#[doc(hidden)] +pub const NRBITS: ioctl_num_type = 8; +#[doc(hidden)] +pub const TYPEBITS: ioctl_num_type = 8; + +#[cfg(any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "powerpc", + target_arch = "powerpc64", + target_arch = "sparc64" +))] +mod consts { + #[doc(hidden)] + pub const NONE: u8 = 1; + #[doc(hidden)] + pub const READ: u8 = 2; + #[doc(hidden)] + pub const WRITE: u8 = 4; + #[doc(hidden)] + pub const SIZEBITS: u8 = 13; + #[doc(hidden)] + pub const DIRBITS: u8 = 3; +} + +// "Generic" ioctl protocol +#[cfg(any( + target_arch = "x86", + target_arch = "arm", + target_arch = "s390x", + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "riscv32", + target_arch = "riscv64" +))] +mod consts { + #[doc(hidden)] + pub const NONE: u8 = 0; + #[doc(hidden)] + pub const READ: u8 = 2; + #[doc(hidden)] + pub const WRITE: u8 = 1; + #[doc(hidden)] + pub const SIZEBITS: u8 = 14; + #[doc(hidden)] + pub const DIRBITS: u8 = 2; +} + +pub use self::consts::*; + +#[doc(hidden)] +pub const NRSHIFT: ioctl_num_type = 0; +#[doc(hidden)] +pub const TYPESHIFT: ioctl_num_type = NRSHIFT + NRBITS as ioctl_num_type; +#[doc(hidden)] +pub const SIZESHIFT: ioctl_num_type = TYPESHIFT + TYPEBITS as ioctl_num_type; +#[doc(hidden)] +pub const DIRSHIFT: ioctl_num_type = SIZESHIFT + SIZEBITS as ioctl_num_type; + +#[doc(hidden)] +pub const NRMASK: ioctl_num_type = (1 << NRBITS) - 1; +#[doc(hidden)] +pub const TYPEMASK: ioctl_num_type = (1 << TYPEBITS) - 1; +#[doc(hidden)] +pub const SIZEMASK: ioctl_num_type = (1 << SIZEBITS) - 1; +#[doc(hidden)] +pub const DIRMASK: ioctl_num_type = (1 << DIRBITS) - 1; + +/// Encode an ioctl command. +#[macro_export] +#[doc(hidden)] +macro_rules! ioc { + ($dir:expr, $ty:expr, $nr:expr, $sz:expr) => { + (($dir as $crate::sys::ioctl::ioctl_num_type + & $crate::sys::ioctl::DIRMASK) + << $crate::sys::ioctl::DIRSHIFT) + | (($ty as $crate::sys::ioctl::ioctl_num_type + & $crate::sys::ioctl::TYPEMASK) + << $crate::sys::ioctl::TYPESHIFT) + | (($nr as $crate::sys::ioctl::ioctl_num_type + & $crate::sys::ioctl::NRMASK) + << $crate::sys::ioctl::NRSHIFT) + | (($sz as $crate::sys::ioctl::ioctl_num_type + & $crate::sys::ioctl::SIZEMASK) + << $crate::sys::ioctl::SIZESHIFT) + }; +} + +/// Generate an ioctl request code for a command that passes no data. +/// +/// This is equivalent to the `_IO()` macro exposed by the C ioctl API. +/// +/// You should only use this macro directly if the `ioctl` you're working +/// with is "bad" and you cannot use `ioctl_none!()` directly. +/// +/// # Example +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// const KVMIO: u8 = 0xAE; +/// ioctl_write_int_bad!(kvm_create_vm, request_code_none!(KVMIO, 0x03)); +/// # fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! request_code_none { + ($ty:expr, $nr:expr) => { + ioc!($crate::sys::ioctl::NONE, $ty, $nr, 0) + }; +} + +/// Generate an ioctl request code for a command that reads. +/// +/// This is equivalent to the `_IOR()` macro exposed by the C ioctl API. +/// +/// You should only use this macro directly if the `ioctl` you're working +/// with is "bad" and you cannot use `ioctl_read!()` directly. +/// +/// The read/write direction is relative to userland, so this +/// command would be userland is reading and the kernel is +/// writing. +#[macro_export(local_inner_macros)] +macro_rules! request_code_read { + ($ty:expr, $nr:expr, $sz:expr) => { + ioc!($crate::sys::ioctl::READ, $ty, $nr, $sz) + }; +} + +/// Generate an ioctl request code for a command that writes. +/// +/// This is equivalent to the `_IOW()` macro exposed by the C ioctl API. +/// +/// You should only use this macro directly if the `ioctl` you're working +/// with is "bad" and you cannot use `ioctl_write!()` directly. +/// +/// The read/write direction is relative to userland, so this +/// command would be userland is writing and the kernel is +/// reading. +#[macro_export(local_inner_macros)] +macro_rules! request_code_write { + ($ty:expr, $nr:expr, $sz:expr) => { + ioc!($crate::sys::ioctl::WRITE, $ty, $nr, $sz) + }; +} + +/// Generate an ioctl request code for a command that reads and writes. +/// +/// This is equivalent to the `_IOWR()` macro exposed by the C ioctl API. +/// +/// You should only use this macro directly if the `ioctl` you're working +/// with is "bad" and you cannot use `ioctl_readwrite!()` directly. +#[macro_export(local_inner_macros)] +macro_rules! request_code_readwrite { + ($ty:expr, $nr:expr, $sz:expr) => { + ioc!( + $crate::sys::ioctl::READ | $crate::sys::ioctl::WRITE, + $ty, + $nr, + $sz + ) + }; +} diff --git a/third_party/rust/nix/src/sys/ioctl/mod.rs b/third_party/rust/nix/src/sys/ioctl/mod.rs new file mode 100644 index 0000000000..98d6b5c99d --- /dev/null +++ b/third_party/rust/nix/src/sys/ioctl/mod.rs @@ -0,0 +1,786 @@ +//! Provide helpers for making ioctl system calls. +//! +//! This library is pretty low-level and messy. `ioctl` is not fun. +//! +//! What is an `ioctl`? +//! =================== +//! +//! The `ioctl` syscall is the grab-bag syscall on POSIX systems. Don't want to add a new +//! syscall? Make it an `ioctl`! `ioctl` refers to both the syscall, and the commands that can be +//! sent with it. `ioctl` stands for "IO control", and the commands are always sent to a file +//! descriptor. +//! +//! It is common to see `ioctl`s used for the following purposes: +//! +//! * Provide read/write access to out-of-band data related to a device such as configuration +//! (for instance, setting serial port options) +//! * Provide a mechanism for performing full-duplex data transfers (for instance, xfer on SPI +//! devices). +//! * Provide access to control functions on a device (for example, on Linux you can send +//! commands like pause, resume, and eject to the CDROM device. +//! * Do whatever else the device driver creator thought made most sense. +//! +//! `ioctl`s are synchronous system calls and are similar to read and write calls in that regard. +//! They operate on file descriptors and have an identifier that specifies what the ioctl is. +//! Additionally they may read or write data and therefore need to pass along a data pointer. +//! Besides the semantics of the ioctls being confusing, the generation of this identifer can also +//! be difficult. +//! +//! Historically `ioctl` numbers were arbitrary hard-coded values. In Linux (before 2.6) and some +//! unices this has changed to a more-ordered system where the ioctl numbers are partitioned into +//! subcomponents (For linux this is documented in +//! [`Documentation/ioctl/ioctl-number.rst`](https://elixir.bootlin.com/linux/latest/source/Documentation/userspace-api/ioctl/ioctl-number.rst)): +//! +//! * Number: The actual ioctl ID +//! * Type: A grouping of ioctls for a common purpose or driver +//! * Size: The size in bytes of the data that will be transferred +//! * Direction: Whether there is any data and if it's read, write, or both +//! +//! Newer drivers should not generate complete integer identifiers for their `ioctl`s instead +//! preferring to use the 4 components above to generate the final ioctl identifier. Because of +//! how old `ioctl`s are, however, there are many hard-coded `ioctl` identifiers. These are +//! commonly referred to as "bad" in `ioctl` documentation. +//! +//! Defining `ioctl`s +//! ================= +//! +//! This library provides several `ioctl_*!` macros for binding `ioctl`s. These generate public +//! unsafe functions that can then be used for calling the ioctl. This macro has a few different +//! ways it can be used depending on the specific ioctl you're working with. +//! +//! A simple `ioctl` is `SPI_IOC_RD_MODE`. This ioctl works with the SPI interface on Linux. This +//! specific `ioctl` reads the mode of the SPI device as a `u8`. It's declared in +//! `/include/uapi/linux/spi/spidev.h` as `_IOR(SPI_IOC_MAGIC, 1, __u8)`. Since it uses the `_IOR` +//! macro, we know it's a `read` ioctl and can use the `ioctl_read!` macro as follows: +//! +//! ``` +//! # #[macro_use] extern crate nix; +//! const SPI_IOC_MAGIC: u8 = b'k'; // Defined in linux/spi/spidev.h +//! const SPI_IOC_TYPE_MODE: u8 = 1; +//! ioctl_read!(spi_read_mode, SPI_IOC_MAGIC, SPI_IOC_TYPE_MODE, u8); +//! # fn main() {} +//! ``` +//! +//! This generates the function: +//! +//! ``` +//! # #[macro_use] extern crate nix; +//! # use std::mem; +//! # use nix::{libc, Result}; +//! # use nix::errno::Errno; +//! # use nix::libc::c_int as c_int; +//! # const SPI_IOC_MAGIC: u8 = b'k'; // Defined in linux/spi/spidev.h +//! # const SPI_IOC_TYPE_MODE: u8 = 1; +//! pub unsafe fn spi_read_mode(fd: c_int, data: *mut u8) -> Result<c_int> { +//! let res = libc::ioctl(fd, request_code_read!(SPI_IOC_MAGIC, SPI_IOC_TYPE_MODE, mem::size_of::<u8>()), data); +//! Errno::result(res) +//! } +//! # fn main() {} +//! ``` +//! +//! The return value for the wrapper functions generated by the `ioctl_*!` macros are `nix::Error`s. +//! These are generated by assuming the return value of the ioctl is `-1` on error and everything +//! else is a valid return value. If this is not the case, `Result::map` can be used to map some +//! of the range of "good" values (-Inf..-2, 0..Inf) into a smaller range in a helper function. +//! +//! Writing `ioctl`s generally use pointers as their data source and these should use the +//! `ioctl_write_ptr!`. But in some cases an `int` is passed directly. For these `ioctl`s use the +//! `ioctl_write_int!` macro. This variant does not take a type as the last argument: +//! +//! ``` +//! # #[macro_use] extern crate nix; +//! const HCI_IOC_MAGIC: u8 = b'k'; +//! const HCI_IOC_HCIDEVUP: u8 = 1; +//! ioctl_write_int!(hci_dev_up, HCI_IOC_MAGIC, HCI_IOC_HCIDEVUP); +//! # fn main() {} +//! ``` +//! +//! Some `ioctl`s don't transfer any data, and those should use `ioctl_none!`. This macro +//! doesn't take a type and so it is declared similar to the `write_int` variant shown above. +//! +//! The mode for a given `ioctl` should be clear from the documentation if it has good +//! documentation. Otherwise it will be clear based on the macro used to generate the `ioctl` +//! number where `_IO`, `_IOR`, `_IOW`, and `_IOWR` map to "none", "read", "write_*", and "readwrite" +//! respectively. To determine the specific `write_` variant to use you'll need to find +//! what the argument type is supposed to be. If it's an `int`, then `write_int` should be used, +//! otherwise it should be a pointer and `write_ptr` should be used. On Linux the +//! [`ioctl_list` man page](https://man7.org/linux/man-pages/man2/ioctl_list.2.html) describes a +//! large number of `ioctl`s and describes their argument data type. +//! +//! Using "bad" `ioctl`s +//! -------------------- +//! +//! As mentioned earlier, there are many old `ioctl`s that do not use the newer method of +//! generating `ioctl` numbers and instead use hardcoded values. These can be used with the +//! `ioctl_*_bad!` macros. This naming comes from the Linux kernel which refers to these +//! `ioctl`s as "bad". These are a different variant as they bypass calling the macro that generates +//! the ioctl number and instead use the defined value directly. +//! +//! For example the `TCGETS` `ioctl` reads a `termios` data structure for a given file descriptor. +//! It's defined as `0x5401` in `ioctls.h` on Linux and can be implemented as: +//! +//! ``` +//! # #[macro_use] extern crate nix; +//! # #[cfg(any(target_os = "android", target_os = "linux"))] +//! # use nix::libc::TCGETS as TCGETS; +//! # #[cfg(any(target_os = "android", target_os = "linux"))] +//! # use nix::libc::termios as termios; +//! # #[cfg(any(target_os = "android", target_os = "linux"))] +//! ioctl_read_bad!(tcgets, TCGETS, termios); +//! # fn main() {} +//! ``` +//! +//! The generated function has the same form as that generated by `ioctl_read!`: +//! +//! ```text +//! pub unsafe fn tcgets(fd: c_int, data: *mut termios) -> Result<c_int>; +//! ``` +//! +//! Working with Arrays +//! ------------------- +//! +//! Some `ioctl`s work with entire arrays of elements. These are supported by the `ioctl_*_buf` +//! family of macros: `ioctl_read_buf`, `ioctl_write_buf`, and `ioctl_readwrite_buf`. Note that +//! there are no "bad" versions for working with buffers. The generated functions include a `len` +//! argument to specify the number of elements (where the type of each element is specified in the +//! macro). +//! +//! Again looking to the SPI `ioctl`s on Linux for an example, there is a `SPI_IOC_MESSAGE` `ioctl` +//! that queues up multiple SPI messages by writing an entire array of `spi_ioc_transfer` structs. +//! `linux/spi/spidev.h` defines a macro to calculate the `ioctl` number like: +//! +//! ```C +//! #define SPI_IOC_MAGIC 'k' +//! #define SPI_MSGSIZE(N) ... +//! #define SPI_IOC_MESSAGE(N) _IOW(SPI_IOC_MAGIC, 0, char[SPI_MSGSIZE(N)]) +//! ``` +//! +//! The `SPI_MSGSIZE(N)` calculation is already handled by the `ioctl_*!` macros, so all that's +//! needed to define this `ioctl` is: +//! +//! ``` +//! # #[macro_use] extern crate nix; +//! const SPI_IOC_MAGIC: u8 = b'k'; // Defined in linux/spi/spidev.h +//! const SPI_IOC_TYPE_MESSAGE: u8 = 0; +//! # pub struct spi_ioc_transfer(u64); +//! ioctl_write_buf!(spi_transfer, SPI_IOC_MAGIC, SPI_IOC_TYPE_MESSAGE, spi_ioc_transfer); +//! # fn main() {} +//! ``` +//! +//! This generates a function like: +//! +//! ``` +//! # #[macro_use] extern crate nix; +//! # use std::mem; +//! # use nix::{libc, Result}; +//! # use nix::errno::Errno; +//! # use nix::libc::c_int as c_int; +//! # const SPI_IOC_MAGIC: u8 = b'k'; +//! # const SPI_IOC_TYPE_MESSAGE: u8 = 0; +//! # pub struct spi_ioc_transfer(u64); +//! pub unsafe fn spi_message(fd: c_int, data: &mut [spi_ioc_transfer]) -> Result<c_int> { +//! let res = libc::ioctl(fd, +//! request_code_write!(SPI_IOC_MAGIC, SPI_IOC_TYPE_MESSAGE, data.len() * mem::size_of::<spi_ioc_transfer>()), +//! data); +//! Errno::result(res) +//! } +//! # fn main() {} +//! ``` +//! +//! Finding `ioctl` Documentation +//! ----------------------------- +//! +//! For Linux, look at your system's headers. For example, `/usr/include/linux/input.h` has a lot +//! of lines defining macros which use `_IO`, `_IOR`, `_IOW`, `_IOC`, and `_IOWR`. Some `ioctl`s are +//! documented directly in the headers defining their constants, but others have more extensive +//! documentation in man pages (like termios' `ioctl`s which are in `tty_ioctl(4)`). +//! +//! Documenting the Generated Functions +//! =================================== +//! +//! In many cases, users will wish for the functions generated by the `ioctl` +//! macro to be public and documented. For this reason, the generated functions +//! are public by default. If you wish to hide the ioctl, you will need to put +//! them in a private module. +//! +//! For documentation, it is possible to use doc comments inside the `ioctl_*!` macros. Here is an +//! example : +//! +//! ``` +//! # #[macro_use] extern crate nix; +//! # use nix::libc::c_int; +//! ioctl_read! { +//! /// Make the given terminal the controlling terminal of the calling process. The calling +//! /// process must be a session leader and not have a controlling terminal already. If the +//! /// terminal is already the controlling terminal of a different session group then the +//! /// ioctl will fail with **EPERM**, unless the caller is root (more precisely: has the +//! /// **CAP_SYS_ADMIN** capability) and arg equals 1, in which case the terminal is stolen +//! /// and all processes that had it as controlling terminal lose it. +//! tiocsctty, b't', 19, c_int +//! } +//! +//! # fn main() {} +//! ``` +use cfg_if::cfg_if; + +#[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))] +#[macro_use] +mod linux; + +#[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "redox" +))] +pub use self::linux::*; + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "haiku", + target_os = "openbsd" +))] +#[macro_use] +mod bsd; + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "haiku", + target_os = "openbsd" +))] +pub use self::bsd::*; + +/// Convert raw ioctl return value to a Nix result +#[macro_export] +#[doc(hidden)] +macro_rules! convert_ioctl_res { + ($w:expr) => {{ + $crate::errno::Errno::result($w) + }}; +} + +/// Generates a wrapper function for an ioctl that passes no data to the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl identifier +/// * The ioctl sequence number +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int) -> Result<libc::c_int> +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +/// +/// # Example +/// +/// The `videodev2` driver on Linux defines the `log_status` `ioctl` as: +/// +/// ```C +/// #define VIDIOC_LOG_STATUS _IO('V', 70) +/// ``` +/// +/// This can be implemented in Rust like: +/// +/// ```no_run +/// # #[macro_use] extern crate nix; +/// ioctl_none!(log_status, b'V', 70); +/// fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! ioctl_none { + ($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_none!($ioty, $nr) as $crate::sys::ioctl::ioctl_num_type)) + } + ) +} + +/// Generates a wrapper function for a "bad" ioctl that passes no data to the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl request code +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int) -> Result<libc::c_int> +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +/// +/// # Example +/// +/// ```no_run +/// # #[macro_use] extern crate nix; +/// # use libc::TIOCNXCL; +/// # use std::fs::File; +/// # use std::os::unix::io::AsRawFd; +/// ioctl_none_bad!(tiocnxcl, TIOCNXCL); +/// fn main() { +/// let file = File::open("/dev/ttyUSB0").unwrap(); +/// unsafe { tiocnxcl(file.as_raw_fd()) }.unwrap(); +/// } +/// ``` +// TODO: add an example using request_code_*!() +#[macro_export(local_inner_macros)] +macro_rules! ioctl_none_bad { + ($(#[$attr:meta])* $name:ident, $nr:expr) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type)) + } + ) +} + +/// Generates a wrapper function for an ioctl that reads data from the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl identifier +/// * The ioctl sequence number +/// * The data type passed by this ioctl +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: *mut DATA_TYPE) -> Result<libc::c_int> +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +/// +/// # Example +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// const SPI_IOC_MAGIC: u8 = b'k'; // Defined in linux/spi/spidev.h +/// const SPI_IOC_TYPE_MODE: u8 = 1; +/// ioctl_read!(spi_read_mode, SPI_IOC_MAGIC, SPI_IOC_TYPE_MODE, u8); +/// # fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! ioctl_read { + ($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr, $ty:ty) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: *mut $ty) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_read!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} + +/// Generates a wrapper function for a "bad" ioctl that reads data from the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl request code +/// * The data type passed by this ioctl +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: *mut DATA_TYPE) -> Result<libc::c_int> +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +/// +/// # Example +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// # #[cfg(any(target_os = "android", target_os = "linux"))] +/// ioctl_read_bad!(tcgets, libc::TCGETS, libc::termios); +/// # fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! ioctl_read_bad { + ($(#[$attr:meta])* $name:ident, $nr:expr, $ty:ty) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: *mut $ty) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} + +/// Generates a wrapper function for an ioctl that writes data through a pointer to the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl identifier +/// * The ioctl sequence number +/// * The data type passed by this ioctl +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: *const DATA_TYPE) -> Result<libc::c_int> +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +/// +/// # Example +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// # pub struct v4l2_audio {} +/// ioctl_write_ptr!(s_audio, b'V', 34, v4l2_audio); +/// # fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! ioctl_write_ptr { + ($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr, $ty:ty) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: *const $ty) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} + +/// Generates a wrapper function for a "bad" ioctl that writes data through a pointer to the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl request code +/// * The data type passed by this ioctl +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: *const DATA_TYPE) -> Result<libc::c_int> +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +/// +/// # Example +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// # #[cfg(any(target_os = "android", target_os = "linux"))] +/// ioctl_write_ptr_bad!(tcsets, libc::TCSETS, libc::termios); +/// # fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! ioctl_write_ptr_bad { + ($(#[$attr:meta])* $name:ident, $nr:expr, $ty:ty) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: *const $ty) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} + +cfg_if! { + if #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] { + /// Generates a wrapper function for a ioctl that writes an integer to the kernel. + /// + /// The arguments to this macro are: + /// + /// * The function name + /// * The ioctl identifier + /// * The ioctl sequence number + /// + /// The generated function has the following signature: + /// + /// ```rust,ignore + /// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: nix::sys::ioctl::ioctl_param_type) -> Result<libc::c_int> + /// ``` + /// + /// `nix::sys::ioctl::ioctl_param_type` depends on the OS: + /// * BSD - `libc::c_int` + /// * Linux - `libc::c_ulong` + /// + /// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). + /// + /// # Example + /// + /// ``` + /// # #[macro_use] extern crate nix; + /// ioctl_write_int!(vt_activate, b'v', 4); + /// # fn main() {} + /// ``` + #[macro_export(local_inner_macros)] + macro_rules! ioctl_write_int { + ($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: $crate::sys::ioctl::ioctl_param_type) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write_int!($ioty, $nr) as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) + } + } else { + /// Generates a wrapper function for a ioctl that writes an integer to the kernel. + /// + /// The arguments to this macro are: + /// + /// * The function name + /// * The ioctl identifier + /// * The ioctl sequence number + /// + /// The generated function has the following signature: + /// + /// ```rust,ignore + /// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: nix::sys::ioctl::ioctl_param_type) -> Result<libc::c_int> + /// ``` + /// + /// `nix::sys::ioctl::ioctl_param_type` depends on the OS: + /// * BSD - `libc::c_int` + /// * Linux - `libc::c_ulong` + /// + /// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). + /// + /// # Example + /// + /// ``` + /// # #[macro_use] extern crate nix; + /// const HCI_IOC_MAGIC: u8 = b'k'; + /// const HCI_IOC_HCIDEVUP: u8 = 1; + /// ioctl_write_int!(hci_dev_up, HCI_IOC_MAGIC, HCI_IOC_HCIDEVUP); + /// # fn main() {} + /// ``` + #[macro_export(local_inner_macros)] + macro_rules! ioctl_write_int { + ($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: $crate::sys::ioctl::ioctl_param_type) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write!($ioty, $nr, ::std::mem::size_of::<$crate::libc::c_int>()) as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) + } + } +} + +/// Generates a wrapper function for a "bad" ioctl that writes an integer to the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl request code +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: libc::c_int) -> Result<libc::c_int> +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +/// +/// # Examples +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// # #[cfg(any(target_os = "android", target_os = "linux"))] +/// ioctl_write_int_bad!(tcsbrk, libc::TCSBRK); +/// # fn main() {} +/// ``` +/// +/// ```rust +/// # #[macro_use] extern crate nix; +/// const KVMIO: u8 = 0xAE; +/// ioctl_write_int_bad!(kvm_create_vm, request_code_none!(KVMIO, 0x03)); +/// # fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! ioctl_write_int_bad { + ($(#[$attr:meta])* $name:ident, $nr:expr) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: $crate::libc::c_int) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} + +/// Generates a wrapper function for an ioctl that reads and writes data to the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl identifier +/// * The ioctl sequence number +/// * The data type passed by this ioctl +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: *mut DATA_TYPE) -> Result<libc::c_int> +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +/// +/// # Example +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// # pub struct v4l2_audio {} +/// ioctl_readwrite!(enum_audio, b'V', 65, v4l2_audio); +/// # fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! ioctl_readwrite { + ($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr, $ty:ty) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: *mut $ty) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_readwrite!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} + +/// Generates a wrapper function for a "bad" ioctl that reads and writes data to the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl request code +/// * The data type passed by this ioctl +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: *mut DATA_TYPE) -> Result<libc::c_int> +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +// TODO: Find an example for ioctl_readwrite_bad +#[macro_export(local_inner_macros)] +macro_rules! ioctl_readwrite_bad { + ($(#[$attr:meta])* $name:ident, $nr:expr, $ty:ty) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: *mut $ty) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} + +/// Generates a wrapper function for an ioctl that reads an array of elements from the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl identifier +/// * The ioctl sequence number +/// * The data type passed by this ioctl +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: &mut [DATA_TYPE]) -> Result<libc::c_int> +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +// TODO: Find an example for ioctl_read_buf +#[macro_export(local_inner_macros)] +macro_rules! ioctl_read_buf { + ($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr, $ty:ty) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: &mut [$ty]) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_read!($ioty, $nr, data.len() * ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} + +/// Generates a wrapper function for an ioctl that writes an array of elements to the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl identifier +/// * The ioctl sequence number +/// * The data type passed by this ioctl +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: &[DATA_TYPE]) -> Result<libc::c_int> +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +/// +/// # Examples +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// const SPI_IOC_MAGIC: u8 = b'k'; // Defined in linux/spi/spidev.h +/// const SPI_IOC_TYPE_MESSAGE: u8 = 0; +/// # pub struct spi_ioc_transfer(u64); +/// ioctl_write_buf!(spi_transfer, SPI_IOC_MAGIC, SPI_IOC_TYPE_MESSAGE, spi_ioc_transfer); +/// # fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! ioctl_write_buf { + ($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr, $ty:ty) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: &[$ty]) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write!($ioty, $nr, data.len() * ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} + +/// Generates a wrapper function for an ioctl that reads and writes an array of elements to the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl identifier +/// * The ioctl sequence number +/// * The data type passed by this ioctl +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: &mut [DATA_TYPE]) -> Result<libc::c_int> +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +// TODO: Find an example for readwrite_buf +#[macro_export(local_inner_macros)] +macro_rules! ioctl_readwrite_buf { + ($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr, $ty:ty) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: &mut [$ty]) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_readwrite!($ioty, $nr, data.len() * ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} diff --git a/third_party/rust/nix/src/sys/memfd.rs b/third_party/rust/nix/src/sys/memfd.rs new file mode 100644 index 0000000000..ad9345e89d --- /dev/null +++ b/third_party/rust/nix/src/sys/memfd.rs @@ -0,0 +1,64 @@ +//! Interfaces for managing memory-backed files. + +use cfg_if::cfg_if; +use std::os::unix::io::RawFd; + +use crate::errno::Errno; +use crate::Result; +use std::ffi::CStr; + +libc_bitflags!( + /// Options that change the behavior of [`memfd_create`]. + pub struct MemFdCreateFlag: libc::c_uint { + /// Set the close-on-exec ([`FD_CLOEXEC`]) flag on the new file descriptor. + /// + /// By default, the new file descriptor is set to remain open across an [`execve`] + /// (the `FD_CLOEXEC` flag is initially disabled). This flag can be used to change + /// this default. The file offset is set to the beginning of the file (see [`lseek`]). + /// + /// See also the description of the `O_CLOEXEC` flag in [`open(2)`]. + /// + /// [`execve`]: crate::unistd::execve + /// [`lseek`]: crate::unistd::lseek + /// [`FD_CLOEXEC`]: crate::fcntl::FdFlag::FD_CLOEXEC + /// [`open(2)`]: https://man7.org/linux/man-pages/man2/open.2.html + MFD_CLOEXEC; + /// Allow sealing operations on this file. + /// + /// See also the file sealing notes given in [`memfd_create(2)`]. + /// + /// [`memfd_create(2)`]: https://man7.org/linux/man-pages/man2/memfd_create.2.html + MFD_ALLOW_SEALING; + } +); + +/// Creates an anonymous file that lives in memory, and return a file-descriptor to it. +/// +/// The file behaves like a regular file, and so can be modified, truncated, memory-mapped, and so on. +/// However, unlike a regular file, it lives in RAM and has a volatile backing storage. +/// +/// For more information, see [`memfd_create(2)`]. +/// +/// [`memfd_create(2)`]: https://man7.org/linux/man-pages/man2/memfd_create.2.html +pub fn memfd_create(name: &CStr, flags: MemFdCreateFlag) -> Result<RawFd> { + let res = unsafe { + cfg_if! { + if #[cfg(all( + // Android does not have a memfd_create symbol + not(target_os = "android"), + any( + target_os = "freebsd", + // If the OS is Linux, gnu and musl expose a memfd_create symbol but not uclibc + target_env = "gnu", + target_env = "musl", + )))] + { + libc::memfd_create(name.as_ptr(), flags.bits()) + } else { + libc::syscall(libc::SYS_memfd_create, name.as_ptr(), flags.bits()) + } + } + }; + + Errno::result(res).map(|r| r as RawFd) +} diff --git a/third_party/rust/nix/src/sys/mman.rs b/third_party/rust/nix/src/sys/mman.rs new file mode 100644 index 0000000000..2bee091610 --- /dev/null +++ b/third_party/rust/nix/src/sys/mman.rs @@ -0,0 +1,599 @@ +//! Memory management declarations. + +use crate::errno::Errno; +#[cfg(not(target_os = "android"))] +use crate::NixPath; +use crate::Result; +#[cfg(not(target_os = "android"))] +#[cfg(feature = "fs")] +use crate::{fcntl::OFlag, sys::stat::Mode}; +use libc::{self, c_int, c_void, off_t, size_t}; +use std::{os::unix::io::RawFd, num::NonZeroUsize}; + +libc_bitflags! { + /// Desired memory protection of a memory mapping. + pub struct ProtFlags: c_int { + /// Pages cannot be accessed. + PROT_NONE; + /// Pages can be read. + PROT_READ; + /// Pages can be written. + PROT_WRITE; + /// Pages can be executed + PROT_EXEC; + /// Apply protection up to the end of a mapping that grows upwards. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + PROT_GROWSDOWN; + /// Apply protection down to the beginning of a mapping that grows downwards. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + PROT_GROWSUP; + } +} + +libc_bitflags! { + /// Additional parameters for [`mmap`]. + pub struct MapFlags: c_int { + /// Compatibility flag. Ignored. + MAP_FILE; + /// Share this mapping. Mutually exclusive with `MAP_PRIVATE`. + MAP_SHARED; + /// Create a private copy-on-write mapping. Mutually exclusive with `MAP_SHARED`. + MAP_PRIVATE; + /// Place the mapping at exactly the address specified in `addr`. + MAP_FIXED; + /// Place the mapping at exactly the address specified in `addr`, but never clobber an existing range. + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_FIXED_NOREPLACE; + /// To be used with `MAP_FIXED`, to forbid the system + /// to select a different address than the one specified. + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_EXCL; + /// Synonym for `MAP_ANONYMOUS`. + MAP_ANON; + /// The mapping is not backed by any file. + MAP_ANONYMOUS; + /// Put the mapping into the first 2GB of the process address space. + #[cfg(any(all(any(target_os = "android", target_os = "linux"), + any(target_arch = "x86", target_arch = "x86_64")), + all(target_os = "linux", target_env = "musl", any(target_arch = "x86", target_arch = "x86_64")), + all(target_os = "freebsd", target_pointer_width = "64")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_32BIT; + /// Used for stacks; indicates to the kernel that the mapping should extend downward in memory. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_GROWSDOWN; + /// Compatibility flag. Ignored. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_DENYWRITE; + /// Compatibility flag. Ignored. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_EXECUTABLE; + /// Mark the mmaped region to be locked in the same way as `mlock(2)`. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_LOCKED; + /// Do not reserve swap space for this mapping. + /// + /// This was removed in FreeBSD 11 and is unused in DragonFlyBSD. + #[cfg(not(any(target_os = "dragonfly", target_os = "freebsd")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_NORESERVE; + /// Populate page tables for a mapping. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_POPULATE; + /// Only meaningful when used with `MAP_POPULATE`. Don't perform read-ahead. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_NONBLOCK; + /// Allocate the mapping using "huge pages." + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGETLB; + /// Make use of 64KB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_64KB; + /// Make use of 512KB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_512KB; + /// Make use of 1MB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_1MB; + /// Make use of 2MB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_2MB; + /// Make use of 8MB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_8MB; + /// Make use of 16MB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_16MB; + /// Make use of 32MB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_32MB; + /// Make use of 256MB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_256MB; + /// Make use of 512MB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_512MB; + /// Make use of 1GB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_1GB; + /// Make use of 2GB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_2GB; + /// Make use of 16GB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_16GB; + + /// Lock the mapped region into memory as with `mlock(2)`. + #[cfg(target_os = "netbsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_WIRED; + /// Causes dirtied data in the specified range to be flushed to disk only when necessary. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_NOSYNC; + /// Rename private pages to a file. + /// + /// This was removed in FreeBSD 11 and is unused in DragonFlyBSD. + #[cfg(any(target_os = "netbsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_RENAME; + /// Region may contain semaphores. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HASSEMAPHORE; + /// Region grows down, like a stack. + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "linux", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_STACK; + /// Pages in this mapping are not retained in the kernel's memory cache. + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_NOCACHE; + /// Allows the W/X bit on the page, it's necessary on aarch64 architecture. + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_JIT; + /// Allows to use large pages, underlying alignment based on size. + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_ALIGNED_SUPER; + /// Pages will be discarded in the core dumps. + #[cfg(target_os = "openbsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_CONCEAL; + } +} + +#[cfg(any(target_os = "linux", target_os = "netbsd"))] +libc_bitflags! { + /// Options for [`mremap`]. + pub struct MRemapFlags: c_int { + /// Permit the kernel to relocate the mapping to a new virtual address, if necessary. + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MREMAP_MAYMOVE; + /// Place the mapping at exactly the address specified in `new_address`. + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MREMAP_FIXED; + /// Place the mapping at exactly the address specified in `new_address`. + #[cfg(target_os = "netbsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_FIXED; + /// Allows to duplicate the mapping to be able to apply different flags on the copy. + #[cfg(target_os = "netbsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_REMAPDUP; + } +} + +libc_enum! { + /// Usage information for a range of memory to allow for performance optimizations by the kernel. + /// + /// Used by [`madvise`]. + #[repr(i32)] + #[non_exhaustive] + pub enum MmapAdvise { + /// No further special treatment. This is the default. + MADV_NORMAL, + /// Expect random page references. + MADV_RANDOM, + /// Expect sequential page references. + MADV_SEQUENTIAL, + /// Expect access in the near future. + MADV_WILLNEED, + /// Do not expect access in the near future. + MADV_DONTNEED, + /// Free up a given range of pages and its associated backing store. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_REMOVE, + /// Do not make pages in this range available to the child after a `fork(2)`. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_DONTFORK, + /// Undo the effect of `MADV_DONTFORK`. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_DOFORK, + /// Poison the given pages. + /// + /// Subsequent references to those pages are treated like hardware memory corruption. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_HWPOISON, + /// Enable Kernel Samepage Merging (KSM) for the given pages. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_MERGEABLE, + /// Undo the effect of `MADV_MERGEABLE` + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_UNMERGEABLE, + /// Preserve the memory of each page but offline the original page. + #[cfg(any(target_os = "android", + all(target_os = "linux", any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "powerpc", + target_arch = "powerpc64", + target_arch = "s390x", + target_arch = "x86", + target_arch = "x86_64", + target_arch = "sparc64"))))] + MADV_SOFT_OFFLINE, + /// Enable Transparent Huge Pages (THP) for pages in the given range. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_HUGEPAGE, + /// Undo the effect of `MADV_HUGEPAGE`. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_NOHUGEPAGE, + /// Exclude the given range from a core dump. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_DONTDUMP, + /// Undo the effect of an earlier `MADV_DONTDUMP`. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_DODUMP, + /// Specify that the application no longer needs the pages in the given range. + MADV_FREE, + /// Request that the system not flush the current range to disk unless it needs to. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_NOSYNC, + /// Undoes the effects of `MADV_NOSYNC` for any future pages dirtied within the given range. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_AUTOSYNC, + /// Region is not included in a core file. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_NOCORE, + /// Include region in a core file + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_CORE, + /// This process should not be killed when swap space is exhausted. + #[cfg(any(target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_PROTECT, + /// Invalidate the hardware page table for the given region. + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_INVAL, + /// Set the offset of the page directory page to `value` for the virtual page table. + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_SETMAP, + /// Indicates that the application will not need the data in the given range. + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_ZERO_WIRED_PAGES, + /// Pages can be reused (by anyone). + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_FREE_REUSABLE, + /// Caller wants to reuse those pages. + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_FREE_REUSE, + // Darwin doesn't document this flag's behavior. + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + #[allow(missing_docs)] + MADV_CAN_REUSE, + } +} + +libc_bitflags! { + /// Configuration flags for [`msync`]. + pub struct MsFlags: c_int { + /// Schedule an update but return immediately. + MS_ASYNC; + /// Invalidate all cached data. + MS_INVALIDATE; + /// Invalidate pages, but leave them mapped. + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MS_KILLPAGES; + /// Deactivate pages, but leave them mapped. + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MS_DEACTIVATE; + /// Perform an update and wait for it to complete. + MS_SYNC; + } +} + +#[cfg(not(target_os = "haiku"))] +libc_bitflags! { + /// Flags for [`mlockall`]. + pub struct MlockAllFlags: c_int { + /// Lock pages that are currently mapped into the address space of the process. + MCL_CURRENT; + /// Lock pages which will become mapped into the address space of the process in the future. + MCL_FUTURE; + } +} + +/// Locks all memory pages that contain part of the address range with `length` +/// bytes starting at `addr`. +/// +/// Locked pages never move to the swap area. +/// +/// # Safety +/// +/// `addr` must meet all the requirements described in the [`mlock(2)`] man page. +/// +/// [`mlock(2)`]: https://man7.org/linux/man-pages/man2/mlock.2.html +pub unsafe fn mlock(addr: *const c_void, length: size_t) -> Result<()> { + Errno::result(libc::mlock(addr, length)).map(drop) +} + +/// Unlocks all memory pages that contain part of the address range with +/// `length` bytes starting at `addr`. +/// +/// # Safety +/// +/// `addr` must meet all the requirements described in the [`munlock(2)`] man +/// page. +/// +/// [`munlock(2)`]: https://man7.org/linux/man-pages/man2/munlock.2.html +pub unsafe fn munlock(addr: *const c_void, length: size_t) -> Result<()> { + Errno::result(libc::munlock(addr, length)).map(drop) +} + +/// Locks all memory pages mapped into this process' address space. +/// +/// Locked pages never move to the swap area. For more information, see [`mlockall(2)`]. +/// +/// [`mlockall(2)`]: https://man7.org/linux/man-pages/man2/mlockall.2.html +#[cfg(not(target_os = "haiku"))] +pub fn mlockall(flags: MlockAllFlags) -> Result<()> { + unsafe { Errno::result(libc::mlockall(flags.bits())) }.map(drop) +} + +/// Unlocks all memory pages mapped into this process' address space. +/// +/// For more information, see [`munlockall(2)`]. +/// +/// [`munlockall(2)`]: https://man7.org/linux/man-pages/man2/munlockall.2.html +#[cfg(not(target_os = "haiku"))] +pub fn munlockall() -> Result<()> { + unsafe { Errno::result(libc::munlockall()) }.map(drop) +} + +/// allocate memory, or map files or devices into memory +/// +/// # Safety +/// +/// See the [`mmap(2)`] man page for detailed requirements. +/// +/// [`mmap(2)`]: https://man7.org/linux/man-pages/man2/mmap.2.html +pub unsafe fn mmap( + addr: Option<NonZeroUsize>, + length: NonZeroUsize, + prot: ProtFlags, + flags: MapFlags, + fd: RawFd, + offset: off_t, +) -> Result<*mut c_void> { + let ptr = addr.map_or( + std::ptr::null_mut(), + |a| usize::from(a) as *mut c_void + ); + + let ret = libc::mmap(ptr, length.into(), prot.bits(), flags.bits(), fd, offset); + + if ret == libc::MAP_FAILED { + Err(Errno::last()) + } else { + Ok(ret) + } +} + +/// Expands (or shrinks) an existing memory mapping, potentially moving it at +/// the same time. +/// +/// # Safety +/// +/// See the `mremap(2)` [man page](https://man7.org/linux/man-pages/man2/mremap.2.html) for +/// detailed requirements. +#[cfg(any(target_os = "linux", target_os = "netbsd"))] +pub unsafe fn mremap( + addr: *mut c_void, + old_size: size_t, + new_size: size_t, + flags: MRemapFlags, + new_address: Option<*mut c_void>, +) -> Result<*mut c_void> { + #[cfg(target_os = "linux")] + let ret = libc::mremap( + addr, + old_size, + new_size, + flags.bits(), + new_address.unwrap_or(std::ptr::null_mut()), + ); + #[cfg(target_os = "netbsd")] + let ret = libc::mremap( + addr, + old_size, + new_address.unwrap_or(std::ptr::null_mut()), + new_size, + flags.bits(), + ); + + if ret == libc::MAP_FAILED { + Err(Errno::last()) + } else { + Ok(ret) + } +} + +/// remove a mapping +/// +/// # Safety +/// +/// `addr` must meet all the requirements described in the [`munmap(2)`] man +/// page. +/// +/// [`munmap(2)`]: https://man7.org/linux/man-pages/man2/munmap.2.html +pub unsafe fn munmap(addr: *mut c_void, len: size_t) -> Result<()> { + Errno::result(libc::munmap(addr, len)).map(drop) +} + +/// give advice about use of memory +/// +/// # Safety +/// +/// See the [`madvise(2)`] man page. Take special care when using +/// [`MmapAdvise::MADV_FREE`]. +/// +/// [`madvise(2)`]: https://man7.org/linux/man-pages/man2/madvise.2.html +pub unsafe fn madvise( + addr: *mut c_void, + length: size_t, + advise: MmapAdvise, +) -> Result<()> { + Errno::result(libc::madvise(addr, length, advise as i32)).map(drop) +} + +/// Set protection of memory mapping. +/// +/// See [`mprotect(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mprotect.html) for +/// details. +/// +/// # Safety +/// +/// Calls to `mprotect` are inherently unsafe, as changes to memory protections can lead to +/// SIGSEGVs. +/// +/// ``` +/// # use nix::libc::size_t; +/// # use nix::sys::mman::{mmap, mprotect, MapFlags, ProtFlags}; +/// # use std::ptr; +/// const ONE_K: size_t = 1024; +/// let one_k_non_zero = std::num::NonZeroUsize::new(ONE_K).unwrap(); +/// let mut slice: &mut [u8] = unsafe { +/// let mem = mmap(None, one_k_non_zero, ProtFlags::PROT_NONE, +/// MapFlags::MAP_ANON | MapFlags::MAP_PRIVATE, -1, 0).unwrap(); +/// mprotect(mem, ONE_K, ProtFlags::PROT_READ | ProtFlags::PROT_WRITE).unwrap(); +/// std::slice::from_raw_parts_mut(mem as *mut u8, ONE_K) +/// }; +/// assert_eq!(slice[0], 0x00); +/// slice[0] = 0xFF; +/// assert_eq!(slice[0], 0xFF); +/// ``` +pub unsafe fn mprotect( + addr: *mut c_void, + length: size_t, + prot: ProtFlags, +) -> Result<()> { + Errno::result(libc::mprotect(addr, length, prot.bits())).map(drop) +} + +/// synchronize a mapped region +/// +/// # Safety +/// +/// `addr` must meet all the requirements described in the [`msync(2)`] man +/// page. +/// +/// [`msync(2)`]: https://man7.org/linux/man-pages/man2/msync.2.html +pub unsafe fn msync( + addr: *mut c_void, + length: size_t, + flags: MsFlags, +) -> Result<()> { + Errno::result(libc::msync(addr, length, flags.bits())).map(drop) +} + +#[cfg(not(target_os = "android"))] +feature! { +#![feature = "fs"] +/// Creates and opens a new, or opens an existing, POSIX shared memory object. +/// +/// For more information, see [`shm_open(3)`]. +/// +/// [`shm_open(3)`]: https://man7.org/linux/man-pages/man3/shm_open.3.html +pub fn shm_open<P>( + name: &P, + flag: OFlag, + mode: Mode + ) -> Result<RawFd> + where P: ?Sized + NixPath +{ + let ret = name.with_nix_path(|cstr| { + #[cfg(any(target_os = "macos", target_os = "ios"))] + unsafe { + libc::shm_open(cstr.as_ptr(), flag.bits(), mode.bits() as libc::c_uint) + } + #[cfg(not(any(target_os = "macos", target_os = "ios")))] + unsafe { + libc::shm_open(cstr.as_ptr(), flag.bits(), mode.bits() as libc::mode_t) + } + })?; + + Errno::result(ret) +} +} + +/// Performs the converse of [`shm_open`], removing an object previously created. +/// +/// For more information, see [`shm_unlink(3)`]. +/// +/// [`shm_unlink(3)`]: https://man7.org/linux/man-pages/man3/shm_unlink.3.html +#[cfg(not(target_os = "android"))] +pub fn shm_unlink<P: ?Sized + NixPath>(name: &P) -> Result<()> { + let ret = + name.with_nix_path(|cstr| unsafe { libc::shm_unlink(cstr.as_ptr()) })?; + + Errno::result(ret).map(drop) +} diff --git a/third_party/rust/nix/src/sys/mod.rs b/third_party/rust/nix/src/sys/mod.rs new file mode 100644 index 0000000000..2065059de8 --- /dev/null +++ b/third_party/rust/nix/src/sys/mod.rs @@ -0,0 +1,228 @@ +//! Mostly platform-specific functionality +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + all(target_os = "linux", not(target_env = "uclibc")), + target_os = "macos", + target_os = "netbsd" +))] +feature! { + #![feature = "aio"] + pub mod aio; +} + +feature! { + #![feature = "event"] + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[allow(missing_docs)] + pub mod epoll; + + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[allow(missing_docs)] + pub mod event; + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[allow(missing_docs)] + pub mod eventfd; +} + +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "redox", + target_os = "macos", + target_os = "netbsd", + target_os = "illumos", + target_os = "openbsd" +))] +#[cfg(feature = "ioctl")] +#[cfg_attr(docsrs, doc(cfg(feature = "ioctl")))] +#[macro_use] +pub mod ioctl; + +#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +feature! { + #![feature = "fs"] + pub mod memfd; +} + +#[cfg(not(target_os = "redox"))] +feature! { + #![feature = "mman"] + pub mod mman; +} + +#[cfg(target_os = "linux")] +feature! { + #![feature = "personality"] + pub mod personality; +} + +feature! { + #![feature = "pthread"] + pub mod pthread; +} + +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" +))] +feature! { + #![feature = "ptrace"] + #[allow(missing_docs)] + pub mod ptrace; +} + +#[cfg(target_os = "linux")] +feature! { + #![feature = "quota"] + pub mod quota; +} + +#[cfg(target_os = "linux")] +feature! { + #![feature = "reboot"] + pub mod reboot; +} + +#[cfg(not(any( + target_os = "redox", + target_os = "fuchsia", + target_os = "illumos", + target_os = "haiku" +)))] +feature! { + #![feature = "resource"] + pub mod resource; +} + +#[cfg(not(target_os = "redox"))] +feature! { + #![feature = "poll"] + pub mod select; +} + +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos" +))] +feature! { + #![feature = "zerocopy"] + pub mod sendfile; +} + +pub mod signal; + +#[cfg(any(target_os = "android", target_os = "linux"))] +feature! { + #![feature = "signal"] + #[allow(missing_docs)] + pub mod signalfd; +} + +#[cfg(not(target_os = "redox"))] +feature! { + #![feature = "socket"] + #[allow(missing_docs)] + pub mod socket; +} + +feature! { + #![feature = "fs"] + #[allow(missing_docs)] + pub mod stat; +} + +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "openbsd" +))] +feature! { + #![feature = "fs"] + pub mod statfs; +} + +feature! { + #![feature = "fs"] + pub mod statvfs; +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +#[allow(missing_docs)] +pub mod sysinfo; + +feature! { + #![feature = "term"] + #[allow(missing_docs)] + pub mod termios; +} + +#[allow(missing_docs)] +pub mod time; + +feature! { + #![feature = "uio"] + pub mod uio; +} + +feature! { + #![feature = "feature"] + pub mod utsname; +} + +feature! { + #![feature = "process"] + pub mod wait; +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +feature! { + #![feature = "inotify"] + pub mod inotify; +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +feature! { + #![feature = "time"] + pub mod timerfd; +} + +#[cfg(all( + any( + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd" + ), + feature = "time", + feature = "signal" +))] +feature! { + #![feature = "time"] + pub mod timer; +} diff --git a/third_party/rust/nix/src/sys/personality.rs b/third_party/rust/nix/src/sys/personality.rs new file mode 100644 index 0000000000..f295a05fad --- /dev/null +++ b/third_party/rust/nix/src/sys/personality.rs @@ -0,0 +1,93 @@ +//! Process execution domains +use crate::errno::Errno; +use crate::Result; + +use libc::{self, c_int, c_ulong}; + +libc_bitflags! { + /// Flags used and returned by [`get()`](fn.get.html) and + /// [`set()`](fn.set.html). + pub struct Persona: c_int { + /// Provide the legacy virtual address space layout. + ADDR_COMPAT_LAYOUT; + /// Disable address-space-layout randomization. + ADDR_NO_RANDOMIZE; + /// Limit the address space to 32 bits. + ADDR_LIMIT_32BIT; + /// Use `0xc0000000` as the offset at which to search a virtual memory + /// chunk on [`mmap(2)`], otherwise use `0xffffe000`. + /// + /// [`mmap(2)`]: https://man7.org/linux/man-pages/man2/mmap.2.html + ADDR_LIMIT_3GB; + /// User-space function pointers to signal handlers point to descriptors. + #[cfg(not(any(target_env = "musl", target_env = "uclibc")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + FDPIC_FUNCPTRS; + /// Map page 0 as read-only. + MMAP_PAGE_ZERO; + /// `PROT_READ` implies `PROT_EXEC` for [`mmap(2)`]. + /// + /// [`mmap(2)`]: https://man7.org/linux/man-pages/man2/mmap.2.html + READ_IMPLIES_EXEC; + /// No effects. + SHORT_INODE; + /// [`select(2)`], [`pselect(2)`], and [`ppoll(2)`] do not modify the + /// returned timeout argument when interrupted by a signal handler. + /// + /// [`select(2)`]: https://man7.org/linux/man-pages/man2/select.2.html + /// [`pselect(2)`]: https://man7.org/linux/man-pages/man2/pselect.2.html + /// [`ppoll(2)`]: https://man7.org/linux/man-pages/man2/ppoll.2.html + STICKY_TIMEOUTS; + /// Have [`uname(2)`] report a 2.6.40+ version number rather than a 3.x + /// version number. + /// + /// [`uname(2)`]: https://man7.org/linux/man-pages/man2/uname.2.html + #[cfg(not(any(target_env = "musl", target_env = "uclibc")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + UNAME26; + /// No effects. + WHOLE_SECONDS; + } +} + +/// Retrieve the current process personality. +/// +/// Returns a Result containing a Persona instance. +/// +/// Example: +/// +/// ``` +/// # use nix::sys::personality::{self, Persona}; +/// let pers = personality::get().unwrap(); +/// assert!(!pers.contains(Persona::WHOLE_SECONDS)); +/// ``` +pub fn get() -> Result<Persona> { + let res = unsafe { libc::personality(0xFFFFFFFF) }; + + Errno::result(res).map(Persona::from_bits_truncate) +} + +/// Set the current process personality. +/// +/// Returns a Result containing the *previous* personality for the +/// process, as a Persona. +/// +/// For more information, see [personality(2)](https://man7.org/linux/man-pages/man2/personality.2.html) +/// +/// **NOTE**: This call **replaces** the current personality entirely. +/// To **update** the personality, first call `get()` and then `set()` +/// with the modified persona. +/// +/// Example: +/// +/// ``` +/// # use nix::sys::personality::{self, Persona}; +/// let mut pers = personality::get().unwrap(); +/// assert!(!pers.contains(Persona::ADDR_NO_RANDOMIZE)); +/// personality::set(pers | Persona::ADDR_NO_RANDOMIZE).unwrap(); +/// ``` +pub fn set(persona: Persona) -> Result<Persona> { + let res = unsafe { libc::personality(persona.bits() as c_ulong) }; + + Errno::result(res).map(Persona::from_bits_truncate) +} diff --git a/third_party/rust/nix/src/sys/pthread.rs b/third_party/rust/nix/src/sys/pthread.rs new file mode 100644 index 0000000000..6bad03a4d4 --- /dev/null +++ b/third_party/rust/nix/src/sys/pthread.rs @@ -0,0 +1,43 @@ +//! Low level threading primitives + +#[cfg(not(target_os = "redox"))] +use crate::errno::Errno; +#[cfg(not(target_os = "redox"))] +use crate::Result; +use libc::{self, pthread_t}; + +/// Identifies an individual thread. +pub type Pthread = pthread_t; + +/// Obtain ID of the calling thread (see +/// [`pthread_self(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_self.html) +/// +/// The thread ID returned by `pthread_self()` is not the same thing as +/// the kernel thread ID returned by a call to `gettid(2)`. +#[inline] +pub fn pthread_self() -> Pthread { + unsafe { libc::pthread_self() } +} + +feature! { +#![feature = "signal"] + +/// Send a signal to a thread (see [`pthread_kill(3)`]). +/// +/// If `signal` is `None`, `pthread_kill` will only preform error checking and +/// won't send any signal. +/// +/// [`pthread_kill(3)`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_kill.html +#[allow(clippy::not_unsafe_ptr_arg_deref)] +#[cfg(not(target_os = "redox"))] +pub fn pthread_kill<T>(thread: Pthread, signal: T) -> Result<()> + where T: Into<Option<crate::sys::signal::Signal>> +{ + let sig = match signal.into() { + Some(s) => s as libc::c_int, + None => 0, + }; + let res = unsafe { libc::pthread_kill(thread, sig) }; + Errno::result(res).map(drop) +} +} diff --git a/third_party/rust/nix/src/sys/ptrace/bsd.rs b/third_party/rust/nix/src/sys/ptrace/bsd.rs new file mode 100644 index 0000000000..ba267c6577 --- /dev/null +++ b/third_party/rust/nix/src/sys/ptrace/bsd.rs @@ -0,0 +1,195 @@ +use crate::errno::Errno; +use crate::sys::signal::Signal; +use crate::unistd::Pid; +use crate::Result; +use cfg_if::cfg_if; +use libc::{self, c_int}; +use std::ptr; + +pub type RequestType = c_int; + +cfg_if! { + if #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "openbsd"))] { + #[doc(hidden)] + pub type AddressType = *mut ::libc::c_char; + } else { + #[doc(hidden)] + pub type AddressType = *mut ::libc::c_void; + } +} + +libc_enum! { + #[repr(i32)] + /// Ptrace Request enum defining the action to be taken. + #[non_exhaustive] + pub enum Request { + PT_TRACE_ME, + PT_READ_I, + PT_READ_D, + #[cfg(target_os = "macos")] + #[cfg_attr(docsrs, doc(cfg(all())))] + PT_READ_U, + PT_WRITE_I, + PT_WRITE_D, + #[cfg(target_os = "macos")] + #[cfg_attr(docsrs, doc(cfg(all())))] + PT_WRITE_U, + PT_CONTINUE, + PT_KILL, + #[cfg(any(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos"), + all(target_os = "openbsd", target_arch = "x86_64"), + all(target_os = "netbsd", any(target_arch = "x86_64", + target_arch = "powerpc"))))] + PT_STEP, + PT_ATTACH, + PT_DETACH, + #[cfg(target_os = "macos")] + #[cfg_attr(docsrs, doc(cfg(all())))] + PT_SIGEXC, + #[cfg(target_os = "macos")] + #[cfg_attr(docsrs, doc(cfg(all())))] + PT_THUPDATE, + #[cfg(target_os = "macos")] + #[cfg_attr(docsrs, doc(cfg(all())))] + PT_ATTACHEXC + } +} + +unsafe fn ptrace_other( + request: Request, + pid: Pid, + addr: AddressType, + data: c_int, +) -> Result<c_int> { + Errno::result(libc::ptrace( + request as RequestType, + libc::pid_t::from(pid), + addr, + data, + )) + .map(|_| 0) +} + +/// Sets the process as traceable, as with `ptrace(PT_TRACEME, ...)` +/// +/// Indicates that this process is to be traced by its parent. +/// This is the only ptrace request to be issued by the tracee. +pub fn traceme() -> Result<()> { + unsafe { + ptrace_other(Request::PT_TRACE_ME, Pid::from_raw(0), ptr::null_mut(), 0) + .map(drop) + } +} + +/// Attach to a running process, as with `ptrace(PT_ATTACH, ...)` +/// +/// Attaches to the process specified by `pid`, making it a tracee of the calling process. +pub fn attach(pid: Pid) -> Result<()> { + unsafe { + ptrace_other(Request::PT_ATTACH, pid, ptr::null_mut(), 0).map(drop) + } +} + +/// Detaches the current running process, as with `ptrace(PT_DETACH, ...)` +/// +/// Detaches from the process specified by `pid` allowing it to run freely, optionally delivering a +/// signal specified by `sig`. +pub fn detach<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as c_int, + None => 0, + }; + unsafe { + ptrace_other(Request::PT_DETACH, pid, ptr::null_mut(), data).map(drop) + } +} + +/// Restart the stopped tracee process, as with `ptrace(PTRACE_CONT, ...)` +/// +/// Continues the execution of the process with PID `pid`, optionally +/// delivering a signal specified by `sig`. +pub fn cont<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as c_int, + None => 0, + }; + unsafe { + // Ignore the useless return value + ptrace_other(Request::PT_CONTINUE, pid, 1 as AddressType, data) + .map(drop) + } +} + +/// Issues a kill request as with `ptrace(PT_KILL, ...)` +/// +/// This request is equivalent to `ptrace(PT_CONTINUE, ..., SIGKILL);` +pub fn kill(pid: Pid) -> Result<()> { + unsafe { + ptrace_other(Request::PT_KILL, pid, 0 as AddressType, 0).map(drop) + } +} + +/// Move the stopped tracee process forward by a single step as with +/// `ptrace(PT_STEP, ...)` +/// +/// Advances the execution of the process with PID `pid` by a single step optionally delivering a +/// signal specified by `sig`. +/// +/// # Example +/// ```rust +/// use nix::sys::ptrace::step; +/// use nix::unistd::Pid; +/// use nix::sys::signal::Signal; +/// use nix::sys::wait::*; +/// // If a process changes state to the stopped state because of a SIGUSR1 +/// // signal, this will step the process forward and forward the user +/// // signal to the stopped process +/// match waitpid(Pid::from_raw(-1), None) { +/// Ok(WaitStatus::Stopped(pid, Signal::SIGUSR1)) => { +/// let _ = step(pid, Signal::SIGUSR1); +/// } +/// _ => {}, +/// } +/// ``` +#[cfg(any( + any(target_os = "dragonfly", target_os = "freebsd", target_os = "macos"), + all(target_os = "openbsd", target_arch = "x86_64"), + all( + target_os = "netbsd", + any(target_arch = "x86_64", target_arch = "powerpc") + ) +))] +pub fn step<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as c_int, + None => 0, + }; + unsafe { + ptrace_other(Request::PT_STEP, pid, ptr::null_mut(), data).map(drop) + } +} + +/// Reads a word from a processes memory at the given address +// Technically, ptrace doesn't dereference the pointer. It passes it directly +// to the kernel. +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn read(pid: Pid, addr: AddressType) -> Result<c_int> { + unsafe { + // Traditionally there was a difference between reading data or + // instruction memory but not in modern systems. + ptrace_other(Request::PT_READ_D, pid, addr, 0) + } +} + +/// Writes a word into the processes memory at the given address +// Technically, ptrace doesn't dereference the pointer. It passes it directly +// to the kernel. +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn write(pid: Pid, addr: AddressType, data: c_int) -> Result<()> { + unsafe { ptrace_other(Request::PT_WRITE_D, pid, addr, data).map(drop) } +} diff --git a/third_party/rust/nix/src/sys/ptrace/linux.rs b/third_party/rust/nix/src/sys/ptrace/linux.rs new file mode 100644 index 0000000000..9687e05d42 --- /dev/null +++ b/third_party/rust/nix/src/sys/ptrace/linux.rs @@ -0,0 +1,558 @@ +//! For detailed description of the ptrace requests, consult `man ptrace`. + +use crate::errno::Errno; +use crate::sys::signal::Signal; +use crate::unistd::Pid; +use crate::Result; +use cfg_if::cfg_if; +use libc::{self, c_long, c_void, siginfo_t}; +use std::{mem, ptr}; + +pub type AddressType = *mut ::libc::c_void; + +#[cfg(all( + target_os = "linux", + any( + all( + target_arch = "x86_64", + any(target_env = "gnu", target_env = "musl") + ), + all(target_arch = "x86", target_env = "gnu") + ) +))] +use libc::user_regs_struct; + +cfg_if! { + if #[cfg(any(all(target_os = "linux", target_arch = "s390x"), + all(target_os = "linux", target_env = "gnu"), + target_env = "uclibc"))] { + #[doc(hidden)] + pub type RequestType = ::libc::c_uint; + } else { + #[doc(hidden)] + pub type RequestType = ::libc::c_int; + } +} + +libc_enum! { + #[cfg_attr(not(any(target_env = "musl", target_env = "uclibc", target_os = "android")), repr(u32))] + #[cfg_attr(any(target_env = "musl", target_env = "uclibc", target_os = "android"), repr(i32))] + /// Ptrace Request enum defining the action to be taken. + #[non_exhaustive] + pub enum Request { + PTRACE_TRACEME, + PTRACE_PEEKTEXT, + PTRACE_PEEKDATA, + PTRACE_PEEKUSER, + PTRACE_POKETEXT, + PTRACE_POKEDATA, + PTRACE_POKEUSER, + PTRACE_CONT, + PTRACE_KILL, + PTRACE_SINGLESTEP, + #[cfg(any(all(target_os = "android", target_pointer_width = "32"), + all(target_os = "linux", any(target_env = "musl", + target_arch = "mips", + target_arch = "mips64", + target_arch = "x86_64", + target_pointer_width = "32"))))] + PTRACE_GETREGS, + #[cfg(any(all(target_os = "android", target_pointer_width = "32"), + all(target_os = "linux", any(target_env = "musl", + target_arch = "mips", + target_arch = "mips64", + target_arch = "x86_64", + target_pointer_width = "32"))))] + PTRACE_SETREGS, + #[cfg(any(all(target_os = "android", target_pointer_width = "32"), + all(target_os = "linux", any(target_env = "musl", + target_arch = "mips", + target_arch = "mips64", + target_arch = "x86_64", + target_pointer_width = "32"))))] + PTRACE_GETFPREGS, + #[cfg(any(all(target_os = "android", target_pointer_width = "32"), + all(target_os = "linux", any(target_env = "musl", + target_arch = "mips", + target_arch = "mips64", + target_arch = "x86_64", + target_pointer_width = "32"))))] + PTRACE_SETFPREGS, + PTRACE_ATTACH, + PTRACE_DETACH, + #[cfg(all(target_os = "linux", any(target_env = "musl", + target_arch = "mips", + target_arch = "mips64", + target_arch = "x86", + target_arch = "x86_64")))] + PTRACE_GETFPXREGS, + #[cfg(all(target_os = "linux", any(target_env = "musl", + target_arch = "mips", + target_arch = "mips64", + target_arch = "x86", + target_arch = "x86_64")))] + PTRACE_SETFPXREGS, + PTRACE_SYSCALL, + PTRACE_SETOPTIONS, + PTRACE_GETEVENTMSG, + PTRACE_GETSIGINFO, + PTRACE_SETSIGINFO, + #[cfg(all(target_os = "linux", not(any(target_arch = "mips", + target_arch = "mips64"))))] + PTRACE_GETREGSET, + #[cfg(all(target_os = "linux", not(any(target_arch = "mips", + target_arch = "mips64"))))] + PTRACE_SETREGSET, + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + PTRACE_SEIZE, + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + PTRACE_INTERRUPT, + #[cfg(all(target_os = "linux", not(any(target_arch = "mips", + target_arch = "mips64"))))] + PTRACE_LISTEN, + #[cfg(all(target_os = "linux", not(any(target_arch = "mips", + target_arch = "mips64"))))] + PTRACE_PEEKSIGINFO, + #[cfg(all(target_os = "linux", target_env = "gnu", + any(target_arch = "x86", target_arch = "x86_64")))] + PTRACE_SYSEMU, + #[cfg(all(target_os = "linux", target_env = "gnu", + any(target_arch = "x86", target_arch = "x86_64")))] + PTRACE_SYSEMU_SINGLESTEP, + } +} + +libc_enum! { + #[repr(i32)] + /// Using the ptrace options the tracer can configure the tracee to stop + /// at certain events. This enum is used to define those events as defined + /// in `man ptrace`. + #[non_exhaustive] + pub enum Event { + /// Event that stops before a return from fork or clone. + PTRACE_EVENT_FORK, + /// Event that stops before a return from vfork or clone. + PTRACE_EVENT_VFORK, + /// Event that stops before a return from clone. + PTRACE_EVENT_CLONE, + /// Event that stops before a return from execve. + PTRACE_EVENT_EXEC, + /// Event for a return from vfork. + PTRACE_EVENT_VFORK_DONE, + /// Event for a stop before an exit. Unlike the waitpid Exit status program. + /// registers can still be examined + PTRACE_EVENT_EXIT, + /// Stop triggered by a seccomp rule on a tracee. + PTRACE_EVENT_SECCOMP, + /// Stop triggered by the `INTERRUPT` syscall, or a group stop, + /// or when a new child is attached. + PTRACE_EVENT_STOP, + } +} + +libc_bitflags! { + /// Ptrace options used in conjunction with the PTRACE_SETOPTIONS request. + /// See `man ptrace` for more details. + pub struct Options: libc::c_int { + /// When delivering system call traps set a bit to allow tracer to + /// distinguish between normal stops or syscall stops. May not work on + /// all systems. + PTRACE_O_TRACESYSGOOD; + /// Stop tracee at next fork and start tracing the forked process. + PTRACE_O_TRACEFORK; + /// Stop tracee at next vfork call and trace the vforked process. + PTRACE_O_TRACEVFORK; + /// Stop tracee at next clone call and trace the cloned process. + PTRACE_O_TRACECLONE; + /// Stop tracee at next execve call. + PTRACE_O_TRACEEXEC; + /// Stop tracee at vfork completion. + PTRACE_O_TRACEVFORKDONE; + /// Stop tracee at next exit call. Stops before exit commences allowing + /// tracer to see location of exit and register states. + PTRACE_O_TRACEEXIT; + /// Stop tracee when a SECCOMP_RET_TRACE rule is triggered. See `man seccomp` for more + /// details. + PTRACE_O_TRACESECCOMP; + /// Send a SIGKILL to the tracee if the tracer exits. This is useful + /// for ptrace jailers to prevent tracees from escaping their control. + PTRACE_O_EXITKILL; + } +} + +fn ptrace_peek( + request: Request, + pid: Pid, + addr: AddressType, + data: *mut c_void, +) -> Result<c_long> { + let ret = unsafe { + Errno::clear(); + libc::ptrace(request as RequestType, libc::pid_t::from(pid), addr, data) + }; + match Errno::result(ret) { + Ok(..) | Err(Errno::UnknownErrno) => Ok(ret), + err @ Err(..) => err, + } +} + +/// Get user registers, as with `ptrace(PTRACE_GETREGS, ...)` +#[cfg(all( + target_os = "linux", + any( + all( + target_arch = "x86_64", + any(target_env = "gnu", target_env = "musl") + ), + all(target_arch = "x86", target_env = "gnu") + ) +))] +pub fn getregs(pid: Pid) -> Result<user_regs_struct> { + ptrace_get_data::<user_regs_struct>(Request::PTRACE_GETREGS, pid) +} + +/// Set user registers, as with `ptrace(PTRACE_SETREGS, ...)` +#[cfg(all( + target_os = "linux", + any( + all( + target_arch = "x86_64", + any(target_env = "gnu", target_env = "musl") + ), + all(target_arch = "x86", target_env = "gnu") + ) +))] +pub fn setregs(pid: Pid, regs: user_regs_struct) -> Result<()> { + let res = unsafe { + libc::ptrace( + Request::PTRACE_SETREGS as RequestType, + libc::pid_t::from(pid), + ptr::null_mut::<c_void>(), + ®s as *const _ as *const c_void, + ) + }; + Errno::result(res).map(drop) +} + +/// Function for ptrace requests that return values from the data field. +/// Some ptrace get requests populate structs or larger elements than `c_long` +/// and therefore use the data field to return values. This function handles these +/// requests. +fn ptrace_get_data<T>(request: Request, pid: Pid) -> Result<T> { + let mut data = mem::MaybeUninit::uninit(); + let res = unsafe { + libc::ptrace( + request as RequestType, + libc::pid_t::from(pid), + ptr::null_mut::<T>(), + data.as_mut_ptr() as *const _ as *const c_void, + ) + }; + Errno::result(res)?; + Ok(unsafe { data.assume_init() }) +} + +unsafe fn ptrace_other( + request: Request, + pid: Pid, + addr: AddressType, + data: *mut c_void, +) -> Result<c_long> { + Errno::result(libc::ptrace( + request as RequestType, + libc::pid_t::from(pid), + addr, + data, + )) + .map(|_| 0) +} + +/// Set options, as with `ptrace(PTRACE_SETOPTIONS,...)`. +pub fn setoptions(pid: Pid, options: Options) -> Result<()> { + let res = unsafe { + libc::ptrace( + Request::PTRACE_SETOPTIONS as RequestType, + libc::pid_t::from(pid), + ptr::null_mut::<c_void>(), + options.bits() as *mut c_void, + ) + }; + Errno::result(res).map(drop) +} + +/// Gets a ptrace event as described by `ptrace(PTRACE_GETEVENTMSG,...)` +pub fn getevent(pid: Pid) -> Result<c_long> { + ptrace_get_data::<c_long>(Request::PTRACE_GETEVENTMSG, pid) +} + +/// Get siginfo as with `ptrace(PTRACE_GETSIGINFO,...)` +pub fn getsiginfo(pid: Pid) -> Result<siginfo_t> { + ptrace_get_data::<siginfo_t>(Request::PTRACE_GETSIGINFO, pid) +} + +/// Set siginfo as with `ptrace(PTRACE_SETSIGINFO,...)` +pub fn setsiginfo(pid: Pid, sig: &siginfo_t) -> Result<()> { + let ret = unsafe { + Errno::clear(); + libc::ptrace( + Request::PTRACE_SETSIGINFO as RequestType, + libc::pid_t::from(pid), + ptr::null_mut::<c_void>(), + sig as *const _ as *const c_void, + ) + }; + match Errno::result(ret) { + Ok(_) => Ok(()), + Err(e) => Err(e), + } +} + +/// Sets the process as traceable, as with `ptrace(PTRACE_TRACEME, ...)` +/// +/// Indicates that this process is to be traced by its parent. +/// This is the only ptrace request to be issued by the tracee. +pub fn traceme() -> Result<()> { + unsafe { + ptrace_other( + Request::PTRACE_TRACEME, + Pid::from_raw(0), + ptr::null_mut(), + ptr::null_mut(), + ) + .map(drop) // ignore the useless return value + } +} + +/// Continue execution until the next syscall, as with `ptrace(PTRACE_SYSCALL, ...)` +/// +/// Arranges for the tracee to be stopped at the next entry to or exit from a system call, +/// optionally delivering a signal specified by `sig`. +pub fn syscall<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as i32 as *mut c_void, + None => ptr::null_mut(), + }; + unsafe { + ptrace_other(Request::PTRACE_SYSCALL, pid, ptr::null_mut(), data) + .map(drop) // ignore the useless return value + } +} + +/// Continue execution until the next syscall, as with `ptrace(PTRACE_SYSEMU, ...)` +/// +/// In contrast to the `syscall` function, the syscall stopped at will not be executed. +/// Thus the the tracee will only be stopped once per syscall, +/// optionally delivering a signal specified by `sig`. +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any(target_arch = "x86", target_arch = "x86_64") +))] +pub fn sysemu<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as i32 as *mut c_void, + None => ptr::null_mut(), + }; + unsafe { + ptrace_other(Request::PTRACE_SYSEMU, pid, ptr::null_mut(), data) + .map(drop) + // ignore the useless return value + } +} + +/// Attach to a running process, as with `ptrace(PTRACE_ATTACH, ...)` +/// +/// Attaches to the process specified by `pid`, making it a tracee of the calling process. +pub fn attach(pid: Pid) -> Result<()> { + unsafe { + ptrace_other( + Request::PTRACE_ATTACH, + pid, + ptr::null_mut(), + ptr::null_mut(), + ) + .map(drop) // ignore the useless return value + } +} + +/// Attach to a running process, as with `ptrace(PTRACE_SEIZE, ...)` +/// +/// Attaches to the process specified in pid, making it a tracee of the calling process. +#[cfg(target_os = "linux")] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn seize(pid: Pid, options: Options) -> Result<()> { + unsafe { + ptrace_other( + Request::PTRACE_SEIZE, + pid, + ptr::null_mut(), + options.bits() as *mut c_void, + ) + .map(drop) // ignore the useless return value + } +} + +/// Detaches the current running process, as with `ptrace(PTRACE_DETACH, ...)` +/// +/// Detaches from the process specified by `pid` allowing it to run freely, optionally delivering a +/// signal specified by `sig`. +pub fn detach<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as i32 as *mut c_void, + None => ptr::null_mut(), + }; + unsafe { + ptrace_other(Request::PTRACE_DETACH, pid, ptr::null_mut(), data) + .map(drop) + } +} + +/// Restart the stopped tracee process, as with `ptrace(PTRACE_CONT, ...)` +/// +/// Continues the execution of the process with PID `pid`, optionally +/// delivering a signal specified by `sig`. +pub fn cont<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as i32 as *mut c_void, + None => ptr::null_mut(), + }; + unsafe { + ptrace_other(Request::PTRACE_CONT, pid, ptr::null_mut(), data).map(drop) + // ignore the useless return value + } +} + +/// Stop a tracee, as with `ptrace(PTRACE_INTERRUPT, ...)` +/// +/// This request is equivalent to `ptrace(PTRACE_INTERRUPT, ...)` +#[cfg(target_os = "linux")] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn interrupt(pid: Pid) -> Result<()> { + unsafe { + ptrace_other( + Request::PTRACE_INTERRUPT, + pid, + ptr::null_mut(), + ptr::null_mut(), + ) + .map(drop) + } +} + +/// Issues a kill request as with `ptrace(PTRACE_KILL, ...)` +/// +/// This request is equivalent to `ptrace(PTRACE_CONT, ..., SIGKILL);` +pub fn kill(pid: Pid) -> Result<()> { + unsafe { + ptrace_other( + Request::PTRACE_KILL, + pid, + ptr::null_mut(), + ptr::null_mut(), + ) + .map(drop) + } +} + +/// Move the stopped tracee process forward by a single step as with +/// `ptrace(PTRACE_SINGLESTEP, ...)` +/// +/// Advances the execution of the process with PID `pid` by a single step optionally delivering a +/// signal specified by `sig`. +/// +/// # Example +/// ```rust +/// use nix::sys::ptrace::step; +/// use nix::unistd::Pid; +/// use nix::sys::signal::Signal; +/// use nix::sys::wait::*; +/// +/// // If a process changes state to the stopped state because of a SIGUSR1 +/// // signal, this will step the process forward and forward the user +/// // signal to the stopped process +/// match waitpid(Pid::from_raw(-1), None) { +/// Ok(WaitStatus::Stopped(pid, Signal::SIGUSR1)) => { +/// let _ = step(pid, Signal::SIGUSR1); +/// } +/// _ => {}, +/// } +/// ``` +pub fn step<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as i32 as *mut c_void, + None => ptr::null_mut(), + }; + unsafe { + ptrace_other(Request::PTRACE_SINGLESTEP, pid, ptr::null_mut(), data) + .map(drop) + } +} + +/// Move the stopped tracee process forward by a single step or stop at the next syscall +/// as with `ptrace(PTRACE_SYSEMU_SINGLESTEP, ...)` +/// +/// Advances the execution by a single step or until the next syscall. +/// In case the tracee is stopped at a syscall, the syscall will not be executed. +/// Optionally, the signal specified by `sig` is delivered to the tracee upon continuation. +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any(target_arch = "x86", target_arch = "x86_64") +))] +pub fn sysemu_step<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as i32 as *mut c_void, + None => ptr::null_mut(), + }; + unsafe { + ptrace_other( + Request::PTRACE_SYSEMU_SINGLESTEP, + pid, + ptr::null_mut(), + data, + ) + .map(drop) // ignore the useless return value + } +} + +/// Reads a word from a processes memory at the given address +pub fn read(pid: Pid, addr: AddressType) -> Result<c_long> { + ptrace_peek(Request::PTRACE_PEEKDATA, pid, addr, ptr::null_mut()) +} + +/// Writes a word into the processes memory at the given address +/// +/// # Safety +/// +/// The `data` argument is passed directly to `ptrace(2)`. Read that man page +/// for guidance. +pub unsafe fn write( + pid: Pid, + addr: AddressType, + data: *mut c_void, +) -> Result<()> { + ptrace_other(Request::PTRACE_POKEDATA, pid, addr, data).map(drop) +} + +/// Reads a word from a user area at `offset`. +/// The user struct definition can be found in `/usr/include/sys/user.h`. +pub fn read_user(pid: Pid, offset: AddressType) -> Result<c_long> { + ptrace_peek(Request::PTRACE_PEEKUSER, pid, offset, ptr::null_mut()) +} + +/// Writes a word to a user area at `offset`. +/// The user struct definition can be found in `/usr/include/sys/user.h`. +/// +/// # Safety +/// +/// The `data` argument is passed directly to `ptrace(2)`. Read that man page +/// for guidance. +pub unsafe fn write_user( + pid: Pid, + offset: AddressType, + data: *mut c_void, +) -> Result<()> { + ptrace_other(Request::PTRACE_POKEUSER, pid, offset, data).map(drop) +} diff --git a/third_party/rust/nix/src/sys/ptrace/mod.rs b/third_party/rust/nix/src/sys/ptrace/mod.rs new file mode 100644 index 0000000000..2b121c0b4d --- /dev/null +++ b/third_party/rust/nix/src/sys/ptrace/mod.rs @@ -0,0 +1,25 @@ +///! Provides helpers for making ptrace system calls + +#[cfg(any(target_os = "android", target_os = "linux"))] +mod linux; + +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use self::linux::*; + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" +))] +mod bsd; + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" +))] +pub use self::bsd::*; diff --git a/third_party/rust/nix/src/sys/quota.rs b/third_party/rust/nix/src/sys/quota.rs new file mode 100644 index 0000000000..b3c44ca705 --- /dev/null +++ b/third_party/rust/nix/src/sys/quota.rs @@ -0,0 +1,338 @@ +//! Set and configure disk quotas for users, groups, or projects. +//! +//! # Examples +//! +//! Enabling and setting a quota: +//! +//! ```rust,no_run +//! # use nix::sys::quota::{Dqblk, quotactl_on, quotactl_set, QuotaFmt, QuotaType, QuotaValidFlags}; +//! quotactl_on(QuotaType::USRQUOTA, "/dev/sda1", QuotaFmt::QFMT_VFS_V1, "aquota.user").unwrap(); +//! let mut dqblk: Dqblk = Default::default(); +//! dqblk.set_blocks_hard_limit(10000); +//! dqblk.set_blocks_soft_limit(8000); +//! quotactl_set(QuotaType::USRQUOTA, "/dev/sda1", 50, &dqblk, QuotaValidFlags::QIF_BLIMITS).unwrap(); +//! ``` +use crate::errno::Errno; +use crate::{NixPath, Result}; +use libc::{self, c_char, c_int}; +use std::default::Default; +use std::{mem, ptr}; + +struct QuotaCmd(QuotaSubCmd, QuotaType); + +impl QuotaCmd { + #[allow(unused_unsafe)] + fn as_int(&self) -> c_int { + unsafe { libc::QCMD(self.0 as i32, self.1 as i32) } + } +} + +// linux quota version >= 2 +libc_enum! { + #[repr(i32)] + enum QuotaSubCmd { + Q_SYNC, + Q_QUOTAON, + Q_QUOTAOFF, + Q_GETQUOTA, + Q_SETQUOTA, + } +} + +libc_enum! { + /// The scope of the quota. + #[repr(i32)] + #[non_exhaustive] + pub enum QuotaType { + /// Specify a user quota + USRQUOTA, + /// Specify a group quota + GRPQUOTA, + } +} + +libc_enum! { + /// The type of quota format to use. + #[repr(i32)] + #[non_exhaustive] + pub enum QuotaFmt { + /// Use the original quota format. + QFMT_VFS_OLD, + /// Use the standard VFS v0 quota format. + /// + /// Handles 32-bit UIDs/GIDs and quota limits up to 2<sup>32</sup> bytes/2<sup>32</sup> inodes. + QFMT_VFS_V0, + /// Use the VFS v1 quota format. + /// + /// Handles 32-bit UIDs/GIDs and quota limits of 2<sup>64</sup> bytes/2<sup>64</sup> inodes. + QFMT_VFS_V1, + } +} + +libc_bitflags!( + /// Indicates the quota fields that are valid to read from. + #[derive(Default)] + pub struct QuotaValidFlags: u32 { + /// The block hard & soft limit fields. + QIF_BLIMITS; + /// The current space field. + QIF_SPACE; + /// The inode hard & soft limit fields. + QIF_ILIMITS; + /// The current inodes field. + QIF_INODES; + /// The disk use time limit field. + QIF_BTIME; + /// The file quote time limit field. + QIF_ITIME; + /// All block & inode limits. + QIF_LIMITS; + /// The space & inodes usage fields. + QIF_USAGE; + /// The time limit fields. + QIF_TIMES; + /// All fields. + QIF_ALL; + } +); + +/// Wrapper type for `if_dqblk` +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct Dqblk(libc::dqblk); + +impl Default for Dqblk { + fn default() -> Dqblk { + Dqblk(libc::dqblk { + dqb_bhardlimit: 0, + dqb_bsoftlimit: 0, + dqb_curspace: 0, + dqb_ihardlimit: 0, + dqb_isoftlimit: 0, + dqb_curinodes: 0, + dqb_btime: 0, + dqb_itime: 0, + dqb_valid: 0, + }) + } +} + +impl Dqblk { + /// The absolute limit on disk quota blocks allocated. + pub fn blocks_hard_limit(&self) -> Option<u64> { + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + if valid_fields.contains(QuotaValidFlags::QIF_BLIMITS) { + Some(self.0.dqb_bhardlimit) + } else { + None + } + } + + /// Set the absolute limit on disk quota blocks allocated. + pub fn set_blocks_hard_limit(&mut self, limit: u64) { + self.0.dqb_bhardlimit = limit; + } + + /// Preferred limit on disk quota blocks + pub fn blocks_soft_limit(&self) -> Option<u64> { + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + if valid_fields.contains(QuotaValidFlags::QIF_BLIMITS) { + Some(self.0.dqb_bsoftlimit) + } else { + None + } + } + + /// Set the preferred limit on disk quota blocks allocated. + pub fn set_blocks_soft_limit(&mut self, limit: u64) { + self.0.dqb_bsoftlimit = limit; + } + + /// Current occupied space (bytes). + pub fn occupied_space(&self) -> Option<u64> { + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + if valid_fields.contains(QuotaValidFlags::QIF_SPACE) { + Some(self.0.dqb_curspace) + } else { + None + } + } + + /// Maximum number of allocated inodes. + pub fn inodes_hard_limit(&self) -> Option<u64> { + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + if valid_fields.contains(QuotaValidFlags::QIF_ILIMITS) { + Some(self.0.dqb_ihardlimit) + } else { + None + } + } + + /// Set the maximum number of allocated inodes. + pub fn set_inodes_hard_limit(&mut self, limit: u64) { + self.0.dqb_ihardlimit = limit; + } + + /// Preferred inode limit + pub fn inodes_soft_limit(&self) -> Option<u64> { + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + if valid_fields.contains(QuotaValidFlags::QIF_ILIMITS) { + Some(self.0.dqb_isoftlimit) + } else { + None + } + } + + /// Set the preferred limit of allocated inodes. + pub fn set_inodes_soft_limit(&mut self, limit: u64) { + self.0.dqb_isoftlimit = limit; + } + + /// Current number of allocated inodes. + pub fn allocated_inodes(&self) -> Option<u64> { + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + if valid_fields.contains(QuotaValidFlags::QIF_INODES) { + Some(self.0.dqb_curinodes) + } else { + None + } + } + + /// Time limit for excessive disk use. + pub fn block_time_limit(&self) -> Option<u64> { + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + if valid_fields.contains(QuotaValidFlags::QIF_BTIME) { + Some(self.0.dqb_btime) + } else { + None + } + } + + /// Set the time limit for excessive disk use. + pub fn set_block_time_limit(&mut self, limit: u64) { + self.0.dqb_btime = limit; + } + + /// Time limit for excessive files. + pub fn inode_time_limit(&self) -> Option<u64> { + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + if valid_fields.contains(QuotaValidFlags::QIF_ITIME) { + Some(self.0.dqb_itime) + } else { + None + } + } + + /// Set the time limit for excessive files. + pub fn set_inode_time_limit(&mut self, limit: u64) { + self.0.dqb_itime = limit; + } +} + +fn quotactl<P: ?Sized + NixPath>( + cmd: QuotaCmd, + special: Option<&P>, + id: c_int, + addr: *mut c_char, +) -> Result<()> { + unsafe { + Errno::clear(); + let res = match special { + Some(dev) => dev.with_nix_path(|path| { + libc::quotactl(cmd.as_int(), path.as_ptr(), id, addr) + }), + None => Ok(libc::quotactl(cmd.as_int(), ptr::null(), id, addr)), + }?; + + Errno::result(res).map(drop) + } +} + +/// Turn on disk quotas for a block device. +pub fn quotactl_on<P: ?Sized + NixPath>( + which: QuotaType, + special: &P, + format: QuotaFmt, + quota_file: &P, +) -> Result<()> { + quota_file.with_nix_path(|path| { + let mut path_copy = path.to_bytes_with_nul().to_owned(); + let p: *mut c_char = path_copy.as_mut_ptr() as *mut c_char; + quotactl( + QuotaCmd(QuotaSubCmd::Q_QUOTAON, which), + Some(special), + format as c_int, + p, + ) + })? +} + +/// Disable disk quotas for a block device. +pub fn quotactl_off<P: ?Sized + NixPath>( + which: QuotaType, + special: &P, +) -> Result<()> { + quotactl( + QuotaCmd(QuotaSubCmd::Q_QUOTAOFF, which), + Some(special), + 0, + ptr::null_mut(), + ) +} + +/// Update the on-disk copy of quota usages for a filesystem. +/// +/// If `special` is `None`, then all file systems with active quotas are sync'd. +pub fn quotactl_sync<P: ?Sized + NixPath>( + which: QuotaType, + special: Option<&P>, +) -> Result<()> { + quotactl( + QuotaCmd(QuotaSubCmd::Q_SYNC, which), + special, + 0, + ptr::null_mut(), + ) +} + +/// Get disk quota limits and current usage for the given user/group id. +pub fn quotactl_get<P: ?Sized + NixPath>( + which: QuotaType, + special: &P, + id: c_int, +) -> Result<Dqblk> { + let mut dqblk = mem::MaybeUninit::uninit(); + quotactl( + QuotaCmd(QuotaSubCmd::Q_GETQUOTA, which), + Some(special), + id, + dqblk.as_mut_ptr() as *mut c_char, + )?; + Ok(unsafe { Dqblk(dqblk.assume_init()) }) +} + +/// Configure quota values for the specified fields for a given user/group id. +pub fn quotactl_set<P: ?Sized + NixPath>( + which: QuotaType, + special: &P, + id: c_int, + dqblk: &Dqblk, + fields: QuotaValidFlags, +) -> Result<()> { + let mut dqblk_copy = *dqblk; + dqblk_copy.0.dqb_valid = fields.bits(); + quotactl( + QuotaCmd(QuotaSubCmd::Q_SETQUOTA, which), + Some(special), + id, + &mut dqblk_copy as *mut _ as *mut c_char, + ) +} diff --git a/third_party/rust/nix/src/sys/reboot.rs b/third_party/rust/nix/src/sys/reboot.rs new file mode 100644 index 0000000000..02d98162bd --- /dev/null +++ b/third_party/rust/nix/src/sys/reboot.rs @@ -0,0 +1,48 @@ +//! Reboot/shutdown or enable/disable Ctrl-Alt-Delete. + +use crate::errno::Errno; +use crate::Result; +use std::convert::Infallible; +use std::mem::drop; + +libc_enum! { + /// How exactly should the system be rebooted. + /// + /// See [`set_cad_enabled()`](fn.set_cad_enabled.html) for + /// enabling/disabling Ctrl-Alt-Delete. + #[repr(i32)] + #[non_exhaustive] + pub enum RebootMode { + /// Halt the system. + RB_HALT_SYSTEM, + /// Execute a kernel that has been loaded earlier with + /// [`kexec_load(2)`](https://man7.org/linux/man-pages/man2/kexec_load.2.html). + RB_KEXEC, + /// Stop the system and switch off power, if possible. + RB_POWER_OFF, + /// Restart the system. + RB_AUTOBOOT, + // we do not support Restart2. + /// Suspend the system using software suspend. + RB_SW_SUSPEND, + } +} + +/// Reboots or shuts down the system. +pub fn reboot(how: RebootMode) -> Result<Infallible> { + unsafe { libc::reboot(how as libc::c_int) }; + Err(Errno::last()) +} + +/// Enable or disable the reboot keystroke (Ctrl-Alt-Delete). +/// +/// Corresponds to calling `reboot(RB_ENABLE_CAD)` or `reboot(RB_DISABLE_CAD)` in C. +pub fn set_cad_enabled(enable: bool) -> Result<()> { + let cmd = if enable { + libc::RB_ENABLE_CAD + } else { + libc::RB_DISABLE_CAD + }; + let res = unsafe { libc::reboot(cmd) }; + Errno::result(res).map(drop) +} diff --git a/third_party/rust/nix/src/sys/resource.rs b/third_party/rust/nix/src/sys/resource.rs new file mode 100644 index 0000000000..8927737763 --- /dev/null +++ b/third_party/rust/nix/src/sys/resource.rs @@ -0,0 +1,443 @@ +//! Configure the process resource limits. +use cfg_if::cfg_if; +use libc::{c_int, c_long, rusage}; + +use crate::errno::Errno; +use crate::sys::time::TimeVal; +use crate::Result; +pub use libc::rlim_t; +pub use libc::RLIM_INFINITY; +use std::mem; + +cfg_if! { + if #[cfg(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))]{ + use libc::{__rlimit_resource_t, rlimit}; + } else if #[cfg(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "macos", + target_os = "ios", + target_os = "android", + target_os = "dragonfly", + all(target_os = "linux", not(target_env = "gnu")) + ))]{ + use libc::rlimit; + } +} + +libc_enum! { + /// Types of process resources. + /// + /// The Resource enum is platform dependent. Check different platform + /// manuals for more details. Some platform links have been provided for + /// easier reference (non-exhaustive). + /// + /// * [Linux](https://man7.org/linux/man-pages/man2/getrlimit.2.html) + /// * [FreeBSD](https://www.freebsd.org/cgi/man.cgi?query=setrlimit) + /// * [NetBSD](https://man.netbsd.org/setrlimit.2) + + // linux-gnu uses u_int as resource enum, which is implemented in libc as + // well. + // + // https://gcc.gnu.org/legacy-ml/gcc/2015-08/msg00441.html + // https://github.com/rust-lang/libc/blob/master/src/unix/linux_like/linux/gnu/mod.rs + #[cfg_attr(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")), repr(u32))] + #[cfg_attr(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "macos", + target_os = "ios", + target_os = "android", + target_os = "dragonfly", + all(target_os = "linux", not(any(target_env = "gnu", target_env = "uclibc"))) + ), repr(i32))] + #[non_exhaustive] + pub enum Resource { + #[cfg(not(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The maximum amount (in bytes) of virtual memory the process is + /// allowed to map. + RLIMIT_AS, + /// The largest size (in bytes) core(5) file that may be created. + RLIMIT_CORE, + /// The maximum amount of cpu time (in seconds) to be used by each + /// process. + RLIMIT_CPU, + /// The maximum size (in bytes) of the data segment for a process + RLIMIT_DATA, + /// The largest size (in bytes) file that may be created. + RLIMIT_FSIZE, + /// The maximum number of open files for this process. + RLIMIT_NOFILE, + /// The maximum size (in bytes) of the stack segment for a process. + RLIMIT_STACK, + + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The maximum number of kqueues this user id is allowed to create. + RLIMIT_KQUEUES, + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// A limit on the combined number of flock locks and fcntl leases that + /// this process may establish. + RLIMIT_LOCKS, + + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "openbsd", + target_os = "linux", + target_os = "netbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The maximum size (in bytes) which a process may lock into memory + /// using the mlock(2) system call. + RLIMIT_MEMLOCK, + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// A limit on the number of bytes that can be allocated for POSIX + /// message queues for the real user ID of the calling process. + RLIMIT_MSGQUEUE, + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// A ceiling to which the process's nice value can be raised using + /// setpriority or nice. + RLIMIT_NICE, + + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The maximum number of simultaneous processes for this user id. + RLIMIT_NPROC, + + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The maximum number of pseudo-terminals this user id is allowed to + /// create. + RLIMIT_NPTS, + + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// When there is memory pressure and swap is available, prioritize + /// eviction of a process' resident pages beyond this amount (in bytes). + RLIMIT_RSS, + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// A ceiling on the real-time priority that may be set for this process + /// using sched_setscheduler and sched_set‐ param. + RLIMIT_RTPRIO, + + #[cfg(any(target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// A limit (in microseconds) on the amount of CPU time that a process + /// scheduled under a real-time scheduling policy may con‐ sume without + /// making a blocking system call. + RLIMIT_RTTIME, + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// A limit on the number of signals that may be queued for the real + /// user ID of the calling process. + RLIMIT_SIGPENDING, + + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The maximum size (in bytes) of socket buffer usage for this user. + RLIMIT_SBSIZE, + + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The maximum size (in bytes) of the swap space that may be reserved + /// or used by all of this user id's processes. + RLIMIT_SWAP, + + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// An alias for RLIMIT_AS. + RLIMIT_VMEM, + } +} + +/// Get the current processes resource limits +/// +/// The special value [`RLIM_INFINITY`] indicates that no limit will be +/// enforced. +/// +/// # Parameters +/// +/// * `resource`: The [`Resource`] that we want to get the limits of. +/// +/// # Examples +/// +/// ``` +/// # use nix::sys::resource::{getrlimit, Resource}; +/// +/// let (soft_limit, hard_limit) = getrlimit(Resource::RLIMIT_NOFILE).unwrap(); +/// println!("current soft_limit: {}", soft_limit); +/// println!("current hard_limit: {}", hard_limit); +/// ``` +/// +/// # References +/// +/// [getrlimit(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html#tag_16_215) +/// +/// [`Resource`]: enum.Resource.html +pub fn getrlimit(resource: Resource) -> Result<(rlim_t, rlim_t)> { + let mut old_rlim = mem::MaybeUninit::<rlimit>::uninit(); + + cfg_if! { + if #[cfg(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))]{ + let res = unsafe { libc::getrlimit(resource as __rlimit_resource_t, old_rlim.as_mut_ptr()) }; + } else { + let res = unsafe { libc::getrlimit(resource as c_int, old_rlim.as_mut_ptr()) }; + } + } + + Errno::result(res).map(|_| { + let rlimit { rlim_cur, rlim_max } = unsafe { old_rlim.assume_init() }; + (rlim_cur, rlim_max) + }) +} + +/// Set the current processes resource limits +/// +/// # Parameters +/// +/// * `resource`: The [`Resource`] that we want to set the limits of. +/// * `soft_limit`: The value that the kernel enforces for the corresponding +/// resource. +/// * `hard_limit`: The ceiling for the soft limit. Must be lower or equal to +/// the current hard limit for non-root users. +/// +/// The special value [`RLIM_INFINITY`] indicates that no limit will be +/// enforced. +/// +/// # Examples +/// +/// ``` +/// # use nix::sys::resource::{setrlimit, Resource}; +/// +/// let soft_limit = 512; +/// let hard_limit = 1024; +/// setrlimit(Resource::RLIMIT_NOFILE, soft_limit, hard_limit).unwrap(); +/// ``` +/// +/// # References +/// +/// [setrlimit(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html#tag_16_215) +/// +/// [`Resource`]: enum.Resource.html +/// +/// Note: `setrlimit` provides a safe wrapper to libc's `setrlimit`. +pub fn setrlimit( + resource: Resource, + soft_limit: rlim_t, + hard_limit: rlim_t, +) -> Result<()> { + let new_rlim = rlimit { + rlim_cur: soft_limit, + rlim_max: hard_limit, + }; + cfg_if! { + if #[cfg(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))]{ + let res = unsafe { libc::setrlimit(resource as __rlimit_resource_t, &new_rlim as *const rlimit) }; + }else{ + let res = unsafe { libc::setrlimit(resource as c_int, &new_rlim as *const rlimit) }; + } + } + + Errno::result(res).map(drop) +} + +libc_enum! { + /// Whose resource usage should be returned by [`getrusage`]. + #[repr(i32)] + #[non_exhaustive] + pub enum UsageWho { + /// Resource usage for the current process. + RUSAGE_SELF, + + /// Resource usage for all the children that have terminated and been waited for. + RUSAGE_CHILDREN, + + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Resource usage for the calling thread. + RUSAGE_THREAD, + } +} + +/// Output of `getrusage` with information about resource usage. Some of the fields +/// may be unused in some platforms, and will be always zeroed out. See their manuals +/// for details. +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct Usage(rusage); + +impl AsRef<rusage> for Usage { + fn as_ref(&self) -> &rusage { + &self.0 + } +} + +impl AsMut<rusage> for Usage { + fn as_mut(&mut self) -> &mut rusage { + &mut self.0 + } +} + +impl Usage { + /// Total amount of time spent executing in user mode. + pub fn user_time(&self) -> TimeVal { + TimeVal::from(self.0.ru_utime) + } + + /// Total amount of time spent executing in kernel mode. + pub fn system_time(&self) -> TimeVal { + TimeVal::from(self.0.ru_stime) + } + + /// The resident set size at its peak, in kilobytes. + pub fn max_rss(&self) -> c_long { + self.0.ru_maxrss + } + + /// Integral value expressed in kilobytes times ticks of execution indicating + /// the amount of text memory shared with other processes. + pub fn shared_integral(&self) -> c_long { + self.0.ru_ixrss + } + + /// Integral value expressed in kilobytes times ticks of execution indicating + /// the amount of unshared memory used by data. + pub fn unshared_data_integral(&self) -> c_long { + self.0.ru_idrss + } + + /// Integral value expressed in kilobytes times ticks of execution indicating + /// the amount of unshared memory used for stack space. + pub fn unshared_stack_integral(&self) -> c_long { + self.0.ru_isrss + } + + /// Number of page faults that were served without resorting to I/O, with pages + /// that have been allocated previously by the kernel. + pub fn minor_page_faults(&self) -> c_long { + self.0.ru_minflt + } + + /// Number of page faults that were served through I/O (i.e. swap). + pub fn major_page_faults(&self) -> c_long { + self.0.ru_majflt + } + + /// Number of times all of the memory was fully swapped out. + pub fn full_swaps(&self) -> c_long { + self.0.ru_nswap + } + + /// Number of times a read was done from a block device. + pub fn block_reads(&self) -> c_long { + self.0.ru_inblock + } + + /// Number of times a write was done to a block device. + pub fn block_writes(&self) -> c_long { + self.0.ru_oublock + } + + /// Number of IPC messages sent. + pub fn ipc_sends(&self) -> c_long { + self.0.ru_msgsnd + } + + /// Number of IPC messages received. + pub fn ipc_receives(&self) -> c_long { + self.0.ru_msgrcv + } + + /// Number of signals received. + pub fn signals(&self) -> c_long { + self.0.ru_nsignals + } + + /// Number of times a context switch was voluntarily invoked. + pub fn voluntary_context_switches(&self) -> c_long { + self.0.ru_nvcsw + } + + /// Number of times a context switch was imposed by the kernel (usually due to + /// time slice expiring or preemption by a higher priority process). + pub fn involuntary_context_switches(&self) -> c_long { + self.0.ru_nivcsw + } +} + +/// Get usage information for a process, its children or the current thread +/// +/// Real time information can be obtained for either the current process or (in some +/// systems) thread, but information about children processes is only provided for +/// those that have terminated and been waited for (see [`super::wait::wait`]). +/// +/// Some information may be missing depending on the platform, and the way information +/// is provided for children may also vary. Check the manuals for details. +/// +/// # References +/// +/// * [getrusage(2)](https://pubs.opengroup.org/onlinepubs/009696699/functions/getrusage.html) +/// * [Linux](https://man7.org/linux/man-pages/man2/getrusage.2.html) +/// * [FreeBSD](https://www.freebsd.org/cgi/man.cgi?query=getrusage) +/// * [NetBSD](https://man.netbsd.org/getrusage.2) +/// * [MacOS](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/getrusage.2.html) +/// +/// [`UsageWho`]: enum.UsageWho.html +/// +/// Note: `getrusage` provides a safe wrapper to libc's [`libc::getrusage`]. +pub fn getrusage(who: UsageWho) -> Result<Usage> { + unsafe { + let mut rusage = mem::MaybeUninit::<rusage>::uninit(); + let res = libc::getrusage(who as c_int, rusage.as_mut_ptr()); + Errno::result(res).map(|_| Usage(rusage.assume_init())) + } +} + +#[cfg(test)] +mod test { + use super::{getrusage, UsageWho}; + + #[test] + pub fn test_self_cpu_time() { + // Make sure some CPU time is used. + let mut numbers: Vec<i32> = (1..1_000_000).collect(); + numbers.iter_mut().for_each(|item| *item *= 2); + + // FIXME: this is here to help ensure the compiler does not optimize the whole + // thing away. Replace the assert with test::black_box once stabilized. + assert_eq!(numbers[100..200].iter().sum::<i32>(), 30_100); + + let usage = getrusage(UsageWho::RUSAGE_SELF) + .expect("Failed to call getrusage for SELF"); + let rusage = usage.as_ref(); + + let user = usage.user_time(); + assert!(user.tv_sec() > 0 || user.tv_usec() > 0); + assert_eq!(user.tv_sec(), rusage.ru_utime.tv_sec); + assert_eq!(user.tv_usec(), rusage.ru_utime.tv_usec); + } +} diff --git a/third_party/rust/nix/src/sys/select.rs b/third_party/rust/nix/src/sys/select.rs new file mode 100644 index 0000000000..7a94cff87e --- /dev/null +++ b/third_party/rust/nix/src/sys/select.rs @@ -0,0 +1,455 @@ +//! Portably monitor a group of file descriptors for readiness. +use crate::errno::Errno; +use crate::sys::time::{TimeSpec, TimeVal}; +use crate::Result; +use libc::{self, c_int}; +use std::convert::TryFrom; +use std::iter::FusedIterator; +use std::mem; +use std::ops::Range; +use std::os::unix::io::RawFd; +use std::ptr::{null, null_mut}; + +pub use libc::FD_SETSIZE; + +/// Contains a set of file descriptors used by [`select`] +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct FdSet(libc::fd_set); + +fn assert_fd_valid(fd: RawFd) { + assert!( + usize::try_from(fd).map_or(false, |fd| fd < FD_SETSIZE), + "fd must be in the range 0..FD_SETSIZE", + ); +} + +impl FdSet { + /// Create an empty `FdSet` + pub fn new() -> FdSet { + let mut fdset = mem::MaybeUninit::uninit(); + unsafe { + libc::FD_ZERO(fdset.as_mut_ptr()); + FdSet(fdset.assume_init()) + } + } + + /// Add a file descriptor to an `FdSet` + pub fn insert(&mut self, fd: RawFd) { + assert_fd_valid(fd); + unsafe { libc::FD_SET(fd, &mut self.0) }; + } + + /// Remove a file descriptor from an `FdSet` + pub fn remove(&mut self, fd: RawFd) { + assert_fd_valid(fd); + unsafe { libc::FD_CLR(fd, &mut self.0) }; + } + + /// Test an `FdSet` for the presence of a certain file descriptor. + pub fn contains(&self, fd: RawFd) -> bool { + assert_fd_valid(fd); + unsafe { libc::FD_ISSET(fd, &self.0) } + } + + /// Remove all file descriptors from this `FdSet`. + pub fn clear(&mut self) { + unsafe { libc::FD_ZERO(&mut self.0) }; + } + + /// Finds the highest file descriptor in the set. + /// + /// Returns `None` if the set is empty. + /// + /// This can be used to calculate the `nfds` parameter of the [`select`] function. + /// + /// # Example + /// + /// ``` + /// # use nix::sys::select::FdSet; + /// let mut set = FdSet::new(); + /// set.insert(4); + /// set.insert(9); + /// assert_eq!(set.highest(), Some(9)); + /// ``` + /// + /// [`select`]: fn.select.html + pub fn highest(&self) -> Option<RawFd> { + self.fds(None).next_back() + } + + /// Returns an iterator over the file descriptors in the set. + /// + /// For performance, it takes an optional higher bound: the iterator will + /// not return any elements of the set greater than the given file + /// descriptor. + /// + /// # Examples + /// + /// ``` + /// # use nix::sys::select::FdSet; + /// # use std::os::unix::io::RawFd; + /// let mut set = FdSet::new(); + /// set.insert(4); + /// set.insert(9); + /// let fds: Vec<RawFd> = set.fds(None).collect(); + /// assert_eq!(fds, vec![4, 9]); + /// ``` + #[inline] + pub fn fds(&self, highest: Option<RawFd>) -> Fds { + Fds { + set: self, + range: 0..highest.map(|h| h as usize + 1).unwrap_or(FD_SETSIZE), + } + } +} + +impl Default for FdSet { + fn default() -> Self { + Self::new() + } +} + +/// Iterator over `FdSet`. +#[derive(Debug)] +pub struct Fds<'a> { + set: &'a FdSet, + range: Range<usize>, +} + +impl<'a> Iterator for Fds<'a> { + type Item = RawFd; + + fn next(&mut self) -> Option<RawFd> { + for i in &mut self.range { + if self.set.contains(i as RawFd) { + return Some(i as RawFd); + } + } + None + } + + #[inline] + fn size_hint(&self) -> (usize, Option<usize>) { + let (_, upper) = self.range.size_hint(); + (0, upper) + } +} + +impl<'a> DoubleEndedIterator for Fds<'a> { + #[inline] + fn next_back(&mut self) -> Option<RawFd> { + while let Some(i) = self.range.next_back() { + if self.set.contains(i as RawFd) { + return Some(i as RawFd); + } + } + None + } +} + +impl<'a> FusedIterator for Fds<'a> {} + +/// Monitors file descriptors for readiness +/// +/// Returns the total number of ready file descriptors in all sets. The sets are changed so that all +/// file descriptors that are ready for the given operation are set. +/// +/// When this function returns, `timeout` has an implementation-defined value. +/// +/// # Parameters +/// +/// * `nfds`: The highest file descriptor set in any of the passed `FdSet`s, plus 1. If `None`, this +/// is calculated automatically by calling [`FdSet::highest`] on all descriptor sets and adding 1 +/// to the maximum of that. +/// * `readfds`: File descriptors to check for being ready to read. +/// * `writefds`: File descriptors to check for being ready to write. +/// * `errorfds`: File descriptors to check for pending error conditions. +/// * `timeout`: Maximum time to wait for descriptors to become ready (`None` to block +/// indefinitely). +/// +/// # References +/// +/// [select(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/select.html) +/// +/// [`FdSet::highest`]: struct.FdSet.html#method.highest +pub fn select<'a, N, R, W, E, T>( + nfds: N, + readfds: R, + writefds: W, + errorfds: E, + timeout: T, +) -> Result<c_int> +where + N: Into<Option<c_int>>, + R: Into<Option<&'a mut FdSet>>, + W: Into<Option<&'a mut FdSet>>, + E: Into<Option<&'a mut FdSet>>, + T: Into<Option<&'a mut TimeVal>>, +{ + let mut readfds = readfds.into(); + let mut writefds = writefds.into(); + let mut errorfds = errorfds.into(); + let timeout = timeout.into(); + + let nfds = nfds.into().unwrap_or_else(|| { + readfds + .iter_mut() + .chain(writefds.iter_mut()) + .chain(errorfds.iter_mut()) + .map(|set| set.highest().unwrap_or(-1)) + .max() + .unwrap_or(-1) + + 1 + }); + + let readfds = readfds + .map(|set| set as *mut _ as *mut libc::fd_set) + .unwrap_or(null_mut()); + let writefds = writefds + .map(|set| set as *mut _ as *mut libc::fd_set) + .unwrap_or(null_mut()); + let errorfds = errorfds + .map(|set| set as *mut _ as *mut libc::fd_set) + .unwrap_or(null_mut()); + let timeout = timeout + .map(|tv| tv as *mut _ as *mut libc::timeval) + .unwrap_or(null_mut()); + + let res = + unsafe { libc::select(nfds, readfds, writefds, errorfds, timeout) }; + + Errno::result(res) +} + +feature! { +#![feature = "signal"] + +use crate::sys::signal::SigSet; + +/// Monitors file descriptors for readiness with an altered signal mask. +/// +/// Returns the total number of ready file descriptors in all sets. The sets are changed so that all +/// file descriptors that are ready for the given operation are set. +/// +/// When this function returns, the original signal mask is restored. +/// +/// Unlike [`select`](#fn.select), `pselect` does not mutate the `timeout` value. +/// +/// # Parameters +/// +/// * `nfds`: The highest file descriptor set in any of the passed `FdSet`s, plus 1. If `None`, this +/// is calculated automatically by calling [`FdSet::highest`] on all descriptor sets and adding 1 +/// to the maximum of that. +/// * `readfds`: File descriptors to check for read readiness +/// * `writefds`: File descriptors to check for write readiness +/// * `errorfds`: File descriptors to check for pending error conditions. +/// * `timeout`: Maximum time to wait for descriptors to become ready (`None` to block +/// indefinitely). +/// * `sigmask`: Signal mask to activate while waiting for file descriptors to turn +/// ready (`None` to set no alternative signal mask). +/// +/// # References +/// +/// [pselect(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pselect.html) +/// +/// [The new pselect() system call](https://lwn.net/Articles/176911/) +/// +/// [`FdSet::highest`]: struct.FdSet.html#method.highest +pub fn pselect<'a, N, R, W, E, T, S>(nfds: N, + readfds: R, + writefds: W, + errorfds: E, + timeout: T, + sigmask: S) -> Result<c_int> +where + N: Into<Option<c_int>>, + R: Into<Option<&'a mut FdSet>>, + W: Into<Option<&'a mut FdSet>>, + E: Into<Option<&'a mut FdSet>>, + T: Into<Option<&'a TimeSpec>>, + S: Into<Option<&'a SigSet>>, +{ + let mut readfds = readfds.into(); + let mut writefds = writefds.into(); + let mut errorfds = errorfds.into(); + let sigmask = sigmask.into(); + let timeout = timeout.into(); + + let nfds = nfds.into().unwrap_or_else(|| { + readfds.iter_mut() + .chain(writefds.iter_mut()) + .chain(errorfds.iter_mut()) + .map(|set| set.highest().unwrap_or(-1)) + .max() + .unwrap_or(-1) + 1 + }); + + let readfds = readfds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut()); + let writefds = writefds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut()); + let errorfds = errorfds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut()); + let timeout = timeout.map(|ts| ts.as_ref() as *const libc::timespec).unwrap_or(null()); + let sigmask = sigmask.map(|sm| sm.as_ref() as *const libc::sigset_t).unwrap_or(null()); + + let res = unsafe { + libc::pselect(nfds, readfds, writefds, errorfds, timeout, sigmask) + }; + + Errno::result(res) +} +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::sys::time::{TimeVal, TimeValLike}; + use crate::unistd::{pipe, write}; + use std::os::unix::io::RawFd; + + #[test] + fn fdset_insert() { + let mut fd_set = FdSet::new(); + + for i in 0..FD_SETSIZE { + assert!(!fd_set.contains(i as RawFd)); + } + + fd_set.insert(7); + + assert!(fd_set.contains(7)); + } + + #[test] + fn fdset_remove() { + let mut fd_set = FdSet::new(); + + for i in 0..FD_SETSIZE { + assert!(!fd_set.contains(i as RawFd)); + } + + fd_set.insert(7); + fd_set.remove(7); + + for i in 0..FD_SETSIZE { + assert!(!fd_set.contains(i as RawFd)); + } + } + + #[test] + fn fdset_clear() { + let mut fd_set = FdSet::new(); + fd_set.insert(1); + fd_set.insert((FD_SETSIZE / 2) as RawFd); + fd_set.insert((FD_SETSIZE - 1) as RawFd); + + fd_set.clear(); + + for i in 0..FD_SETSIZE { + assert!(!fd_set.contains(i as RawFd)); + } + } + + #[test] + fn fdset_highest() { + let mut set = FdSet::new(); + assert_eq!(set.highest(), None); + set.insert(0); + assert_eq!(set.highest(), Some(0)); + set.insert(90); + assert_eq!(set.highest(), Some(90)); + set.remove(0); + assert_eq!(set.highest(), Some(90)); + set.remove(90); + assert_eq!(set.highest(), None); + + set.insert(4); + set.insert(5); + set.insert(7); + assert_eq!(set.highest(), Some(7)); + } + + #[test] + fn fdset_fds() { + let mut set = FdSet::new(); + assert_eq!(set.fds(None).collect::<Vec<_>>(), vec![]); + set.insert(0); + assert_eq!(set.fds(None).collect::<Vec<_>>(), vec![0]); + set.insert(90); + assert_eq!(set.fds(None).collect::<Vec<_>>(), vec![0, 90]); + + // highest limit + assert_eq!(set.fds(Some(89)).collect::<Vec<_>>(), vec![0]); + assert_eq!(set.fds(Some(90)).collect::<Vec<_>>(), vec![0, 90]); + } + + #[test] + fn test_select() { + let (r1, w1) = pipe().unwrap(); + write(w1, b"hi!").unwrap(); + let (r2, _w2) = pipe().unwrap(); + + let mut fd_set = FdSet::new(); + fd_set.insert(r1); + fd_set.insert(r2); + + let mut timeout = TimeVal::seconds(10); + assert_eq!( + 1, + select(None, &mut fd_set, None, None, &mut timeout).unwrap() + ); + assert!(fd_set.contains(r1)); + assert!(!fd_set.contains(r2)); + } + + #[test] + fn test_select_nfds() { + let (r1, w1) = pipe().unwrap(); + write(w1, b"hi!").unwrap(); + let (r2, _w2) = pipe().unwrap(); + + let mut fd_set = FdSet::new(); + fd_set.insert(r1); + fd_set.insert(r2); + + let mut timeout = TimeVal::seconds(10); + assert_eq!( + 1, + select( + Some(fd_set.highest().unwrap() + 1), + &mut fd_set, + None, + None, + &mut timeout + ) + .unwrap() + ); + assert!(fd_set.contains(r1)); + assert!(!fd_set.contains(r2)); + } + + #[test] + fn test_select_nfds2() { + let (r1, w1) = pipe().unwrap(); + write(w1, b"hi!").unwrap(); + let (r2, _w2) = pipe().unwrap(); + + let mut fd_set = FdSet::new(); + fd_set.insert(r1); + fd_set.insert(r2); + + let mut timeout = TimeVal::seconds(10); + assert_eq!( + 1, + select( + ::std::cmp::max(r1, r2) + 1, + &mut fd_set, + None, + None, + &mut timeout + ) + .unwrap() + ); + assert!(fd_set.contains(r1)); + assert!(!fd_set.contains(r2)); + } +} diff --git a/third_party/rust/nix/src/sys/sendfile.rs b/third_party/rust/nix/src/sys/sendfile.rs new file mode 100644 index 0000000000..fb293a4e74 --- /dev/null +++ b/third_party/rust/nix/src/sys/sendfile.rs @@ -0,0 +1,277 @@ +//! Send data from a file to a socket, bypassing userland. + +use cfg_if::cfg_if; +use std::os::unix::io::RawFd; +use std::ptr; + +use libc::{self, off_t}; + +use crate::errno::Errno; +use crate::Result; + +/// Copy up to `count` bytes to `out_fd` from `in_fd` starting at `offset`. +/// +/// Returns a `Result` with the number of bytes written. +/// +/// If `offset` is `None`, `sendfile` will begin reading at the current offset of `in_fd`and will +/// update the offset of `in_fd`. If `offset` is `Some`, `sendfile` will begin at the specified +/// offset and will not update the offset of `in_fd`. Instead, it will mutate `offset` to point to +/// the byte after the last byte copied. +/// +/// `in_fd` must support `mmap`-like operations and therefore cannot be a socket. +/// +/// For more information, see [the sendfile(2) man page.](https://man7.org/linux/man-pages/man2/sendfile.2.html) +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn sendfile( + out_fd: RawFd, + in_fd: RawFd, + offset: Option<&mut off_t>, + count: usize, +) -> Result<usize> { + let offset = offset + .map(|offset| offset as *mut _) + .unwrap_or(ptr::null_mut()); + let ret = unsafe { libc::sendfile(out_fd, in_fd, offset, count) }; + Errno::result(ret).map(|r| r as usize) +} + +/// Copy up to `count` bytes to `out_fd` from `in_fd` starting at `offset`. +/// +/// Returns a `Result` with the number of bytes written. +/// +/// If `offset` is `None`, `sendfile` will begin reading at the current offset of `in_fd`and will +/// update the offset of `in_fd`. If `offset` is `Some`, `sendfile` will begin at the specified +/// offset and will not update the offset of `in_fd`. Instead, it will mutate `offset` to point to +/// the byte after the last byte copied. +/// +/// `in_fd` must support `mmap`-like operations and therefore cannot be a socket. +/// +/// For more information, see [the sendfile(2) man page.](https://man7.org/linux/man-pages/man2/sendfile.2.html) +#[cfg(target_os = "linux")] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn sendfile64( + out_fd: RawFd, + in_fd: RawFd, + offset: Option<&mut libc::off64_t>, + count: usize, +) -> Result<usize> { + let offset = offset + .map(|offset| offset as *mut _) + .unwrap_or(ptr::null_mut()); + let ret = unsafe { libc::sendfile64(out_fd, in_fd, offset, count) }; + Errno::result(ret).map(|r| r as usize) +} + +cfg_if! { + if #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos"))] { + use std::io::IoSlice; + + #[derive(Clone, Debug)] + struct SendfileHeaderTrailer<'a>( + libc::sf_hdtr, + Option<Vec<IoSlice<'a>>>, + Option<Vec<IoSlice<'a>>>, + ); + + impl<'a> SendfileHeaderTrailer<'a> { + fn new( + headers: Option<&'a [&'a [u8]]>, + trailers: Option<&'a [&'a [u8]]> + ) -> SendfileHeaderTrailer<'a> { + let header_iovecs: Option<Vec<IoSlice<'_>>> = + headers.map(|s| s.iter().map(|b| IoSlice::new(b)).collect()); + let trailer_iovecs: Option<Vec<IoSlice<'_>>> = + trailers.map(|s| s.iter().map(|b| IoSlice::new(b)).collect()); + SendfileHeaderTrailer( + libc::sf_hdtr { + headers: { + header_iovecs + .as_ref() + .map_or(ptr::null(), |v| v.as_ptr()) as *mut libc::iovec + }, + hdr_cnt: header_iovecs.as_ref().map(|v| v.len()).unwrap_or(0) as i32, + trailers: { + trailer_iovecs + .as_ref() + .map_or(ptr::null(), |v| v.as_ptr()) as *mut libc::iovec + }, + trl_cnt: trailer_iovecs.as_ref().map(|v| v.len()).unwrap_or(0) as i32 + }, + header_iovecs, + trailer_iovecs, + ) + } + } + } +} + +cfg_if! { + if #[cfg(target_os = "freebsd")] { + use libc::c_int; + + libc_bitflags!{ + /// Configuration options for [`sendfile`.](fn.sendfile.html) + pub struct SfFlags: c_int { + /// Causes `sendfile` to return EBUSY instead of blocking when attempting to read a + /// busy page. + SF_NODISKIO; + /// Causes `sendfile` to sleep until the network stack releases its reference to the + /// VM pages read. When `sendfile` returns, the data is not guaranteed to have been + /// sent, but it is safe to modify the file. + SF_SYNC; + /// Causes `sendfile` to cache exactly the number of pages specified in the + /// `readahead` parameter, disabling caching heuristics. + SF_USER_READAHEAD; + /// Causes `sendfile` not to cache the data read. + SF_NOCACHE; + } + } + + /// Read up to `count` bytes from `in_fd` starting at `offset` and write to `out_sock`. + /// + /// Returns a `Result` and a count of bytes written. Bytes written may be non-zero even if + /// an error occurs. + /// + /// `in_fd` must describe a regular file or shared memory object. `out_sock` must describe a + /// stream socket. + /// + /// If `offset` falls past the end of the file, the function returns success and zero bytes + /// written. + /// + /// If `count` is `None` or 0, bytes will be read from `in_fd` until reaching the end of + /// file (EOF). + /// + /// `headers` and `trailers` specify optional slices of byte slices to be sent before and + /// after the data read from `in_fd`, respectively. The length of headers and trailers sent + /// is included in the returned count of bytes written. The values of `offset` and `count` + /// do not apply to headers or trailers. + /// + /// `readahead` specifies the minimum number of pages to cache in memory ahead of the page + /// currently being sent. + /// + /// For more information, see + /// [the sendfile(2) man page.](https://www.freebsd.org/cgi/man.cgi?query=sendfile&sektion=2) + #[allow(clippy::too_many_arguments)] + pub fn sendfile( + in_fd: RawFd, + out_sock: RawFd, + offset: off_t, + count: Option<usize>, + headers: Option<&[&[u8]]>, + trailers: Option<&[&[u8]]>, + flags: SfFlags, + readahead: u16 + ) -> (Result<()>, off_t) { + // Readahead goes in upper 16 bits + // Flags goes in lower 16 bits + // see `man 2 sendfile` + let ra32 = u32::from(readahead); + let flags: u32 = (ra32 << 16) | (flags.bits() as u32); + let mut bytes_sent: off_t = 0; + let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers)); + let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.0 as *const libc::sf_hdtr); + let return_code = unsafe { + libc::sendfile(in_fd, + out_sock, + offset, + count.unwrap_or(0), + hdtr_ptr as *mut libc::sf_hdtr, + &mut bytes_sent as *mut off_t, + flags as c_int) + }; + (Errno::result(return_code).and(Ok(())), bytes_sent) + } + } else if #[cfg(target_os = "dragonfly")] { + /// Read up to `count` bytes from `in_fd` starting at `offset` and write to `out_sock`. + /// + /// Returns a `Result` and a count of bytes written. Bytes written may be non-zero even if + /// an error occurs. + /// + /// `in_fd` must describe a regular file. `out_sock` must describe a stream socket. + /// + /// If `offset` falls past the end of the file, the function returns success and zero bytes + /// written. + /// + /// If `count` is `None` or 0, bytes will be read from `in_fd` until reaching the end of + /// file (EOF). + /// + /// `headers` and `trailers` specify optional slices of byte slices to be sent before and + /// after the data read from `in_fd`, respectively. The length of headers and trailers sent + /// is included in the returned count of bytes written. The values of `offset` and `count` + /// do not apply to headers or trailers. + /// + /// For more information, see + /// [the sendfile(2) man page.](https://leaf.dragonflybsd.org/cgi/web-man?command=sendfile§ion=2) + pub fn sendfile( + in_fd: RawFd, + out_sock: RawFd, + offset: off_t, + count: Option<usize>, + headers: Option<&[&[u8]]>, + trailers: Option<&[&[u8]]>, + ) -> (Result<()>, off_t) { + let mut bytes_sent: off_t = 0; + let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers)); + let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.0 as *const libc::sf_hdtr); + let return_code = unsafe { + libc::sendfile(in_fd, + out_sock, + offset, + count.unwrap_or(0), + hdtr_ptr as *mut libc::sf_hdtr, + &mut bytes_sent as *mut off_t, + 0) + }; + (Errno::result(return_code).and(Ok(())), bytes_sent) + } + } else if #[cfg(any(target_os = "ios", target_os = "macos"))] { + /// Read bytes from `in_fd` starting at `offset` and write up to `count` bytes to + /// `out_sock`. + /// + /// Returns a `Result` and a count of bytes written. Bytes written may be non-zero even if + /// an error occurs. + /// + /// `in_fd` must describe a regular file. `out_sock` must describe a stream socket. + /// + /// If `offset` falls past the end of the file, the function returns success and zero bytes + /// written. + /// + /// If `count` is `None` or 0, bytes will be read from `in_fd` until reaching the end of + /// file (EOF). + /// + /// `hdtr` specifies an optional list of headers and trailers to be sent before and after + /// the data read from `in_fd`, respectively. The length of headers and trailers sent is + /// included in the returned count of bytes written. If any headers are specified and + /// `count` is non-zero, the length of the headers will be counted in the limit of total + /// bytes sent. Trailers do not count toward the limit of bytes sent and will always be sent + /// regardless. The value of `offset` does not affect headers or trailers. + /// + /// For more information, see + /// [the sendfile(2) man page.](https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man2/sendfile.2.html) + pub fn sendfile( + in_fd: RawFd, + out_sock: RawFd, + offset: off_t, + count: Option<off_t>, + headers: Option<&[&[u8]]>, + trailers: Option<&[&[u8]]> + ) -> (Result<()>, off_t) { + let mut len = count.unwrap_or(0); + let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers)); + let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.0 as *const libc::sf_hdtr); + let return_code = unsafe { + libc::sendfile(in_fd, + out_sock, + offset, + &mut len as *mut off_t, + hdtr_ptr as *mut libc::sf_hdtr, + 0) + }; + (Errno::result(return_code).and(Ok(())), len) + } + } +} diff --git a/third_party/rust/nix/src/sys/signal.rs b/third_party/rust/nix/src/sys/signal.rs new file mode 100644 index 0000000000..d3746e609a --- /dev/null +++ b/third_party/rust/nix/src/sys/signal.rs @@ -0,0 +1,1348 @@ +// Portions of this file are Copyright 2014 The Rust Project Developers. +// See https://www.rust-lang.org/policies/licenses. + +//! Operating system signals. + +use crate::errno::Errno; +use crate::{Error, Result}; +use cfg_if::cfg_if; +use std::fmt; +use std::mem; +#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] +use std::os::unix::io::RawFd; +use std::ptr; +use std::str::FromStr; + +#[cfg(not(any(target_os = "openbsd", target_os = "redox")))] +#[cfg(any(feature = "aio", feature = "signal"))] +pub use self::sigevent::*; + +#[cfg(any(feature = "aio", feature = "process", feature = "signal"))] +libc_enum! { + /// Types of operating system signals + // Currently there is only one definition of c_int in libc, as well as only one + // type for signal constants. + // We would prefer to use the libc::c_int alias in the repr attribute. Unfortunately + // this is not (yet) possible. + #[repr(i32)] + #[non_exhaustive] + #[cfg_attr(docsrs, doc(cfg(any(feature = "aio", feature = "signal"))))] + pub enum Signal { + /// Hangup + SIGHUP, + /// Interrupt + SIGINT, + /// Quit + SIGQUIT, + /// Illegal instruction (not reset when caught) + SIGILL, + /// Trace trap (not reset when caught) + SIGTRAP, + /// Abort + SIGABRT, + /// Bus error + SIGBUS, + /// Floating point exception + SIGFPE, + /// Kill (cannot be caught or ignored) + SIGKILL, + /// User defined signal 1 + SIGUSR1, + /// Segmentation violation + SIGSEGV, + /// User defined signal 2 + SIGUSR2, + /// Write on a pipe with no one to read it + SIGPIPE, + /// Alarm clock + SIGALRM, + /// Software termination signal from kill + SIGTERM, + /// Stack fault (obsolete) + #[cfg(all(any(target_os = "android", target_os = "emscripten", + target_os = "fuchsia", target_os = "linux"), + not(any(target_arch = "mips", target_arch = "mips64", + target_arch = "sparc64"))))] + SIGSTKFLT, + /// To parent on child stop or exit + SIGCHLD, + /// Continue a stopped process + SIGCONT, + /// Sendable stop signal not from tty + SIGSTOP, + /// Stop signal from tty + SIGTSTP, + /// To readers pgrp upon background tty read + SIGTTIN, + /// Like TTIN if (tp->t_local<OSTOP) + SIGTTOU, + /// Urgent condition on IO channel + SIGURG, + /// Exceeded CPU time limit + SIGXCPU, + /// Exceeded file size limit + SIGXFSZ, + /// Virtual time alarm + SIGVTALRM, + /// Profiling time alarm + SIGPROF, + /// Window size changes + SIGWINCH, + /// Input/output possible signal + #[cfg(not(target_os = "haiku"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + SIGIO, + #[cfg(any(target_os = "android", target_os = "emscripten", + target_os = "fuchsia", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Power failure imminent. + SIGPWR, + /// Bad system call + SIGSYS, + #[cfg(not(any(target_os = "android", target_os = "emscripten", + target_os = "fuchsia", target_os = "linux", + target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Emulator trap + SIGEMT, + #[cfg(not(any(target_os = "android", target_os = "emscripten", + target_os = "fuchsia", target_os = "linux", + target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Information request + SIGINFO, + } + impl TryFrom<i32> +} + +#[cfg(feature = "signal")] +impl FromStr for Signal { + type Err = Error; + fn from_str(s: &str) -> Result<Signal> { + Ok(match s { + "SIGHUP" => Signal::SIGHUP, + "SIGINT" => Signal::SIGINT, + "SIGQUIT" => Signal::SIGQUIT, + "SIGILL" => Signal::SIGILL, + "SIGTRAP" => Signal::SIGTRAP, + "SIGABRT" => Signal::SIGABRT, + "SIGBUS" => Signal::SIGBUS, + "SIGFPE" => Signal::SIGFPE, + "SIGKILL" => Signal::SIGKILL, + "SIGUSR1" => Signal::SIGUSR1, + "SIGSEGV" => Signal::SIGSEGV, + "SIGUSR2" => Signal::SIGUSR2, + "SIGPIPE" => Signal::SIGPIPE, + "SIGALRM" => Signal::SIGALRM, + "SIGTERM" => Signal::SIGTERM, + #[cfg(all( + any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux" + ), + not(any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "sparc64" + )) + ))] + "SIGSTKFLT" => Signal::SIGSTKFLT, + "SIGCHLD" => Signal::SIGCHLD, + "SIGCONT" => Signal::SIGCONT, + "SIGSTOP" => Signal::SIGSTOP, + "SIGTSTP" => Signal::SIGTSTP, + "SIGTTIN" => Signal::SIGTTIN, + "SIGTTOU" => Signal::SIGTTOU, + "SIGURG" => Signal::SIGURG, + "SIGXCPU" => Signal::SIGXCPU, + "SIGXFSZ" => Signal::SIGXFSZ, + "SIGVTALRM" => Signal::SIGVTALRM, + "SIGPROF" => Signal::SIGPROF, + "SIGWINCH" => Signal::SIGWINCH, + #[cfg(not(target_os = "haiku"))] + "SIGIO" => Signal::SIGIO, + #[cfg(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux" + ))] + "SIGPWR" => Signal::SIGPWR, + "SIGSYS" => Signal::SIGSYS, + #[cfg(not(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux", + target_os = "redox", + target_os = "haiku" + )))] + "SIGEMT" => Signal::SIGEMT, + #[cfg(not(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux", + target_os = "redox", + target_os = "haiku" + )))] + "SIGINFO" => Signal::SIGINFO, + _ => return Err(Errno::EINVAL), + }) + } +} + +#[cfg(feature = "signal")] +impl Signal { + /// Returns name of signal. + /// + /// This function is equivalent to `<Signal as AsRef<str>>::as_ref()`, + /// with difference that returned string is `'static` + /// and not bound to `self`'s lifetime. + pub const fn as_str(self) -> &'static str { + match self { + Signal::SIGHUP => "SIGHUP", + Signal::SIGINT => "SIGINT", + Signal::SIGQUIT => "SIGQUIT", + Signal::SIGILL => "SIGILL", + Signal::SIGTRAP => "SIGTRAP", + Signal::SIGABRT => "SIGABRT", + Signal::SIGBUS => "SIGBUS", + Signal::SIGFPE => "SIGFPE", + Signal::SIGKILL => "SIGKILL", + Signal::SIGUSR1 => "SIGUSR1", + Signal::SIGSEGV => "SIGSEGV", + Signal::SIGUSR2 => "SIGUSR2", + Signal::SIGPIPE => "SIGPIPE", + Signal::SIGALRM => "SIGALRM", + Signal::SIGTERM => "SIGTERM", + #[cfg(all( + any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux" + ), + not(any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "sparc64" + )) + ))] + Signal::SIGSTKFLT => "SIGSTKFLT", + Signal::SIGCHLD => "SIGCHLD", + Signal::SIGCONT => "SIGCONT", + Signal::SIGSTOP => "SIGSTOP", + Signal::SIGTSTP => "SIGTSTP", + Signal::SIGTTIN => "SIGTTIN", + Signal::SIGTTOU => "SIGTTOU", + Signal::SIGURG => "SIGURG", + Signal::SIGXCPU => "SIGXCPU", + Signal::SIGXFSZ => "SIGXFSZ", + Signal::SIGVTALRM => "SIGVTALRM", + Signal::SIGPROF => "SIGPROF", + Signal::SIGWINCH => "SIGWINCH", + #[cfg(not(target_os = "haiku"))] + Signal::SIGIO => "SIGIO", + #[cfg(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux" + ))] + Signal::SIGPWR => "SIGPWR", + Signal::SIGSYS => "SIGSYS", + #[cfg(not(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux", + target_os = "redox", + target_os = "haiku" + )))] + Signal::SIGEMT => "SIGEMT", + #[cfg(not(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux", + target_os = "redox", + target_os = "haiku" + )))] + Signal::SIGINFO => "SIGINFO", + } + } +} + +#[cfg(feature = "signal")] +impl AsRef<str> for Signal { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +#[cfg(feature = "signal")] +impl fmt::Display for Signal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(self.as_ref()) + } +} + +#[cfg(feature = "signal")] +pub use self::Signal::*; + +#[cfg(target_os = "redox")] +#[cfg(feature = "signal")] +const SIGNALS: [Signal; 29] = [ + SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL, + SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGCONT, + SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, + SIGPROF, SIGWINCH, SIGIO, SIGSYS, +]; +#[cfg(target_os = "haiku")] +#[cfg(feature = "signal")] +const SIGNALS: [Signal; 28] = [ + SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL, + SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGCONT, + SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, + SIGPROF, SIGWINCH, SIGSYS, +]; +#[cfg(all( + any( + target_os = "linux", + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia" + ), + not(any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "sparc64" + )) +))] +#[cfg(feature = "signal")] +const SIGNALS: [Signal; 31] = [ + SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL, + SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGSTKFLT, SIGCHLD, + SIGCONT, SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ, + SIGVTALRM, SIGPROF, SIGWINCH, SIGIO, SIGPWR, SIGSYS, +]; +#[cfg(all( + any( + target_os = "linux", + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia" + ), + any(target_arch = "mips", target_arch = "mips64", target_arch = "sparc64") +))] +#[cfg(feature = "signal")] +const SIGNALS: [Signal; 30] = [ + SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL, + SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGCONT, + SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, + SIGPROF, SIGWINCH, SIGIO, SIGPWR, SIGSYS, +]; +#[cfg(not(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia", + target_os = "emscripten", + target_os = "redox", + target_os = "haiku" +)))] +#[cfg(feature = "signal")] +const SIGNALS: [Signal; 31] = [ + SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL, + SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGCONT, + SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, + SIGPROF, SIGWINCH, SIGIO, SIGSYS, SIGEMT, SIGINFO, +]; + +feature! { +#![feature = "signal"] + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +/// Iterate through all signals defined by this operating system +pub struct SignalIterator { + next: usize, +} + +impl Iterator for SignalIterator { + type Item = Signal; + + fn next(&mut self) -> Option<Signal> { + if self.next < SIGNALS.len() { + let next_signal = SIGNALS[self.next]; + self.next += 1; + Some(next_signal) + } else { + None + } + } +} + +impl Signal { + /// Iterate through all signals defined by this OS + pub const fn iterator() -> SignalIterator { + SignalIterator{next: 0} + } +} + +/// Alias for [`SIGABRT`] +pub const SIGIOT : Signal = SIGABRT; +/// Alias for [`SIGIO`] +#[cfg(not(target_os = "haiku"))] +pub const SIGPOLL : Signal = SIGIO; +/// Alias for [`SIGSYS`] +pub const SIGUNUSED : Signal = SIGSYS; + +cfg_if! { + if #[cfg(target_os = "redox")] { + type SaFlags_t = libc::c_ulong; + } else if #[cfg(target_env = "uclibc")] { + type SaFlags_t = libc::c_ulong; + } else { + type SaFlags_t = libc::c_int; + } +} +} + +#[cfg(feature = "signal")] +libc_bitflags! { + /// Controls the behavior of a [`SigAction`] + #[cfg_attr(docsrs, doc(cfg(feature = "signal")))] + pub struct SaFlags: SaFlags_t { + /// When catching a [`Signal::SIGCHLD`] signal, the signal will be + /// generated only when a child process exits, not when a child process + /// stops. + SA_NOCLDSTOP; + /// When catching a [`Signal::SIGCHLD`] signal, the system will not + /// create zombie processes when children of the calling process exit. + SA_NOCLDWAIT; + /// Further occurrences of the delivered signal are not masked during + /// the execution of the handler. + SA_NODEFER; + /// The system will deliver the signal to the process on a signal stack, + /// specified by each thread with sigaltstack(2). + SA_ONSTACK; + /// The handler is reset back to the default at the moment the signal is + /// delivered. + SA_RESETHAND; + /// Requests that certain system calls restart if interrupted by this + /// signal. See the man page for complete details. + SA_RESTART; + /// This flag is controlled internally by Nix. + SA_SIGINFO; + } +} + +#[cfg(feature = "signal")] +libc_enum! { + /// Specifies how certain functions should manipulate a signal mask + #[repr(i32)] + #[non_exhaustive] + #[cfg_attr(docsrs, doc(cfg(feature = "signal")))] + pub enum SigmaskHow { + /// The new mask is the union of the current mask and the specified set. + SIG_BLOCK, + /// The new mask is the intersection of the current mask and the + /// complement of the specified set. + SIG_UNBLOCK, + /// The current mask is replaced by the specified set. + SIG_SETMASK, + } +} + +feature! { +#![feature = "signal"] + +use crate::unistd::Pid; +use std::iter::Extend; +use std::iter::FromIterator; +use std::iter::IntoIterator; + +/// Specifies a set of [`Signal`]s that may be blocked, waited for, etc. +// We are using `transparent` here to be super sure that `SigSet` +// is represented exactly like the `sigset_t` struct from C. +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct SigSet { + sigset: libc::sigset_t +} + +impl SigSet { + /// Initialize to include all signals. + #[doc(alias("sigfillset"))] + pub fn all() -> SigSet { + let mut sigset = mem::MaybeUninit::uninit(); + let _ = unsafe { libc::sigfillset(sigset.as_mut_ptr()) }; + + unsafe{ SigSet { sigset: sigset.assume_init() } } + } + + /// Initialize to include nothing. + #[doc(alias("sigemptyset"))] + pub fn empty() -> SigSet { + let mut sigset = mem::MaybeUninit::uninit(); + let _ = unsafe { libc::sigemptyset(sigset.as_mut_ptr()) }; + + unsafe{ SigSet { sigset: sigset.assume_init() } } + } + + /// Add the specified signal to the set. + #[doc(alias("sigaddset"))] + pub fn add(&mut self, signal: Signal) { + unsafe { libc::sigaddset(&mut self.sigset as *mut libc::sigset_t, signal as libc::c_int) }; + } + + /// Remove all signals from this set. + #[doc(alias("sigemptyset"))] + pub fn clear(&mut self) { + unsafe { libc::sigemptyset(&mut self.sigset as *mut libc::sigset_t) }; + } + + /// Remove the specified signal from this set. + #[doc(alias("sigdelset"))] + pub fn remove(&mut self, signal: Signal) { + unsafe { libc::sigdelset(&mut self.sigset as *mut libc::sigset_t, signal as libc::c_int) }; + } + + /// Return whether this set includes the specified signal. + #[doc(alias("sigismember"))] + pub fn contains(&self, signal: Signal) -> bool { + let res = unsafe { libc::sigismember(&self.sigset as *const libc::sigset_t, signal as libc::c_int) }; + + match res { + 1 => true, + 0 => false, + _ => unreachable!("unexpected value from sigismember"), + } + } + + /// Returns an iterator that yields the signals contained in this set. + pub fn iter(&self) -> SigSetIter<'_> { + self.into_iter() + } + + /// Gets the currently blocked (masked) set of signals for the calling thread. + pub fn thread_get_mask() -> Result<SigSet> { + let mut oldmask = mem::MaybeUninit::uninit(); + do_pthread_sigmask(SigmaskHow::SIG_SETMASK, None, Some(oldmask.as_mut_ptr()))?; + Ok(unsafe{ SigSet{sigset: oldmask.assume_init()}}) + } + + /// Sets the set of signals as the signal mask for the calling thread. + pub fn thread_set_mask(&self) -> Result<()> { + pthread_sigmask(SigmaskHow::SIG_SETMASK, Some(self), None) + } + + /// Adds the set of signals to the signal mask for the calling thread. + pub fn thread_block(&self) -> Result<()> { + pthread_sigmask(SigmaskHow::SIG_BLOCK, Some(self), None) + } + + /// Removes the set of signals from the signal mask for the calling thread. + pub fn thread_unblock(&self) -> Result<()> { + pthread_sigmask(SigmaskHow::SIG_UNBLOCK, Some(self), None) + } + + /// Sets the set of signals as the signal mask, and returns the old mask. + pub fn thread_swap_mask(&self, how: SigmaskHow) -> Result<SigSet> { + let mut oldmask = mem::MaybeUninit::uninit(); + do_pthread_sigmask(how, Some(self), Some(oldmask.as_mut_ptr()))?; + Ok(unsafe{ SigSet{sigset: oldmask.assume_init()}}) + } + + /// Suspends execution of the calling thread until one of the signals in the + /// signal mask becomes pending, and returns the accepted signal. + #[cfg(not(target_os = "redox"))] // RedoxFS does not yet support sigwait + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn wait(&self) -> Result<Signal> { + use std::convert::TryFrom; + + let mut signum = mem::MaybeUninit::uninit(); + let res = unsafe { libc::sigwait(&self.sigset as *const libc::sigset_t, signum.as_mut_ptr()) }; + + Errno::result(res).map(|_| unsafe { + Signal::try_from(signum.assume_init()).unwrap() + }) + } + + /// Converts a `libc::sigset_t` object to a [`SigSet`] without checking whether the + /// `libc::sigset_t` is already initialized. + /// + /// # Safety + /// + /// The `sigset` passed in must be a valid an initialized `libc::sigset_t` by calling either + /// [`sigemptyset(3)`](https://man7.org/linux/man-pages/man3/sigemptyset.3p.html) or + /// [`sigfillset(3)`](https://man7.org/linux/man-pages/man3/sigfillset.3p.html). + /// Otherwise, the results are undefined. + pub unsafe fn from_sigset_t_unchecked(sigset: libc::sigset_t) -> SigSet { + SigSet { sigset } + } +} + +impl AsRef<libc::sigset_t> for SigSet { + fn as_ref(&self) -> &libc::sigset_t { + &self.sigset + } +} + +// TODO: Consider specialization for the case where T is &SigSet and libc::sigorset is available. +impl Extend<Signal> for SigSet { + fn extend<T>(&mut self, iter: T) + where T: IntoIterator<Item = Signal> { + for signal in iter { + self.add(signal); + } + } +} + +impl FromIterator<Signal> for SigSet { + fn from_iter<T>(iter: T) -> Self + where T: IntoIterator<Item = Signal> { + let mut sigset = SigSet::empty(); + sigset.extend(iter); + sigset + } +} + +/// Iterator for a [`SigSet`]. +/// +/// Call [`SigSet::iter`] to create an iterator. +#[derive(Clone, Debug)] +pub struct SigSetIter<'a> { + sigset: &'a SigSet, + inner: SignalIterator, +} + +impl Iterator for SigSetIter<'_> { + type Item = Signal; + fn next(&mut self) -> Option<Signal> { + loop { + match self.inner.next() { + None => return None, + Some(signal) if self.sigset.contains(signal) => return Some(signal), + Some(_signal) => continue, + } + } + } +} + +impl<'a> IntoIterator for &'a SigSet { + type Item = Signal; + type IntoIter = SigSetIter<'a>; + fn into_iter(self) -> Self::IntoIter { + SigSetIter { sigset: self, inner: Signal::iterator() } + } +} + +/// A signal handler. +#[allow(unknown_lints)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum SigHandler { + /// Default signal handling. + SigDfl, + /// Request that the signal be ignored. + SigIgn, + /// Use the given signal-catching function, which takes in the signal. + Handler(extern fn(libc::c_int)), + /// Use the given signal-catching function, which takes in the signal, information about how + /// the signal was generated, and a pointer to the threads `ucontext_t`. + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + SigAction(extern fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void)) +} + +/// Action to take on receipt of a signal. Corresponds to `sigaction`. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct SigAction { + sigaction: libc::sigaction +} + +impl SigAction { + /// Creates a new action. + /// + /// The `SA_SIGINFO` bit in the `flags` argument is ignored (it will be set only if `handler` + /// is the `SigAction` variant). `mask` specifies other signals to block during execution of + /// the signal-catching function. + pub fn new(handler: SigHandler, flags: SaFlags, mask: SigSet) -> SigAction { + unsafe fn install_sig(p: *mut libc::sigaction, handler: SigHandler) { + (*p).sa_sigaction = match handler { + SigHandler::SigDfl => libc::SIG_DFL, + SigHandler::SigIgn => libc::SIG_IGN, + SigHandler::Handler(f) => f as *const extern fn(libc::c_int) as usize, + #[cfg(not(target_os = "redox"))] + SigHandler::SigAction(f) => f as *const extern fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void) as usize, + }; + } + + let mut s = mem::MaybeUninit::<libc::sigaction>::uninit(); + unsafe { + let p = s.as_mut_ptr(); + install_sig(p, handler); + (*p).sa_flags = match handler { + #[cfg(not(target_os = "redox"))] + SigHandler::SigAction(_) => (flags | SaFlags::SA_SIGINFO).bits(), + _ => (flags - SaFlags::SA_SIGINFO).bits(), + }; + (*p).sa_mask = mask.sigset; + + SigAction { sigaction: s.assume_init() } + } + } + + /// Returns the flags set on the action. + pub fn flags(&self) -> SaFlags { + SaFlags::from_bits_truncate(self.sigaction.sa_flags) + } + + /// Returns the set of signals that are blocked during execution of the action's + /// signal-catching function. + pub fn mask(&self) -> SigSet { + SigSet { sigset: self.sigaction.sa_mask } + } + + /// Returns the action's handler. + pub fn handler(&self) -> SigHandler { + match self.sigaction.sa_sigaction { + libc::SIG_DFL => SigHandler::SigDfl, + libc::SIG_IGN => SigHandler::SigIgn, + #[cfg(not(target_os = "redox"))] + p if self.flags().contains(SaFlags::SA_SIGINFO) => + SigHandler::SigAction( + // Safe for one of two reasons: + // * The SigHandler was created by SigHandler::new, in which + // case the pointer is correct, or + // * The SigHandler was created by signal or sigaction, which + // are unsafe functions, so the caller should've somehow + // ensured that it is correctly initialized. + unsafe{ + *(&p as *const usize + as *const extern fn(_, _, _)) + } + as extern fn(_, _, _)), + p => SigHandler::Handler( + // Safe for one of two reasons: + // * The SigHandler was created by SigHandler::new, in which + // case the pointer is correct, or + // * The SigHandler was created by signal or sigaction, which + // are unsafe functions, so the caller should've somehow + // ensured that it is correctly initialized. + unsafe{ + *(&p as *const usize + as *const extern fn(libc::c_int)) + } + as extern fn(libc::c_int)), + } + } +} + +/// Changes the action taken by a process on receipt of a specific signal. +/// +/// `signal` can be any signal except `SIGKILL` or `SIGSTOP`. On success, it returns the previous +/// action for the given signal. If `sigaction` fails, no new signal handler is installed. +/// +/// # Safety +/// +/// * Signal handlers may be called at any point during execution, which limits +/// what is safe to do in the body of the signal-catching function. Be certain +/// to only make syscalls that are explicitly marked safe for signal handlers +/// and only share global data using atomics. +/// +/// * There is also no guarantee that the old signal handler was installed +/// correctly. If it was installed by this crate, it will be. But if it was +/// installed by, for example, C code, then there is no guarantee its function +/// pointer is valid. In that case, this function effectively dereferences a +/// raw pointer of unknown provenance. +pub unsafe fn sigaction(signal: Signal, sigaction: &SigAction) -> Result<SigAction> { + let mut oldact = mem::MaybeUninit::<libc::sigaction>::uninit(); + + let res = libc::sigaction(signal as libc::c_int, + &sigaction.sigaction as *const libc::sigaction, + oldact.as_mut_ptr()); + + Errno::result(res).map(|_| SigAction { sigaction: oldact.assume_init() }) +} + +/// Signal management (see [signal(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/signal.html)) +/// +/// Installs `handler` for the given `signal`, returning the previous signal +/// handler. `signal` should only be used following another call to `signal` or +/// if the current handler is the default. The return value of `signal` is +/// undefined after setting the handler with [`sigaction`][SigActionFn]. +/// +/// # Safety +/// +/// If the pointer to the previous signal handler is invalid, undefined +/// behavior could be invoked when casting it back to a [`SigAction`][SigActionStruct]. +/// +/// # Examples +/// +/// Ignore `SIGINT`: +/// +/// ```no_run +/// # use nix::sys::signal::{self, Signal, SigHandler}; +/// unsafe { signal::signal(Signal::SIGINT, SigHandler::SigIgn) }.unwrap(); +/// ``` +/// +/// Use a signal handler to set a flag variable: +/// +/// ```no_run +/// # #[macro_use] extern crate lazy_static; +/// # use std::convert::TryFrom; +/// # use std::sync::atomic::{AtomicBool, Ordering}; +/// # use nix::sys::signal::{self, Signal, SigHandler}; +/// lazy_static! { +/// static ref SIGNALED: AtomicBool = AtomicBool::new(false); +/// } +/// +/// extern fn handle_sigint(signal: libc::c_int) { +/// let signal = Signal::try_from(signal).unwrap(); +/// SIGNALED.store(signal == Signal::SIGINT, Ordering::Relaxed); +/// } +/// +/// fn main() { +/// let handler = SigHandler::Handler(handle_sigint); +/// unsafe { signal::signal(Signal::SIGINT, handler) }.unwrap(); +/// } +/// ``` +/// +/// # Errors +/// +/// Returns [`Error(Errno::EOPNOTSUPP)`] if `handler` is +/// [`SigAction`][SigActionStruct]. Use [`sigaction`][SigActionFn] instead. +/// +/// `signal` also returns any error from `libc::signal`, such as when an attempt +/// is made to catch a signal that cannot be caught or to ignore a signal that +/// cannot be ignored. +/// +/// [`Error::UnsupportedOperation`]: ../../enum.Error.html#variant.UnsupportedOperation +/// [SigActionStruct]: struct.SigAction.html +/// [sigactionFn]: fn.sigaction.html +pub unsafe fn signal(signal: Signal, handler: SigHandler) -> Result<SigHandler> { + let signal = signal as libc::c_int; + let res = match handler { + SigHandler::SigDfl => libc::signal(signal, libc::SIG_DFL), + SigHandler::SigIgn => libc::signal(signal, libc::SIG_IGN), + SigHandler::Handler(handler) => libc::signal(signal, handler as libc::sighandler_t), + #[cfg(not(target_os = "redox"))] + SigHandler::SigAction(_) => return Err(Errno::ENOTSUP), + }; + Errno::result(res).map(|oldhandler| { + match oldhandler { + libc::SIG_DFL => SigHandler::SigDfl, + libc::SIG_IGN => SigHandler::SigIgn, + p => SigHandler::Handler( + *(&p as *const usize + as *const extern fn(libc::c_int)) + as extern fn(libc::c_int)), + } + }) +} + +fn do_pthread_sigmask(how: SigmaskHow, + set: Option<&SigSet>, + oldset: Option<*mut libc::sigset_t>) -> Result<()> { + if set.is_none() && oldset.is_none() { + return Ok(()) + } + + let res = unsafe { + // if set or oldset is None, pass in null pointers instead + libc::pthread_sigmask(how as libc::c_int, + set.map_or_else(ptr::null::<libc::sigset_t>, + |s| &s.sigset as *const libc::sigset_t), + oldset.unwrap_or(ptr::null_mut()) + ) + }; + + Errno::result(res).map(drop) +} + +/// Manages the signal mask (set of blocked signals) for the calling thread. +/// +/// If the `set` parameter is `Some(..)`, then the signal mask will be updated with the signal set. +/// The `how` flag decides the type of update. If `set` is `None`, `how` will be ignored, +/// and no modification will take place. +/// +/// If the 'oldset' parameter is `Some(..)` then the current signal mask will be written into it. +/// +/// If both `set` and `oldset` is `Some(..)`, the current signal mask will be written into oldset, +/// and then it will be updated with `set`. +/// +/// If both `set` and `oldset` is None, this function is a no-op. +/// +/// For more information, visit the [`pthread_sigmask`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_sigmask.html), +/// or [`sigprocmask`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigprocmask.html) man pages. +pub fn pthread_sigmask(how: SigmaskHow, + set: Option<&SigSet>, + oldset: Option<&mut SigSet>) -> Result<()> +{ + do_pthread_sigmask(how, set, oldset.map(|os| &mut os.sigset as *mut _ )) +} + +/// Examine and change blocked signals. +/// +/// For more information see the [`sigprocmask` man +/// pages](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigprocmask.html). +pub fn sigprocmask(how: SigmaskHow, set: Option<&SigSet>, oldset: Option<&mut SigSet>) -> Result<()> { + if set.is_none() && oldset.is_none() { + return Ok(()) + } + + let res = unsafe { + // if set or oldset is None, pass in null pointers instead + libc::sigprocmask(how as libc::c_int, + set.map_or_else(ptr::null::<libc::sigset_t>, + |s| &s.sigset as *const libc::sigset_t), + oldset.map_or_else(ptr::null_mut::<libc::sigset_t>, + |os| &mut os.sigset as *mut libc::sigset_t)) + }; + + Errno::result(res).map(drop) +} + +/// Send a signal to a process +/// +/// # Arguments +/// +/// * `pid` - Specifies which processes should receive the signal. +/// - If positive, specifies an individual process. +/// - If zero, the signal will be sent to all processes whose group +/// ID is equal to the process group ID of the sender. This is a +#[cfg_attr(target_os = "fuchsia", doc = "variant of `killpg`.")] +#[cfg_attr(not(target_os = "fuchsia"), doc = "variant of [`killpg`].")] +/// - If `-1` and the process has super-user privileges, the signal +/// is sent to all processes exclusing system processes. +/// - If less than `-1`, the signal is sent to all processes whose +/// process group ID is equal to the absolute value of `pid`. +/// * `signal` - Signal to send. If `None`, error checking is performed +/// but no signal is actually sent. +/// +/// See Also +/// [`kill(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/kill.html) +pub fn kill<T: Into<Option<Signal>>>(pid: Pid, signal: T) -> Result<()> { + let res = unsafe { libc::kill(pid.into(), + match signal.into() { + Some(s) => s as libc::c_int, + None => 0, + }) }; + + Errno::result(res).map(drop) +} + +/// Send a signal to a process group +/// +/// # Arguments +/// +/// * `pgrp` - Process group to signal. If less then or equal 1, the behavior +/// is platform-specific. +/// * `signal` - Signal to send. If `None`, `killpg` will only preform error +/// checking and won't send any signal. +/// +/// See Also [killpg(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/killpg.html). +#[cfg(not(target_os = "fuchsia"))] +pub fn killpg<T: Into<Option<Signal>>>(pgrp: Pid, signal: T) -> Result<()> { + let res = unsafe { libc::killpg(pgrp.into(), + match signal.into() { + Some(s) => s as libc::c_int, + None => 0, + }) }; + + Errno::result(res).map(drop) +} + +/// Send a signal to the current thread +/// +/// See Also [raise(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/raise.html) +pub fn raise(signal: Signal) -> Result<()> { + let res = unsafe { libc::raise(signal as libc::c_int) }; + + Errno::result(res).map(drop) +} +} + +feature! { +#![any(feature = "aio", feature = "signal")] + +/// Identifies a thread for [`SigevNotify::SigevThreadId`] +#[cfg(target_os = "freebsd")] +pub type type_of_thread_id = libc::lwpid_t; +/// Identifies a thread for [`SigevNotify::SigevThreadId`] +#[cfg(target_os = "linux")] +pub type type_of_thread_id = libc::pid_t; + +/// Specifies the notification method used by a [`SigEvent`] +// sigval is actually a union of a int and a void*. But it's never really used +// as a pointer, because neither libc nor the kernel ever dereference it. nix +// therefore presents it as an intptr_t, which is how kevent uses it. +#[cfg(not(any(target_os = "openbsd", target_os = "redox")))] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum SigevNotify { + /// No notification will be delivered + SigevNone, + /// Notify by delivering a signal to the process. + SigevSignal { + /// Signal to deliver + signal: Signal, + /// Will be present in the `si_value` field of the [`libc::siginfo_t`] + /// structure of the queued signal. + si_value: libc::intptr_t + }, + // Note: SIGEV_THREAD is not implemented because libc::sigevent does not + // expose a way to set the union members needed by SIGEV_THREAD. + /// Notify by delivering an event to a kqueue. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + SigevKevent { + /// File descriptor of the kqueue to notify. + kq: RawFd, + /// Will be contained in the kevent's `udata` field. + udata: libc::intptr_t + }, + /// Notify by delivering a signal to a thread. + #[cfg(any(target_os = "freebsd", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + SigevThreadId { + /// Signal to send + signal: Signal, + /// LWP ID of the thread to notify + thread_id: type_of_thread_id, + /// Will be present in the `si_value` field of the [`libc::siginfo_t`] + /// structure of the queued signal. + si_value: libc::intptr_t + }, +} +} + +#[cfg(not(any(target_os = "openbsd", target_os = "redox")))] +#[cfg_attr(docsrs, doc(cfg(all())))] +mod sigevent { + feature! { + #![any(feature = "aio", feature = "signal")] + + use std::mem; + use std::ptr; + use super::SigevNotify; + #[cfg(any(target_os = "freebsd", target_os = "linux"))] + use super::type_of_thread_id; + + /// Used to request asynchronous notification of the completion of certain + /// events, such as POSIX AIO and timers. + #[repr(C)] + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + pub struct SigEvent { + sigevent: libc::sigevent + } + + impl SigEvent { + /// **Note:** this constructor does not allow the user to set the + /// `sigev_notify_kevent_flags` field. That's considered ok because on FreeBSD + /// at least those flags don't do anything useful. That field is part of a + /// union that shares space with the more genuinely useful fields. + /// + /// **Note:** This constructor also doesn't allow the caller to set the + /// `sigev_notify_function` or `sigev_notify_attributes` fields, which are + /// required for `SIGEV_THREAD`. That's considered ok because on no operating + /// system is `SIGEV_THREAD` the most efficient way to deliver AIO + /// notification. FreeBSD and DragonFly BSD programs should prefer `SIGEV_KEVENT`. + /// Linux, Solaris, and portable programs should prefer `SIGEV_THREAD_ID` or + /// `SIGEV_SIGNAL`. That field is part of a union that shares space with the + /// more genuinely useful `sigev_notify_thread_id` + // Allow invalid_value warning on Fuchsia only. + // See https://github.com/nix-rust/nix/issues/1441 + #[cfg_attr(target_os = "fuchsia", allow(invalid_value))] + pub fn new(sigev_notify: SigevNotify) -> SigEvent { + let mut sev = unsafe { mem::MaybeUninit::<libc::sigevent>::zeroed().assume_init() }; + sev.sigev_notify = match sigev_notify { + SigevNotify::SigevNone => libc::SIGEV_NONE, + SigevNotify::SigevSignal{..} => libc::SIGEV_SIGNAL, + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + SigevNotify::SigevKevent{..} => libc::SIGEV_KEVENT, + #[cfg(target_os = "freebsd")] + SigevNotify::SigevThreadId{..} => libc::SIGEV_THREAD_ID, + #[cfg(all(target_os = "linux", target_env = "gnu", not(target_arch = "mips")))] + SigevNotify::SigevThreadId{..} => libc::SIGEV_THREAD_ID, + #[cfg(all(target_os = "linux", target_env = "uclibc"))] + SigevNotify::SigevThreadId{..} => libc::SIGEV_THREAD_ID, + #[cfg(any(all(target_os = "linux", target_env = "musl"), target_arch = "mips"))] + SigevNotify::SigevThreadId{..} => 4 // No SIGEV_THREAD_ID defined + }; + sev.sigev_signo = match sigev_notify { + SigevNotify::SigevSignal{ signal, .. } => signal as libc::c_int, + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + SigevNotify::SigevKevent{ kq, ..} => kq, + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + SigevNotify::SigevThreadId{ signal, .. } => signal as libc::c_int, + _ => 0 + }; + sev.sigev_value.sival_ptr = match sigev_notify { + SigevNotify::SigevNone => ptr::null_mut::<libc::c_void>(), + SigevNotify::SigevSignal{ si_value, .. } => si_value as *mut libc::c_void, + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + SigevNotify::SigevKevent{ udata, .. } => udata as *mut libc::c_void, + #[cfg(any(target_os = "freebsd", target_os = "linux"))] + SigevNotify::SigevThreadId{ si_value, .. } => si_value as *mut libc::c_void, + }; + SigEvent::set_tid(&mut sev, &sigev_notify); + SigEvent{sigevent: sev} + } + + #[cfg(any(target_os = "freebsd", target_os = "linux"))] + fn set_tid(sev: &mut libc::sigevent, sigev_notify: &SigevNotify) { + sev.sigev_notify_thread_id = match *sigev_notify { + SigevNotify::SigevThreadId { thread_id, .. } => thread_id, + _ => 0 as type_of_thread_id + }; + } + + #[cfg(not(any(target_os = "freebsd", target_os = "linux")))] + fn set_tid(_sev: &mut libc::sigevent, _sigev_notify: &SigevNotify) { + } + + /// Return a copy of the inner structure + pub fn sigevent(&self) -> libc::sigevent { + self.sigevent + } + + /// Returns a mutable pointer to the `sigevent` wrapped by `self` + pub fn as_mut_ptr(&mut self) -> *mut libc::sigevent { + &mut self.sigevent + } + } + + impl<'a> From<&'a libc::sigevent> for SigEvent { + fn from(sigevent: &libc::sigevent) -> Self { + SigEvent{ sigevent: *sigevent } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[cfg(not(target_os = "redox"))] + use std::thread; + + #[test] + fn test_contains() { + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + + assert!(mask.contains(SIGUSR1)); + assert!(!mask.contains(SIGUSR2)); + + let all = SigSet::all(); + assert!(all.contains(SIGUSR1)); + assert!(all.contains(SIGUSR2)); + } + + #[test] + fn test_clear() { + let mut set = SigSet::all(); + set.clear(); + for signal in Signal::iterator() { + assert!(!set.contains(signal)); + } + } + + #[test] + fn test_from_str_round_trips() { + for signal in Signal::iterator() { + assert_eq!(signal.as_ref().parse::<Signal>().unwrap(), signal); + assert_eq!(signal.to_string().parse::<Signal>().unwrap(), signal); + } + } + + #[test] + fn test_from_str_invalid_value() { + let errval = Err(Errno::EINVAL); + assert_eq!("NOSIGNAL".parse::<Signal>(), errval); + assert_eq!("kill".parse::<Signal>(), errval); + assert_eq!("9".parse::<Signal>(), errval); + } + + #[test] + fn test_extend() { + let mut one_signal = SigSet::empty(); + one_signal.add(SIGUSR1); + + let mut two_signals = SigSet::empty(); + two_signals.add(SIGUSR2); + two_signals.extend(&one_signal); + + assert!(two_signals.contains(SIGUSR1)); + assert!(two_signals.contains(SIGUSR2)); + } + + #[test] + #[cfg(not(target_os = "redox"))] + fn test_thread_signal_set_mask() { + thread::spawn(|| { + let prev_mask = SigSet::thread_get_mask() + .expect("Failed to get existing signal mask!"); + + let mut test_mask = prev_mask; + test_mask.add(SIGUSR1); + + test_mask.thread_set_mask().expect("assertion failed"); + let new_mask = + SigSet::thread_get_mask().expect("Failed to get new mask!"); + + assert!(new_mask.contains(SIGUSR1)); + assert!(!new_mask.contains(SIGUSR2)); + + prev_mask + .thread_set_mask() + .expect("Failed to revert signal mask!"); + }) + .join() + .unwrap(); + } + + #[test] + #[cfg(not(target_os = "redox"))] + fn test_thread_signal_block() { + thread::spawn(|| { + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + + mask.thread_block().expect("assertion failed"); + + assert!(SigSet::thread_get_mask().unwrap().contains(SIGUSR1)); + }) + .join() + .unwrap(); + } + + #[test] + #[cfg(not(target_os = "redox"))] + fn test_thread_signal_unblock() { + thread::spawn(|| { + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + + mask.thread_unblock().expect("assertion failed"); + + assert!(!SigSet::thread_get_mask().unwrap().contains(SIGUSR1)); + }) + .join() + .unwrap(); + } + + #[test] + #[cfg(not(target_os = "redox"))] + fn test_thread_signal_swap() { + thread::spawn(|| { + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + mask.thread_block().unwrap(); + + assert!(SigSet::thread_get_mask().unwrap().contains(SIGUSR1)); + + let mut mask2 = SigSet::empty(); + mask2.add(SIGUSR2); + + let oldmask = + mask2.thread_swap_mask(SigmaskHow::SIG_SETMASK).unwrap(); + + assert!(oldmask.contains(SIGUSR1)); + assert!(!oldmask.contains(SIGUSR2)); + + assert!(SigSet::thread_get_mask().unwrap().contains(SIGUSR2)); + }) + .join() + .unwrap(); + } + + #[test] + fn test_from_and_into_iterator() { + let sigset = SigSet::from_iter(vec![Signal::SIGUSR1, Signal::SIGUSR2]); + let signals = sigset.into_iter().collect::<Vec<Signal>>(); + assert_eq!(signals, [Signal::SIGUSR1, Signal::SIGUSR2]); + } + + #[test] + #[cfg(not(target_os = "redox"))] + fn test_sigaction() { + thread::spawn(|| { + extern "C" fn test_sigaction_handler(_: libc::c_int) {} + extern "C" fn test_sigaction_action( + _: libc::c_int, + _: *mut libc::siginfo_t, + _: *mut libc::c_void, + ) { + } + + let handler_sig = SigHandler::Handler(test_sigaction_handler); + + let flags = + SaFlags::SA_ONSTACK | SaFlags::SA_RESTART | SaFlags::SA_SIGINFO; + + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + + let action_sig = SigAction::new(handler_sig, flags, mask); + + assert_eq!( + action_sig.flags(), + SaFlags::SA_ONSTACK | SaFlags::SA_RESTART + ); + assert_eq!(action_sig.handler(), handler_sig); + + mask = action_sig.mask(); + assert!(mask.contains(SIGUSR1)); + assert!(!mask.contains(SIGUSR2)); + + let handler_act = SigHandler::SigAction(test_sigaction_action); + let action_act = SigAction::new(handler_act, flags, mask); + assert_eq!(action_act.handler(), handler_act); + + let action_dfl = SigAction::new(SigHandler::SigDfl, flags, mask); + assert_eq!(action_dfl.handler(), SigHandler::SigDfl); + + let action_ign = SigAction::new(SigHandler::SigIgn, flags, mask); + assert_eq!(action_ign.handler(), SigHandler::SigIgn); + }) + .join() + .unwrap(); + } + + #[test] + #[cfg(not(target_os = "redox"))] + fn test_sigwait() { + thread::spawn(|| { + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + mask.add(SIGUSR2); + mask.thread_block().unwrap(); + + raise(SIGUSR1).unwrap(); + assert_eq!(mask.wait().unwrap(), SIGUSR1); + }) + .join() + .unwrap(); + } + + #[test] + fn test_from_sigset_t_unchecked() { + let src_set = SigSet::empty(); + let set = unsafe { SigSet::from_sigset_t_unchecked(src_set.sigset) }; + + for signal in Signal::iterator() { + assert!(!set.contains(signal)); + } + + let src_set = SigSet::all(); + let set = unsafe { SigSet::from_sigset_t_unchecked(src_set.sigset) }; + + for signal in Signal::iterator() { + assert!(set.contains(signal)); + } + } +} diff --git a/third_party/rust/nix/src/sys/signalfd.rs b/third_party/rust/nix/src/sys/signalfd.rs new file mode 100644 index 0000000000..095e590850 --- /dev/null +++ b/third_party/rust/nix/src/sys/signalfd.rs @@ -0,0 +1,175 @@ +//! Interface for the `signalfd` syscall. +//! +//! # Signal discarding +//! When a signal can't be delivered to a process (or thread), it will become a pending signal. +//! Failure to deliver could happen if the signal is blocked by every thread in the process or if +//! the signal handler is still handling a previous signal. +//! +//! If a signal is sent to a process (or thread) that already has a pending signal of the same +//! type, it will be discarded. This means that if signals of the same type are received faster than +//! they are processed, some of those signals will be dropped. Because of this limitation, +//! `signalfd` in itself cannot be used for reliable communication between processes or threads. +//! +//! Once the signal is unblocked, or the signal handler is finished, and a signal is still pending +//! (ie. not consumed from a signalfd) it will be delivered to the signal handler. +//! +//! Please note that signal discarding is not specific to `signalfd`, but also happens with regular +//! signal handlers. +use crate::errno::Errno; +pub use crate::sys::signal::{self, SigSet}; +use crate::unistd; +use crate::Result; +pub use libc::signalfd_siginfo as siginfo; + +use std::mem; +use std::os::unix::io::{AsRawFd, RawFd}; + +libc_bitflags! { + pub struct SfdFlags: libc::c_int { + SFD_NONBLOCK; + SFD_CLOEXEC; + } +} + +pub const SIGNALFD_NEW: RawFd = -1; +#[deprecated(since = "0.23.0", note = "use mem::size_of::<siginfo>() instead")] +pub const SIGNALFD_SIGINFO_SIZE: usize = mem::size_of::<siginfo>(); + +/// Creates a new file descriptor for reading signals. +/// +/// **Important:** please read the module level documentation about signal discarding before using +/// this function! +/// +/// The `mask` parameter specifies the set of signals that can be accepted via this file descriptor. +/// +/// A signal must be blocked on every thread in a process, otherwise it won't be visible from +/// signalfd (the default handler will be invoked instead). +/// +/// See [the signalfd man page for more information](https://man7.org/linux/man-pages/man2/signalfd.2.html) +pub fn signalfd(fd: RawFd, mask: &SigSet, flags: SfdFlags) -> Result<RawFd> { + unsafe { + Errno::result(libc::signalfd( + fd as libc::c_int, + mask.as_ref(), + flags.bits(), + )) + } +} + +/// A helper struct for creating, reading and closing a `signalfd` instance. +/// +/// **Important:** please read the module level documentation about signal discarding before using +/// this struct! +/// +/// # Examples +/// +/// ``` +/// # use nix::sys::signalfd::*; +/// // Set the thread to block the SIGUSR1 signal, otherwise the default handler will be used +/// let mut mask = SigSet::empty(); +/// mask.add(signal::SIGUSR1); +/// mask.thread_block().unwrap(); +/// +/// // Signals are queued up on the file descriptor +/// let mut sfd = SignalFd::with_flags(&mask, SfdFlags::SFD_NONBLOCK).unwrap(); +/// +/// match sfd.read_signal() { +/// // we caught a signal +/// Ok(Some(sig)) => (), +/// // there were no signals waiting (only happens when the SFD_NONBLOCK flag is set, +/// // otherwise the read_signal call blocks) +/// Ok(None) => (), +/// Err(err) => (), // some error happend +/// } +/// ``` +#[derive(Debug, Eq, Hash, PartialEq)] +pub struct SignalFd(RawFd); + +impl SignalFd { + pub fn new(mask: &SigSet) -> Result<SignalFd> { + Self::with_flags(mask, SfdFlags::empty()) + } + + pub fn with_flags(mask: &SigSet, flags: SfdFlags) -> Result<SignalFd> { + let fd = signalfd(SIGNALFD_NEW, mask, flags)?; + + Ok(SignalFd(fd)) + } + + pub fn set_mask(&mut self, mask: &SigSet) -> Result<()> { + signalfd(self.0, mask, SfdFlags::empty()).map(drop) + } + + pub fn read_signal(&mut self) -> Result<Option<siginfo>> { + let mut buffer = mem::MaybeUninit::<siginfo>::uninit(); + + let size = mem::size_of_val(&buffer); + let res = Errno::result(unsafe { + libc::read(self.0, buffer.as_mut_ptr() as *mut libc::c_void, size) + }) + .map(|r| r as usize); + match res { + Ok(x) if x == size => Ok(Some(unsafe { buffer.assume_init() })), + Ok(_) => unreachable!("partial read on signalfd"), + Err(Errno::EAGAIN) => Ok(None), + Err(error) => Err(error), + } + } +} + +impl Drop for SignalFd { + fn drop(&mut self) { + let e = unistd::close(self.0); + if !std::thread::panicking() && e == Err(Errno::EBADF) { + panic!("Closing an invalid file descriptor!"); + }; + } +} + +impl AsRawFd for SignalFd { + fn as_raw_fd(&self) -> RawFd { + self.0 + } +} + +impl Iterator for SignalFd { + type Item = siginfo; + + fn next(&mut self) -> Option<Self::Item> { + match self.read_signal() { + Ok(Some(sig)) => Some(sig), + Ok(None) | Err(_) => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn create_signalfd() { + let mask = SigSet::empty(); + SignalFd::new(&mask).unwrap(); + } + + #[test] + fn create_signalfd_with_opts() { + let mask = SigSet::empty(); + SignalFd::with_flags( + &mask, + SfdFlags::SFD_CLOEXEC | SfdFlags::SFD_NONBLOCK, + ) + .unwrap(); + } + + #[test] + fn read_empty_signalfd() { + let mask = SigSet::empty(); + let mut fd = + SignalFd::with_flags(&mask, SfdFlags::SFD_NONBLOCK).unwrap(); + + let res = fd.read_signal(); + assert!(res.unwrap().is_none()); + } +} diff --git a/third_party/rust/nix/src/sys/socket/addr.rs b/third_party/rust/nix/src/sys/socket/addr.rs new file mode 100644 index 0000000000..4e565a5b68 --- /dev/null +++ b/third_party/rust/nix/src/sys/socket/addr.rs @@ -0,0 +1,3247 @@ +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "haiku", + target_os = "fuchsia" +))] +#[cfg(feature = "net")] +pub use self::datalink::LinkAddr; +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use self::vsock::VsockAddr; +use super::sa_family_t; +use crate::errno::Errno; +#[cfg(any(target_os = "android", target_os = "linux"))] +use crate::sys::socket::addr::alg::AlgAddr; +#[cfg(any(target_os = "android", target_os = "linux"))] +use crate::sys::socket::addr::netlink::NetlinkAddr; +#[cfg(all( + feature = "ioctl", + any(target_os = "ios", target_os = "macos") +))] +use crate::sys::socket::addr::sys_control::SysControlAddr; +use crate::{NixPath, Result}; +use cfg_if::cfg_if; +use memoffset::offset_of; +use std::convert::TryInto; +use std::ffi::OsStr; +use std::hash::{Hash, Hasher}; +use std::os::unix::ffi::OsStrExt; +#[cfg(any(target_os = "ios", target_os = "macos"))] +use std::os::unix::io::RawFd; +use std::path::Path; +use std::{fmt, mem, net, ptr, slice}; + +/// Convert a std::net::Ipv4Addr into the libc form. +#[cfg(feature = "net")] +pub(crate) const fn ipv4addr_to_libc(addr: net::Ipv4Addr) -> libc::in_addr { + static_assertions::assert_eq_size!(net::Ipv4Addr, libc::in_addr); + // Safe because both types have the same memory layout, and no fancy Drop + // impls. + unsafe { + mem::transmute(addr) + } +} + +/// Convert a std::net::Ipv6Addr into the libc form. +#[cfg(feature = "net")] +pub(crate) const fn ipv6addr_to_libc(addr: &net::Ipv6Addr) -> libc::in6_addr { + static_assertions::assert_eq_size!(net::Ipv6Addr, libc::in6_addr); + // Safe because both are Newtype wrappers around the same libc type + unsafe { + mem::transmute(*addr) + } +} + +/// These constants specify the protocol family to be used +/// in [`socket`](fn.socket.html) and [`socketpair`](fn.socketpair.html) +/// +/// # References +/// +/// [address_families(7)](https://man7.org/linux/man-pages/man7/address_families.7.html) +// Should this be u8? +#[repr(i32)] +#[non_exhaustive] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +pub enum AddressFamily { + /// Local communication (see [`unix(7)`](https://man7.org/linux/man-pages/man7/unix.7.html)) + Unix = libc::AF_UNIX, + /// IPv4 Internet protocols (see [`ip(7)`](https://man7.org/linux/man-pages/man7/ip.7.html)) + Inet = libc::AF_INET, + /// IPv6 Internet protocols (see [`ipv6(7)`](https://man7.org/linux/man-pages/man7/ipv6.7.html)) + Inet6 = libc::AF_INET6, + /// Kernel user interface device (see [`netlink(7)`](https://man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Netlink = libc::AF_NETLINK, + /// Low level packet interface (see [`packet(7)`](https://man7.org/linux/man-pages/man7/packet.7.html)) + #[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "illumos", + target_os = "fuchsia", + target_os = "solaris" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Packet = libc::AF_PACKET, + /// KEXT Controls and Notifications + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + System = libc::AF_SYSTEM, + /// Amateur radio AX.25 protocol + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Ax25 = libc::AF_AX25, + /// IPX - Novell protocols + Ipx = libc::AF_IPX, + /// AppleTalk + AppleTalk = libc::AF_APPLETALK, + /// AX.25 packet layer protocol. + /// (see [netrom(4)](https://www.unix.com/man-page/linux/4/netrom/)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetRom = libc::AF_NETROM, + /// Can't be used for creating sockets; mostly used for bridge + /// links in + /// [rtnetlink(7)](https://man7.org/linux/man-pages/man7/rtnetlink.7.html) + /// protocol commands. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Bridge = libc::AF_BRIDGE, + /// Access to raw ATM PVCs + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + AtmPvc = libc::AF_ATMPVC, + /// ITU-T X.25 / ISO-8208 protocol (see [`x25(7)`](https://man7.org/linux/man-pages/man7/x25.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + X25 = libc::AF_X25, + /// RATS (Radio Amateur Telecommunications Society) Open + /// Systems environment (ROSE) AX.25 packet layer protocol. + /// (see [netrom(4)](https://www.unix.com/man-page/linux/4/netrom/)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Rose = libc::AF_ROSE, + /// DECet protocol sockets. + #[cfg(not(target_os = "haiku"))] + Decnet = libc::AF_DECnet, + /// Reserved for "802.2LLC project"; never used. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetBeui = libc::AF_NETBEUI, + /// This was a short-lived (between Linux 2.1.30 and + /// 2.1.99pre2) protocol family for firewall upcalls. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Security = libc::AF_SECURITY, + /// Key management protocol. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Key = libc::AF_KEY, + #[allow(missing_docs)] // Not documented anywhere that I can find + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Ash = libc::AF_ASH, + /// Acorn Econet protocol + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Econet = libc::AF_ECONET, + /// Access to ATM Switched Virtual Circuits + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + AtmSvc = libc::AF_ATMSVC, + /// Reliable Datagram Sockets (RDS) protocol + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Rds = libc::AF_RDS, + /// IBM SNA + #[cfg(not(target_os = "haiku"))] + Sna = libc::AF_SNA, + /// Socket interface over IrDA + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Irda = libc::AF_IRDA, + /// Generic PPP transport layer, for setting up L2 tunnels (L2TP and PPPoE) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Pppox = libc::AF_PPPOX, + /// Legacy protocol for wide area network (WAN) connectivity that was used + /// by Sangoma WAN cards + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Wanpipe = libc::AF_WANPIPE, + /// Logical link control (IEEE 802.2 LLC) protocol + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Llc = libc::AF_LLC, + /// InfiniBand native addressing + #[cfg(all(target_os = "linux", not(target_env = "uclibc")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Ib = libc::AF_IB, + /// Multiprotocol Label Switching + #[cfg(all(target_os = "linux", not(target_env = "uclibc")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Mpls = libc::AF_MPLS, + /// Controller Area Network automotive bus protocol + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Can = libc::AF_CAN, + /// TIPC, "cluster domain sockets" protocol + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Tipc = libc::AF_TIPC, + /// Bluetooth low-level socket protocol + #[cfg(not(any( + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "solaris" + )))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Bluetooth = libc::AF_BLUETOOTH, + /// IUCV (inter-user communication vehicle) z/VM protocol for + /// hypervisor-guest interaction + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Iucv = libc::AF_IUCV, + /// Rx, Andrew File System remote procedure call protocol + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + RxRpc = libc::AF_RXRPC, + /// New "modular ISDN" driver interface protocol + #[cfg(not(any( + target_os = "illumos", + target_os = "solaris", + target_os = "haiku" + )))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Isdn = libc::AF_ISDN, + /// Nokia cellular modem IPC/RPC interface + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Phonet = libc::AF_PHONET, + /// IEEE 802.15.4 WPAN (wireless personal area network) raw packet protocol + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Ieee802154 = libc::AF_IEEE802154, + /// Ericsson's Communication CPU to Application CPU interface (CAIF) + /// protocol. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Caif = libc::AF_CAIF, + /// Interface to kernel crypto API + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Alg = libc::AF_ALG, + /// Near field communication + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + Nfc = libc::AF_NFC, + /// VMWare VSockets protocol for hypervisor-guest interaction. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Vsock = libc::AF_VSOCK, + /// ARPANet IMP addresses + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ImpLink = libc::AF_IMPLINK, + /// PUP protocols, e.g. BSP + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Pup = libc::AF_PUP, + /// MIT CHAOS protocols + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Chaos = libc::AF_CHAOS, + /// Novell and Xerox protocol + #[cfg(any( + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Ns = libc::AF_NS, + #[allow(missing_docs)] // Not documented anywhere that I can find + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Iso = libc::AF_ISO, + /// Bell Labs virtual circuit switch ? + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Datakit = libc::AF_DATAKIT, + /// CCITT protocols, X.25 etc + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Ccitt = libc::AF_CCITT, + /// DEC Direct data link interface + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Dli = libc::AF_DLI, + #[allow(missing_docs)] // Not documented anywhere that I can find + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Lat = libc::AF_LAT, + /// NSC Hyperchannel + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Hylink = libc::AF_HYLINK, + /// Link layer interface + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Link = libc::AF_LINK, + /// connection-oriented IP, aka ST II + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Coip = libc::AF_COIP, + /// Computer Network Technology + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Cnt = libc::AF_CNT, + /// Native ATM access + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Natm = libc::AF_NATM, + /// Unspecified address family, (see [`getaddrinfo(3)`](https://man7.org/linux/man-pages/man3/getaddrinfo.3.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Unspec = libc::AF_UNSPEC, +} + +impl AddressFamily { + /// Create a new `AddressFamily` from an integer value retrieved from `libc`, usually from + /// the `sa_family` field of a `sockaddr`. + /// + /// Currently only supports these address families: Unix, Inet (v4 & v6), Netlink, Link/Packet + /// and System. Returns None for unsupported or unknown address families. + pub const fn from_i32(family: i32) -> Option<AddressFamily> { + match family { + libc::AF_UNIX => Some(AddressFamily::Unix), + libc::AF_INET => Some(AddressFamily::Inet), + libc::AF_INET6 => Some(AddressFamily::Inet6), + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_NETLINK => Some(AddressFamily::Netlink), + #[cfg(any(target_os = "macos", target_os = "macos"))] + libc::AF_SYSTEM => Some(AddressFamily::System), + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_PACKET => Some(AddressFamily::Packet), + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "illumos", + target_os = "openbsd" + ))] + libc::AF_LINK => Some(AddressFamily::Link), + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_VSOCK => Some(AddressFamily::Vsock), + _ => None, + } + } +} + +feature! { +#![feature = "net"] + +#[deprecated( + since = "0.24.0", + note = "use SockaddrIn, SockaddrIn6, or SockaddrStorage instead" +)] +#[allow(missing_docs)] // Since they're all deprecated anyway +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum InetAddr { + V4(libc::sockaddr_in), + V6(libc::sockaddr_in6), +} + +#[allow(missing_docs)] // It's deprecated anyway +#[allow(deprecated)] +impl InetAddr { + #[allow(clippy::needless_update)] // It isn't needless on all OSes + pub fn from_std(std: &net::SocketAddr) -> InetAddr { + match *std { + net::SocketAddr::V4(ref addr) => { + InetAddr::V4(libc::sockaddr_in { + #[cfg(any(target_os = "dragonfly", target_os = "freebsd", + target_os = "haiku", target_os = "hermit", + target_os = "ios", target_os = "macos", + target_os = "netbsd", target_os = "openbsd"))] + sin_len: mem::size_of::<libc::sockaddr_in>() as u8, + sin_family: AddressFamily::Inet as sa_family_t, + sin_port: addr.port().to_be(), // network byte order + sin_addr: Ipv4Addr::from_std(addr.ip()).0, + .. unsafe { mem::zeroed() } + }) + } + net::SocketAddr::V6(ref addr) => { + InetAddr::V6(libc::sockaddr_in6 { + #[cfg(any(target_os = "dragonfly", target_os = "freebsd", + target_os = "haiku", target_os = "hermit", + target_os = "ios", target_os = "macos", + target_os = "netbsd", target_os = "openbsd"))] + sin6_len: mem::size_of::<libc::sockaddr_in6>() as u8, + sin6_family: AddressFamily::Inet6 as sa_family_t, + sin6_port: addr.port().to_be(), // network byte order + sin6_addr: Ipv6Addr::from_std(addr.ip()).0, + sin6_flowinfo: addr.flowinfo(), // host byte order + sin6_scope_id: addr.scope_id(), // host byte order + .. unsafe { mem::zeroed() } + }) + } + } + } + + #[allow(clippy::needless_update)] // It isn't needless on all OSes + pub fn new(ip: IpAddr, port: u16) -> InetAddr { + match ip { + IpAddr::V4(ref ip) => { + InetAddr::V4(libc::sockaddr_in { + sin_family: AddressFamily::Inet as sa_family_t, + sin_port: port.to_be(), + sin_addr: ip.0, + .. unsafe { mem::zeroed() } + }) + } + IpAddr::V6(ref ip) => { + InetAddr::V6(libc::sockaddr_in6 { + sin6_family: AddressFamily::Inet6 as sa_family_t, + sin6_port: port.to_be(), + sin6_addr: ip.0, + .. unsafe { mem::zeroed() } + }) + } + } + } + /// Gets the IP address associated with this socket address. + pub const fn ip(&self) -> IpAddr { + match *self { + InetAddr::V4(ref sa) => IpAddr::V4(Ipv4Addr(sa.sin_addr)), + InetAddr::V6(ref sa) => IpAddr::V6(Ipv6Addr(sa.sin6_addr)), + } + } + + /// Gets the port number associated with this socket address + pub const fn port(&self) -> u16 { + match *self { + InetAddr::V6(ref sa) => u16::from_be(sa.sin6_port), + InetAddr::V4(ref sa) => u16::from_be(sa.sin_port), + } + } + + pub fn to_std(&self) -> net::SocketAddr { + match *self { + InetAddr::V4(ref sa) => net::SocketAddr::V4( + net::SocketAddrV4::new( + Ipv4Addr(sa.sin_addr).to_std(), + self.port())), + InetAddr::V6(ref sa) => net::SocketAddr::V6( + net::SocketAddrV6::new( + Ipv6Addr(sa.sin6_addr).to_std(), + self.port(), + sa.sin6_flowinfo, + sa.sin6_scope_id)), + } + } + + #[deprecated(since = "0.23.0", note = "use .to_string() instead")] + pub fn to_str(&self) -> String { + format!("{}", self) + } +} + +#[allow(deprecated)] +impl fmt::Display for InetAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + InetAddr::V4(_) => write!(f, "{}:{}", self.ip(), self.port()), + InetAddr::V6(_) => write!(f, "[{}]:{}", self.ip(), self.port()), + } + } +} + +/* + * + * ===== IpAddr ===== + * + */ +#[allow(missing_docs)] // Since they're all deprecated anyway +#[allow(deprecated)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[deprecated( + since = "0.24.0", + note = "Use std::net::IpAddr instead" +)] +pub enum IpAddr { + V4(Ipv4Addr), + V6(Ipv6Addr), +} + +#[allow(deprecated)] +#[allow(missing_docs)] // Since they're all deprecated anyway +impl IpAddr { + /// Create a new IpAddr that contains an IPv4 address. + /// + /// The result will represent the IP address a.b.c.d + pub const fn new_v4(a: u8, b: u8, c: u8, d: u8) -> IpAddr { + IpAddr::V4(Ipv4Addr::new(a, b, c, d)) + } + + /// Create a new IpAddr that contains an IPv6 address. + /// + /// The result will represent the IP address a:b:c:d:e:f + #[allow(clippy::many_single_char_names)] + #[allow(clippy::too_many_arguments)] + pub const fn new_v6(a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, h: u16) -> IpAddr { + IpAddr::V6(Ipv6Addr::new(a, b, c, d, e, f, g, h)) + } + + pub fn from_std(std: &net::IpAddr) -> IpAddr { + match *std { + net::IpAddr::V4(ref std) => IpAddr::V4(Ipv4Addr::from_std(std)), + net::IpAddr::V6(ref std) => IpAddr::V6(Ipv6Addr::from_std(std)), + } + } + + pub const fn to_std(&self) -> net::IpAddr { + match *self { + IpAddr::V4(ref ip) => net::IpAddr::V4(ip.to_std()), + IpAddr::V6(ref ip) => net::IpAddr::V6(ip.to_std()), + } + } +} + +#[allow(deprecated)] +impl fmt::Display for IpAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + IpAddr::V4(ref v4) => v4.fmt(f), + IpAddr::V6(ref v6) => v6.fmt(f) + } + } +} + +/* + * + * ===== Ipv4Addr ===== + * + */ + +#[deprecated( + since = "0.24.0", + note = "Use std::net::Ipv4Addr instead" +)] +#[allow(missing_docs)] // Since they're all deprecated anyway +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(transparent)] +pub struct Ipv4Addr(pub libc::in_addr); + +#[allow(deprecated)] +#[allow(missing_docs)] // Since they're all deprecated anyway +impl Ipv4Addr { + #[allow(clippy::identity_op)] // More readable this way + pub const fn new(a: u8, b: u8, c: u8, d: u8) -> Ipv4Addr { + let ip = (((a as u32) << 24) | + ((b as u32) << 16) | + ((c as u32) << 8) | + ((d as u32) << 0)).to_be(); + + Ipv4Addr(libc::in_addr { s_addr: ip }) + } + + // Use pass by reference for symmetry with Ipv6Addr::from_std + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn from_std(std: &net::Ipv4Addr) -> Ipv4Addr { + let bits = std.octets(); + Ipv4Addr::new(bits[0], bits[1], bits[2], bits[3]) + } + + pub const fn any() -> Ipv4Addr { + Ipv4Addr(libc::in_addr { s_addr: libc::INADDR_ANY }) + } + + pub const fn octets(self) -> [u8; 4] { + let bits = u32::from_be(self.0.s_addr); + [(bits >> 24) as u8, (bits >> 16) as u8, (bits >> 8) as u8, bits as u8] + } + + pub const fn to_std(self) -> net::Ipv4Addr { + let bits = self.octets(); + net::Ipv4Addr::new(bits[0], bits[1], bits[2], bits[3]) + } +} + +#[allow(deprecated)] +impl fmt::Display for Ipv4Addr { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let octets = self.octets(); + write!(fmt, "{}.{}.{}.{}", octets[0], octets[1], octets[2], octets[3]) + } +} + +/* + * + * ===== Ipv6Addr ===== + * + */ + +#[deprecated( + since = "0.24.0", + note = "Use std::net::Ipv6Addr instead" +)] +#[allow(missing_docs)] // Since they're all deprecated anyway +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(transparent)] +pub struct Ipv6Addr(pub libc::in6_addr); + +// Note that IPv6 addresses are stored in big endian order on all architectures. +// See https://tools.ietf.org/html/rfc1700 or consult your favorite search +// engine. + +macro_rules! to_u8_array { + ($($num:ident),*) => { + [ $(($num>>8) as u8, ($num&0xff) as u8,)* ] + } +} + +macro_rules! to_u16_array { + ($slf:ident, $($first:expr, $second:expr),*) => { + [$( (($slf.0.s6_addr[$first] as u16) << 8) + $slf.0.s6_addr[$second] as u16,)*] + } +} + +#[allow(deprecated)] +#[allow(missing_docs)] // Since they're all deprecated anyway +impl Ipv6Addr { + #[allow(clippy::many_single_char_names)] + #[allow(clippy::too_many_arguments)] + pub const fn new(a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, h: u16) -> Ipv6Addr { + Ipv6Addr(libc::in6_addr{s6_addr: to_u8_array!(a,b,c,d,e,f,g,h)}) + } + + pub fn from_std(std: &net::Ipv6Addr) -> Ipv6Addr { + let s = std.segments(); + Ipv6Addr::new(s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]) + } + + /// Return the eight 16-bit segments that make up this address + pub const fn segments(&self) -> [u16; 8] { + to_u16_array!(self, 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15) + } + + pub const fn to_std(&self) -> net::Ipv6Addr { + let s = self.segments(); + net::Ipv6Addr::new(s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]) + } +} + +#[allow(deprecated)] +impl fmt::Display for Ipv6Addr { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + self.to_std().fmt(fmt) + } +} +} + +/// A wrapper around `sockaddr_un`. +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct UnixAddr { + // INVARIANT: sun & sun_len are valid as defined by docs for from_raw_parts + sun: libc::sockaddr_un, + /// The length of the valid part of `sun`, including the sun_family field + /// but excluding any trailing nul. + // On the BSDs, this field is built into sun + #[cfg(any( + target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux" + ))] + sun_len: u8, +} + +// linux man page unix(7) says there are 3 kinds of unix socket: +// pathname: addrlen = offsetof(struct sockaddr_un, sun_path) + strlen(sun_path) + 1 +// unnamed: addrlen = sizeof(sa_family_t) +// abstract: addren > sizeof(sa_family_t), name = sun_path[..(addrlen - sizeof(sa_family_t))] +// +// what we call path_len = addrlen - offsetof(struct sockaddr_un, sun_path) +#[derive(PartialEq, Eq, Hash)] +enum UnixAddrKind<'a> { + Pathname(&'a Path), + Unnamed, + #[cfg(any(target_os = "android", target_os = "linux"))] + Abstract(&'a [u8]), +} +impl<'a> UnixAddrKind<'a> { + /// Safety: sun & sun_len must be valid + unsafe fn get(sun: &'a libc::sockaddr_un, sun_len: u8) -> Self { + assert!(sun_len as usize >= offset_of!(libc::sockaddr_un, sun_path)); + let path_len = + sun_len as usize - offset_of!(libc::sockaddr_un, sun_path); + if path_len == 0 { + return Self::Unnamed; + } + #[cfg(any(target_os = "android", target_os = "linux"))] + if sun.sun_path[0] == 0 { + let name = slice::from_raw_parts( + sun.sun_path.as_ptr().add(1) as *const u8, + path_len - 1, + ); + return Self::Abstract(name); + } + let pathname = + slice::from_raw_parts(sun.sun_path.as_ptr() as *const u8, path_len); + if pathname.last() == Some(&0) { + // A trailing NUL is not considered part of the path, and it does + // not need to be included in the addrlen passed to functions like + // bind(). However, Linux adds a trailing NUL, even if one was not + // originally present, when returning addrs from functions like + // getsockname() (the BSDs do not do that). So we need to filter + // out any trailing NUL here, so sockaddrs can round-trip through + // the kernel and still compare equal. + Self::Pathname(Path::new(OsStr::from_bytes( + &pathname[0..pathname.len() - 1], + ))) + } else { + Self::Pathname(Path::new(OsStr::from_bytes(pathname))) + } + } +} + +impl UnixAddr { + /// Create a new sockaddr_un representing a filesystem path. + pub fn new<P: ?Sized + NixPath>(path: &P) -> Result<UnixAddr> { + path.with_nix_path(|cstr| unsafe { + let mut ret = libc::sockaddr_un { + sun_family: AddressFamily::Unix as sa_family_t, + ..mem::zeroed() + }; + + let bytes = cstr.to_bytes(); + + if bytes.len() >= ret.sun_path.len() { + return Err(Errno::ENAMETOOLONG); + } + + let sun_len = (bytes.len() + + offset_of!(libc::sockaddr_un, sun_path)) + .try_into() + .unwrap(); + + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + { + ret.sun_len = sun_len; + } + ptr::copy_nonoverlapping( + bytes.as_ptr(), + ret.sun_path.as_mut_ptr() as *mut u8, + bytes.len(), + ); + + Ok(UnixAddr::from_raw_parts(ret, sun_len)) + })? + } + + /// Create a new `sockaddr_un` representing an address in the "abstract namespace". + /// + /// The leading nul byte for the abstract namespace is automatically added; + /// thus the input `path` is expected to be the bare name, not NUL-prefixed. + /// This is a Linux-specific extension, primarily used to allow chrooted + /// processes to communicate with processes having a different filesystem view. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn new_abstract(path: &[u8]) -> Result<UnixAddr> { + unsafe { + let mut ret = libc::sockaddr_un { + sun_family: AddressFamily::Unix as sa_family_t, + ..mem::zeroed() + }; + + if path.len() >= ret.sun_path.len() { + return Err(Errno::ENAMETOOLONG); + } + let sun_len = + (path.len() + 1 + offset_of!(libc::sockaddr_un, sun_path)) + .try_into() + .unwrap(); + + // Abstract addresses are represented by sun_path[0] == + // b'\0', so copy starting one byte in. + ptr::copy_nonoverlapping( + path.as_ptr(), + ret.sun_path.as_mut_ptr().offset(1) as *mut u8, + path.len(), + ); + + Ok(UnixAddr::from_raw_parts(ret, sun_len)) + } + } + + /// Create a new `sockaddr_un` representing an "unnamed" unix socket address. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn new_unnamed() -> UnixAddr { + let ret = libc::sockaddr_un { + sun_family: AddressFamily::Unix as sa_family_t, + .. unsafe { mem::zeroed() } + }; + + let sun_len: u8 = offset_of!(libc::sockaddr_un, sun_path).try_into().unwrap(); + + unsafe { UnixAddr::from_raw_parts(ret, sun_len) } + } + + /// Create a UnixAddr from a raw `sockaddr_un` struct and a size. `sun_len` + /// is the size of the valid portion of the struct, excluding any trailing + /// NUL. + /// + /// # Safety + /// This pair of sockaddr_un & sun_len must be a valid unix addr, which + /// means: + /// - sun_len >= offset_of(sockaddr_un, sun_path) + /// - sun_len <= sockaddr_un.sun_path.len() - offset_of(sockaddr_un, sun_path) + /// - if this is a unix addr with a pathname, sun.sun_path is a + /// fs path, not necessarily nul-terminated. + pub(crate) unsafe fn from_raw_parts( + sun: libc::sockaddr_un, + sun_len: u8, + ) -> UnixAddr { + cfg_if! { + if #[cfg(any(target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux" + ))] + { + UnixAddr { sun, sun_len } + } else { + assert_eq!(sun_len, sun.sun_len); + UnixAddr {sun} + } + } + } + + fn kind(&self) -> UnixAddrKind<'_> { + // SAFETY: our sockaddr is always valid because of the invariant on the struct + unsafe { UnixAddrKind::get(&self.sun, self.sun_len()) } + } + + /// If this address represents a filesystem path, return that path. + pub fn path(&self) -> Option<&Path> { + match self.kind() { + UnixAddrKind::Pathname(path) => Some(path), + _ => None, + } + } + + /// If this address represents an abstract socket, return its name. + /// + /// For abstract sockets only the bare name is returned, without the + /// leading NUL byte. `None` is returned for unnamed or path-backed sockets. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn as_abstract(&self) -> Option<&[u8]> { + match self.kind() { + UnixAddrKind::Abstract(name) => Some(name), + _ => None, + } + } + + /// Check if this address is an "unnamed" unix socket address. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + #[inline] + pub fn is_unnamed(&self) -> bool { + matches!(self.kind(), UnixAddrKind::Unnamed) + } + + /// Returns the addrlen of this socket - `offsetof(struct sockaddr_un, sun_path)` + #[inline] + pub fn path_len(&self) -> usize { + self.sun_len() as usize - offset_of!(libc::sockaddr_un, sun_path) + } + /// Returns a pointer to the raw `sockaddr_un` struct + #[inline] + pub fn as_ptr(&self) -> *const libc::sockaddr_un { + &self.sun + } + /// Returns a mutable pointer to the raw `sockaddr_un` struct + #[inline] + pub fn as_mut_ptr(&mut self) -> *mut libc::sockaddr_un { + &mut self.sun + } + + fn sun_len(&self) -> u8 { + cfg_if! { + if #[cfg(any(target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux" + ))] + { + self.sun_len + } else { + self.sun.sun_len + } + } + } +} + +impl private::SockaddrLikePriv for UnixAddr {} +impl SockaddrLike for UnixAddr { + #[cfg(any( + target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux" + ))] + fn len(&self) -> libc::socklen_t { + self.sun_len.into() + } + + unsafe fn from_raw( + addr: *const libc::sockaddr, + len: Option<libc::socklen_t>, + ) -> Option<Self> + where + Self: Sized, + { + if let Some(l) = len { + if (l as usize) < offset_of!(libc::sockaddr_un, sun_path) + || l > u8::MAX as libc::socklen_t + { + return None; + } + } + if (*addr).sa_family as i32 != libc::AF_UNIX { + return None; + } + let mut su: libc::sockaddr_un = mem::zeroed(); + let sup = &mut su as *mut libc::sockaddr_un as *mut u8; + cfg_if! { + if #[cfg(any(target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux" + ))] { + let su_len = len.unwrap_or( + mem::size_of::<libc::sockaddr_un>() as libc::socklen_t + ); + } else { + let su_len = len.unwrap_or((*addr).sa_len as libc::socklen_t); + } + }; + ptr::copy(addr as *const u8, sup, su_len as usize); + Some(Self::from_raw_parts(su, su_len as u8)) + } + + fn size() -> libc::socklen_t + where + Self: Sized, + { + mem::size_of::<libc::sockaddr_un>() as libc::socklen_t + } +} + +impl AsRef<libc::sockaddr_un> for UnixAddr { + fn as_ref(&self) -> &libc::sockaddr_un { + &self.sun + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +fn fmt_abstract(abs: &[u8], f: &mut fmt::Formatter) -> fmt::Result { + use fmt::Write; + f.write_str("@\"")?; + for &b in abs { + use fmt::Display; + char::from(b).escape_default().fmt(f)?; + } + f.write_char('"')?; + Ok(()) +} + +impl fmt::Display for UnixAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.kind() { + UnixAddrKind::Pathname(path) => path.display().fmt(f), + UnixAddrKind::Unnamed => f.pad("<unbound UNIX socket>"), + #[cfg(any(target_os = "android", target_os = "linux"))] + UnixAddrKind::Abstract(name) => fmt_abstract(name, f), + } + } +} + +impl PartialEq for UnixAddr { + fn eq(&self, other: &UnixAddr) -> bool { + self.kind() == other.kind() + } +} + +impl Eq for UnixAddr {} + +impl Hash for UnixAddr { + fn hash<H: Hasher>(&self, s: &mut H) { + self.kind().hash(s) + } +} + +/// Anything that, in C, can be cast back and forth to `sockaddr`. +/// +/// Most implementors also implement `AsRef<libc::XXX>` to access their +/// inner type read-only. +#[allow(clippy::len_without_is_empty)] +pub trait SockaddrLike: private::SockaddrLikePriv { + /// Returns a raw pointer to the inner structure. Useful for FFI. + fn as_ptr(&self) -> *const libc::sockaddr { + self as *const Self as *const libc::sockaddr + } + + /// Unsafe constructor from a variable length source + /// + /// Some C APIs from provide `len`, and others do not. If it's provided it + /// will be validated. If not, it will be guessed based on the family. + /// + /// # Arguments + /// + /// - `addr`: raw pointer to something that can be cast to a + /// `libc::sockaddr`. For example, `libc::sockaddr_in`, + /// `libc::sockaddr_in6`, etc. + /// - `len`: For fixed-width types like `sockaddr_in`, it will be + /// validated if present and ignored if not. For variable-width + /// types it is required and must be the total length of valid + /// data. For example, if `addr` points to a + /// named `sockaddr_un`, then `len` must be the length of the + /// structure up to but not including the trailing NUL. + /// + /// # Safety + /// + /// `addr` must be valid for the specific type of sockaddr. `len`, if + /// present, must not exceed the length of valid data in `addr`. + unsafe fn from_raw( + addr: *const libc::sockaddr, + len: Option<libc::socklen_t>, + ) -> Option<Self> + where + Self: Sized; + + /// Return the address family of this socket + /// + /// # Examples + /// One common use is to match on the family of a union type, like this: + /// ``` + /// # use nix::sys::socket::*; + /// let fd = socket(AddressFamily::Inet, SockType::Stream, + /// SockFlag::empty(), None).unwrap(); + /// let ss: SockaddrStorage = getsockname(fd).unwrap(); + /// match ss.family().unwrap() { + /// AddressFamily::Inet => println!("{}", ss.as_sockaddr_in().unwrap()), + /// AddressFamily::Inet6 => println!("{}", ss.as_sockaddr_in6().unwrap()), + /// _ => println!("Unexpected address family") + /// } + /// ``` + fn family(&self) -> Option<AddressFamily> { + // Safe since all implementors have a sa_family field at the same + // address, and they're all repr(C) + AddressFamily::from_i32(unsafe { + (*(self as *const Self as *const libc::sockaddr)).sa_family as i32 + }) + } + + cfg_if! { + if #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] { + /// Return the length of valid data in the sockaddr structure. + /// + /// For fixed-size sockaddrs, this should be the size of the + /// structure. But for variable-sized types like [`UnixAddr`] it + /// may be less. + fn len(&self) -> libc::socklen_t { + // Safe since all implementors have a sa_len field at the same + // address, and they're all repr(transparent). + // Robust for all implementors. + unsafe { + (*(self as *const Self as *const libc::sockaddr)).sa_len + }.into() + } + } else { + /// Return the length of valid data in the sockaddr structure. + /// + /// For fixed-size sockaddrs, this should be the size of the + /// structure. But for variable-sized types like [`UnixAddr`] it + /// may be less. + fn len(&self) -> libc::socklen_t { + // No robust default implementation is possible without an + // sa_len field. Implementors with a variable size must + // override this method. + mem::size_of_val(self) as libc::socklen_t + } + } + } + + /// Return the available space in the structure + fn size() -> libc::socklen_t + where + Self: Sized, + { + mem::size_of::<Self>() as libc::socklen_t + } +} + +impl private::SockaddrLikePriv for () { + fn as_mut_ptr(&mut self) -> *mut libc::sockaddr { + ptr::null_mut() + } +} + +/// `()` can be used in place of a real Sockaddr when no address is expected, +/// for example for a field of `Option<S> where S: SockaddrLike`. +// If this RFC ever stabilizes, then ! will be a better choice. +// https://github.com/rust-lang/rust/issues/35121 +impl SockaddrLike for () { + fn as_ptr(&self) -> *const libc::sockaddr { + ptr::null() + } + + unsafe fn from_raw( + _: *const libc::sockaddr, + _: Option<libc::socklen_t>, + ) -> Option<Self> + where + Self: Sized, + { + None + } + + fn family(&self) -> Option<AddressFamily> { + None + } + + fn len(&self) -> libc::socklen_t { + 0 + } +} + +/// An IPv4 socket address +// This is identical to net::SocketAddrV4. But the standard library +// doesn't allow direct access to the libc fields, which we need. So we +// reimplement it here. +#[cfg(feature = "net")] +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct SockaddrIn(libc::sockaddr_in); + +#[cfg(feature = "net")] +impl SockaddrIn { + /// Returns the IP address associated with this socket address, in native + /// endian. + pub const fn ip(&self) -> libc::in_addr_t { + u32::from_be(self.0.sin_addr.s_addr) + } + + /// Creates a new socket address from IPv4 octets and a port number. + pub fn new(a: u8, b: u8, c: u8, d: u8, port: u16) -> Self { + Self(libc::sockaddr_in { + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "haiku", + target_os = "openbsd" + ))] + sin_len: Self::size() as u8, + sin_family: AddressFamily::Inet as sa_family_t, + sin_port: u16::to_be(port), + sin_addr: libc::in_addr { + s_addr: u32::from_ne_bytes([a, b, c, d]), + }, + sin_zero: unsafe { mem::zeroed() }, + }) + } + + /// Returns the port number associated with this socket address, in native + /// endian. + pub const fn port(&self) -> u16 { + u16::from_be(self.0.sin_port) + } +} + +#[cfg(feature = "net")] +impl private::SockaddrLikePriv for SockaddrIn {} +#[cfg(feature = "net")] +impl SockaddrLike for SockaddrIn { + unsafe fn from_raw( + addr: *const libc::sockaddr, + len: Option<libc::socklen_t>, + ) -> Option<Self> + where + Self: Sized, + { + if let Some(l) = len { + if l != mem::size_of::<libc::sockaddr_in>() as libc::socklen_t { + return None; + } + } + if (*addr).sa_family as i32 != libc::AF_INET { + return None; + } + Some(Self(ptr::read_unaligned(addr as *const _))) + } +} + +#[cfg(feature = "net")] +impl AsRef<libc::sockaddr_in> for SockaddrIn { + fn as_ref(&self) -> &libc::sockaddr_in { + &self.0 + } +} + +#[cfg(feature = "net")] +impl fmt::Display for SockaddrIn { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let ne = u32::from_be(self.0.sin_addr.s_addr); + let port = u16::from_be(self.0.sin_port); + write!( + f, + "{}.{}.{}.{}:{}", + ne >> 24, + (ne >> 16) & 0xFF, + (ne >> 8) & 0xFF, + ne & 0xFF, + port + ) + } +} + +#[cfg(feature = "net")] +impl From<net::SocketAddrV4> for SockaddrIn { + fn from(addr: net::SocketAddrV4) -> Self { + Self(libc::sockaddr_in { + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "hermit", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + sin_len: mem::size_of::<libc::sockaddr_in>() as u8, + sin_family: AddressFamily::Inet as sa_family_t, + sin_port: addr.port().to_be(), // network byte order + sin_addr: ipv4addr_to_libc(*addr.ip()), + ..unsafe { mem::zeroed() } + }) + } +} + +#[cfg(feature = "net")] +impl From<SockaddrIn> for net::SocketAddrV4 { + fn from(addr: SockaddrIn) -> Self { + net::SocketAddrV4::new( + net::Ipv4Addr::from(addr.0.sin_addr.s_addr.to_ne_bytes()), + u16::from_be(addr.0.sin_port), + ) + } +} + +#[cfg(feature = "net")] +impl std::str::FromStr for SockaddrIn { + type Err = net::AddrParseError; + + fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { + net::SocketAddrV4::from_str(s).map(SockaddrIn::from) + } +} + +/// An IPv6 socket address +#[cfg(feature = "net")] +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct SockaddrIn6(libc::sockaddr_in6); + +#[cfg(feature = "net")] +impl SockaddrIn6 { + /// Returns the flow information associated with this address. + pub const fn flowinfo(&self) -> u32 { + self.0.sin6_flowinfo + } + + /// Returns the IP address associated with this socket address. + pub fn ip(&self) -> net::Ipv6Addr { + net::Ipv6Addr::from(self.0.sin6_addr.s6_addr) + } + + /// Returns the port number associated with this socket address, in native + /// endian. + pub const fn port(&self) -> u16 { + u16::from_be(self.0.sin6_port) + } + + /// Returns the scope ID associated with this address. + pub const fn scope_id(&self) -> u32 { + self.0.sin6_scope_id + } +} + +#[cfg(feature = "net")] +impl private::SockaddrLikePriv for SockaddrIn6 {} +#[cfg(feature = "net")] +impl SockaddrLike for SockaddrIn6 { + unsafe fn from_raw( + addr: *const libc::sockaddr, + len: Option<libc::socklen_t>, + ) -> Option<Self> + where + Self: Sized, + { + if let Some(l) = len { + if l != mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t { + return None; + } + } + if (*addr).sa_family as i32 != libc::AF_INET6 { + return None; + } + Some(Self(ptr::read_unaligned(addr as *const _))) + } +} + +#[cfg(feature = "net")] +impl AsRef<libc::sockaddr_in6> for SockaddrIn6 { + fn as_ref(&self) -> &libc::sockaddr_in6 { + &self.0 + } +} + +#[cfg(feature = "net")] +impl fmt::Display for SockaddrIn6 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // These things are really hard to display properly. Easier to let std + // do it. + let std = net::SocketAddrV6::new( + self.ip(), + self.port(), + self.flowinfo(), + self.scope_id(), + ); + std.fmt(f) + } +} + +#[cfg(feature = "net")] +impl From<net::SocketAddrV6> for SockaddrIn6 { + fn from(addr: net::SocketAddrV6) -> Self { + #[allow(clippy::needless_update)] // It isn't needless on Illumos + Self(libc::sockaddr_in6 { + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "hermit", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + sin6_len: mem::size_of::<libc::sockaddr_in6>() as u8, + sin6_family: AddressFamily::Inet6 as sa_family_t, + sin6_port: addr.port().to_be(), // network byte order + sin6_addr: ipv6addr_to_libc(addr.ip()), + sin6_flowinfo: addr.flowinfo(), // host byte order + sin6_scope_id: addr.scope_id(), // host byte order + ..unsafe { mem::zeroed() } + }) + } +} + +#[cfg(feature = "net")] +impl From<SockaddrIn6> for net::SocketAddrV6 { + fn from(addr: SockaddrIn6) -> Self { + net::SocketAddrV6::new( + net::Ipv6Addr::from(addr.0.sin6_addr.s6_addr), + u16::from_be(addr.0.sin6_port), + addr.0.sin6_flowinfo, + addr.0.sin6_scope_id, + ) + } +} + +#[cfg(feature = "net")] +impl std::str::FromStr for SockaddrIn6 { + type Err = net::AddrParseError; + + fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { + net::SocketAddrV6::from_str(s).map(SockaddrIn6::from) + } +} + +/// A container for any sockaddr type +/// +/// Just like C's `sockaddr_storage`, this type is large enough to hold any type +/// of sockaddr. It can be used as an argument with functions like +/// [`bind`](super::bind) and [`getsockname`](super::getsockname). Though it is +/// a union, it can be safely accessed through the `as_*` methods. +/// +/// # Example +/// ``` +/// # use nix::sys::socket::*; +/// # use std::str::FromStr; +/// let localhost = SockaddrIn::from_str("127.0.0.1:8081").unwrap(); +/// let fd = socket(AddressFamily::Inet, SockType::Stream, SockFlag::empty(), +/// None).unwrap(); +/// bind(fd, &localhost).expect("bind"); +/// let ss: SockaddrStorage = getsockname(fd).expect("getsockname"); +/// assert_eq!(&localhost, ss.as_sockaddr_in().unwrap()); +/// ``` +#[derive(Clone, Copy, Eq)] +#[repr(C)] +pub union SockaddrStorage { + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + alg: AlgAddr, + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + dl: LinkAddr, + #[cfg(any(target_os = "android", target_os = "linux"))] + nl: NetlinkAddr, + #[cfg(all( + feature = "ioctl", + any(target_os = "ios", target_os = "macos") + ))] + #[cfg_attr(docsrs, doc(cfg(feature = "ioctl")))] + sctl: SysControlAddr, + #[cfg(feature = "net")] + sin: SockaddrIn, + #[cfg(feature = "net")] + sin6: SockaddrIn6, + ss: libc::sockaddr_storage, + su: UnixAddr, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + vsock: VsockAddr, +} +impl private::SockaddrLikePriv for SockaddrStorage {} +impl SockaddrLike for SockaddrStorage { + unsafe fn from_raw( + addr: *const libc::sockaddr, + l: Option<libc::socklen_t>, + ) -> Option<Self> + where + Self: Sized, + { + if addr.is_null() { + return None; + } + if let Some(len) = l { + let ulen = len as usize; + if ulen < offset_of!(libc::sockaddr, sa_data) + || ulen > mem::size_of::<libc::sockaddr_storage>() + { + None + } else { + let mut ss: libc::sockaddr_storage = mem::zeroed(); + let ssp = &mut ss as *mut libc::sockaddr_storage as *mut u8; + ptr::copy(addr as *const u8, ssp, len as usize); + #[cfg(any( + target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux" + ))] + if i32::from(ss.ss_family) == libc::AF_UNIX { + // Safe because we UnixAddr is strictly smaller than + // SockaddrStorage, and we just initialized the structure. + (*(&mut ss as *mut libc::sockaddr_storage as *mut UnixAddr)).sun_len = len as u8; + } + Some(Self { ss }) + } + } else { + // If length is not available and addr is of a fixed-length type, + // copy it. If addr is of a variable length type and len is not + // available, then there's nothing we can do. + match (*addr).sa_family as i32 { + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_ALG => { + AlgAddr::from_raw(addr, l).map(|alg| Self { alg }) + } + #[cfg(feature = "net")] + libc::AF_INET => { + SockaddrIn::from_raw(addr, l).map(|sin| Self { sin }) + } + #[cfg(feature = "net")] + libc::AF_INET6 => { + SockaddrIn6::from_raw(addr, l).map(|sin6| Self { sin6 }) + } + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "haiku", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + libc::AF_LINK => { + LinkAddr::from_raw(addr, l).map(|dl| Self { dl }) + } + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_NETLINK => { + NetlinkAddr::from_raw(addr, l).map(|nl| Self { nl }) + } + #[cfg(any( + target_os = "android", + target_os = "fuchsia", + target_os = "linux" + ))] + #[cfg(feature = "net")] + libc::AF_PACKET => { + LinkAddr::from_raw(addr, l).map(|dl| Self { dl }) + } + #[cfg(all( + feature = "ioctl", + any(target_os = "ios", target_os = "macos") + ))] + libc::AF_SYSTEM => { + SysControlAddr::from_raw(addr, l).map(|sctl| Self { sctl }) + } + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_VSOCK => { + VsockAddr::from_raw(addr, l).map(|vsock| Self { vsock }) + } + _ => None, + } + } + } + + #[cfg(any( + target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux" + ))] + fn len(&self) -> libc::socklen_t { + match self.as_unix_addr() { + // The UnixAddr type knows its own length + Some(ua) => ua.len(), + // For all else, we're just a boring SockaddrStorage + None => mem::size_of_val(self) as libc::socklen_t + } + } +} + +macro_rules! accessors { + ( + $fname:ident, + $fname_mut:ident, + $sockty:ty, + $family:expr, + $libc_ty:ty, + $field:ident) => { + /// Safely and falliably downcast to an immutable reference + pub fn $fname(&self) -> Option<&$sockty> { + if self.family() == Some($family) + && self.len() >= mem::size_of::<$libc_ty>() as libc::socklen_t + { + // Safe because family and len are validated + Some(unsafe { &self.$field }) + } else { + None + } + } + + /// Safely and falliably downcast to a mutable reference + pub fn $fname_mut(&mut self) -> Option<&mut $sockty> { + if self.family() == Some($family) + && self.len() >= mem::size_of::<$libc_ty>() as libc::socklen_t + { + // Safe because family and len are validated + Some(unsafe { &mut self.$field }) + } else { + None + } + } + }; +} + +impl SockaddrStorage { + /// Downcast to an immutable `[UnixAddr]` reference. + pub fn as_unix_addr(&self) -> Option<&UnixAddr> { + cfg_if! { + if #[cfg(any(target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux" + ))] + { + let p = unsafe{ &self.ss as *const libc::sockaddr_storage }; + // Safe because UnixAddr is strictly smaller than + // sockaddr_storage, and we're fully initialized + let len = unsafe { + (*(p as *const UnixAddr )).sun_len as usize + }; + } else { + let len = self.len() as usize; + } + } + // Sanity checks + if self.family() != Some(AddressFamily::Unix) || + len < offset_of!(libc::sockaddr_un, sun_path) || + len > mem::size_of::<libc::sockaddr_un>() { + None + } else { + Some(unsafe{&self.su}) + } + } + + /// Downcast to a mutable `[UnixAddr]` reference. + pub fn as_unix_addr_mut(&mut self) -> Option<&mut UnixAddr> { + cfg_if! { + if #[cfg(any(target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux" + ))] + { + let p = unsafe{ &self.ss as *const libc::sockaddr_storage }; + // Safe because UnixAddr is strictly smaller than + // sockaddr_storage, and we're fully initialized + let len = unsafe { + (*(p as *const UnixAddr )).sun_len as usize + }; + } else { + let len = self.len() as usize; + } + } + // Sanity checks + if self.family() != Some(AddressFamily::Unix) || + len < offset_of!(libc::sockaddr_un, sun_path) || + len > mem::size_of::<libc::sockaddr_un>() { + None + } else { + Some(unsafe{&mut self.su}) + } + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + accessors! {as_alg_addr, as_alg_addr_mut, AlgAddr, + AddressFamily::Alg, libc::sockaddr_alg, alg} + + #[cfg(any( + target_os = "android", + target_os = "fuchsia", + target_os = "linux" + ))] + #[cfg(feature = "net")] + accessors! { + as_link_addr, as_link_addr_mut, LinkAddr, + AddressFamily::Packet, libc::sockaddr_ll, dl} + + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + accessors! { + as_link_addr, as_link_addr_mut, LinkAddr, + AddressFamily::Link, libc::sockaddr_dl, dl} + + #[cfg(feature = "net")] + accessors! { + as_sockaddr_in, as_sockaddr_in_mut, SockaddrIn, + AddressFamily::Inet, libc::sockaddr_in, sin} + + #[cfg(feature = "net")] + accessors! { + as_sockaddr_in6, as_sockaddr_in6_mut, SockaddrIn6, + AddressFamily::Inet6, libc::sockaddr_in6, sin6} + + #[cfg(any(target_os = "android", target_os = "linux"))] + accessors! {as_netlink_addr, as_netlink_addr_mut, NetlinkAddr, + AddressFamily::Netlink, libc::sockaddr_nl, nl} + + #[cfg(all(feature = "ioctl", any(target_os = "ios", target_os = "macos")))] + #[cfg_attr(docsrs, doc(cfg(feature = "ioctl")))] + accessors! {as_sys_control_addr, as_sys_control_addr_mut, SysControlAddr, + AddressFamily::System, libc::sockaddr_ctl, sctl} + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + accessors! {as_vsock_addr, as_vsock_addr_mut, VsockAddr, + AddressFamily::Vsock, libc::sockaddr_vm, vsock} +} + +impl fmt::Debug for SockaddrStorage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("SockaddrStorage") + // Safe because sockaddr_storage has the least specific + // field types + .field("ss", unsafe { &self.ss }) + .finish() + } +} + +impl fmt::Display for SockaddrStorage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + unsafe { + match self.ss.ss_family as i32 { + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_ALG => self.alg.fmt(f), + #[cfg(feature = "net")] + libc::AF_INET => self.sin.fmt(f), + #[cfg(feature = "net")] + libc::AF_INET6 => self.sin6.fmt(f), + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + libc::AF_LINK => self.dl.fmt(f), + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_NETLINK => self.nl.fmt(f), + #[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "fuchsia" + ))] + #[cfg(feature = "net")] + libc::AF_PACKET => self.dl.fmt(f), + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(feature = "ioctl")] + libc::AF_SYSTEM => self.sctl.fmt(f), + libc::AF_UNIX => self.su.fmt(f), + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_VSOCK => self.vsock.fmt(f), + _ => "<Address family unspecified>".fmt(f), + } + } + } +} + +#[cfg(feature = "net")] +impl From<net::SocketAddrV4> for SockaddrStorage { + fn from(s: net::SocketAddrV4) -> Self { + unsafe { + let mut ss: Self = mem::zeroed(); + ss.sin = SockaddrIn::from(s); + ss + } + } +} + +#[cfg(feature = "net")] +impl From<net::SocketAddrV6> for SockaddrStorage { + fn from(s: net::SocketAddrV6) -> Self { + unsafe { + let mut ss: Self = mem::zeroed(); + ss.sin6 = SockaddrIn6::from(s); + ss + } + } +} + +#[cfg(feature = "net")] +impl From<net::SocketAddr> for SockaddrStorage { + fn from(s: net::SocketAddr) -> Self { + match s { + net::SocketAddr::V4(sa4) => Self::from(sa4), + net::SocketAddr::V6(sa6) => Self::from(sa6), + } + } +} + +impl Hash for SockaddrStorage { + fn hash<H: Hasher>(&self, s: &mut H) { + unsafe { + match self.ss.ss_family as i32 { + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_ALG => self.alg.hash(s), + #[cfg(feature = "net")] + libc::AF_INET => self.sin.hash(s), + #[cfg(feature = "net")] + libc::AF_INET6 => self.sin6.hash(s), + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + libc::AF_LINK => self.dl.hash(s), + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_NETLINK => self.nl.hash(s), + #[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "fuchsia" + ))] + #[cfg(feature = "net")] + libc::AF_PACKET => self.dl.hash(s), + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(feature = "ioctl")] + libc::AF_SYSTEM => self.sctl.hash(s), + libc::AF_UNIX => self.su.hash(s), + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_VSOCK => self.vsock.hash(s), + _ => self.ss.hash(s), + } + } + } +} + +impl PartialEq for SockaddrStorage { + fn eq(&self, other: &Self) -> bool { + unsafe { + match (self.ss.ss_family as i32, other.ss.ss_family as i32) { + #[cfg(any(target_os = "android", target_os = "linux"))] + (libc::AF_ALG, libc::AF_ALG) => self.alg == other.alg, + #[cfg(feature = "net")] + (libc::AF_INET, libc::AF_INET) => self.sin == other.sin, + #[cfg(feature = "net")] + (libc::AF_INET6, libc::AF_INET6) => self.sin6 == other.sin6, + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + (libc::AF_LINK, libc::AF_LINK) => self.dl == other.dl, + #[cfg(any(target_os = "android", target_os = "linux"))] + (libc::AF_NETLINK, libc::AF_NETLINK) => self.nl == other.nl, + #[cfg(any( + target_os = "android", + target_os = "fuchsia", + target_os = "linux" + ))] + #[cfg(feature = "net")] + (libc::AF_PACKET, libc::AF_PACKET) => self.dl == other.dl, + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(feature = "ioctl")] + (libc::AF_SYSTEM, libc::AF_SYSTEM) => self.sctl == other.sctl, + (libc::AF_UNIX, libc::AF_UNIX) => self.su == other.su, + #[cfg(any(target_os = "android", target_os = "linux"))] + (libc::AF_VSOCK, libc::AF_VSOCK) => self.vsock == other.vsock, + _ => false, + } + } + } +} + +mod private { + pub trait SockaddrLikePriv { + /// Returns a mutable raw pointer to the inner structure. + /// + /// # Safety + /// + /// This method is technically safe, but modifying the inner structure's + /// `family` or `len` fields may result in violating Nix's invariants. + /// It is best to use this method only with foreign functions that do + /// not change the sockaddr type. + fn as_mut_ptr(&mut self) -> *mut libc::sockaddr { + self as *mut Self as *mut libc::sockaddr + } + } +} + +/// Represents a socket address +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[deprecated( + since = "0.24.0", + note = "use SockaddrLike or SockaddrStorage instead" +)] +#[allow(missing_docs)] // Since they're all deprecated anyway +#[allow(deprecated)] +#[non_exhaustive] +pub enum SockAddr { + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Inet(InetAddr), + Unix(UnixAddr), + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Netlink(NetlinkAddr), + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Alg(AlgAddr), + #[cfg(all( + feature = "ioctl", + any(target_os = "ios", target_os = "macos") + ))] + #[cfg_attr(docsrs, doc(cfg(feature = "ioctl")))] + SysControl(SysControlAddr), + /// Datalink address (MAC) + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Link(LinkAddr), + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Vsock(VsockAddr), +} + +#[allow(missing_docs)] // Since they're all deprecated anyway +#[allow(deprecated)] +impl SockAddr { + feature! { + #![feature = "net"] + pub fn new_inet(addr: InetAddr) -> SockAddr { + SockAddr::Inet(addr) + } + } + + pub fn new_unix<P: ?Sized + NixPath>(path: &P) -> Result<SockAddr> { + Ok(SockAddr::Unix(UnixAddr::new(path)?)) + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn new_netlink(pid: u32, groups: u32) -> SockAddr { + SockAddr::Netlink(NetlinkAddr::new(pid, groups)) + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn new_alg(alg_type: &str, alg_name: &str) -> SockAddr { + SockAddr::Alg(AlgAddr::new(alg_type, alg_name)) + } + + feature! { + #![feature = "ioctl"] + #[cfg(any(target_os = "ios", target_os = "macos"))] + pub fn new_sys_control(sockfd: RawFd, name: &str, unit: u32) -> Result<SockAddr> { + SysControlAddr::from_name(sockfd, name, unit).map(SockAddr::SysControl) + } + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn new_vsock(cid: u32, port: u32) -> SockAddr { + SockAddr::Vsock(VsockAddr::new(cid, port)) + } + + pub fn family(&self) -> AddressFamily { + match *self { + #[cfg(feature = "net")] + SockAddr::Inet(InetAddr::V4(..)) => AddressFamily::Inet, + #[cfg(feature = "net")] + SockAddr::Inet(InetAddr::V6(..)) => AddressFamily::Inet6, + SockAddr::Unix(..) => AddressFamily::Unix, + #[cfg(any(target_os = "android", target_os = "linux"))] + SockAddr::Netlink(..) => AddressFamily::Netlink, + #[cfg(any(target_os = "android", target_os = "linux"))] + SockAddr::Alg(..) => AddressFamily::Alg, + #[cfg(all( + feature = "ioctl", + any(target_os = "ios", target_os = "macos") + ))] + SockAddr::SysControl(..) => AddressFamily::System, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(feature = "net")] + SockAddr::Link(..) => AddressFamily::Packet, + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "illumos", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + SockAddr::Link(..) => AddressFamily::Link, + #[cfg(any(target_os = "android", target_os = "linux"))] + SockAddr::Vsock(..) => AddressFamily::Vsock, + } + } + + #[deprecated(since = "0.23.0", note = "use .to_string() instead")] + pub fn to_str(&self) -> String { + format!("{}", self) + } + + /// Creates a `SockAddr` struct from libc's sockaddr. + /// + /// Supports only the following address families: Unix, Inet (v4 & v6), Netlink and System. + /// Returns None for unsupported families. + /// + /// # Safety + /// + /// unsafe because it takes a raw pointer as argument. The caller must + /// ensure that the pointer is valid. + #[cfg(not(target_os = "fuchsia"))] + #[cfg(feature = "net")] + pub(crate) unsafe fn from_libc_sockaddr( + addr: *const libc::sockaddr, + ) -> Option<SockAddr> { + if addr.is_null() { + None + } else { + match AddressFamily::from_i32(i32::from((*addr).sa_family)) { + Some(AddressFamily::Unix) => None, + #[cfg(feature = "net")] + Some(AddressFamily::Inet) => Some(SockAddr::Inet( + InetAddr::V4(ptr::read_unaligned(addr as *const _)), + )), + #[cfg(feature = "net")] + Some(AddressFamily::Inet6) => Some(SockAddr::Inet( + InetAddr::V6(ptr::read_unaligned(addr as *const _)), + )), + #[cfg(any(target_os = "android", target_os = "linux"))] + Some(AddressFamily::Netlink) => Some(SockAddr::Netlink( + NetlinkAddr(ptr::read_unaligned(addr as *const _)), + )), + #[cfg(all( + feature = "ioctl", + any(target_os = "ios", target_os = "macos") + ))] + Some(AddressFamily::System) => Some(SockAddr::SysControl( + SysControlAddr(ptr::read_unaligned(addr as *const _)), + )), + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(feature = "net")] + Some(AddressFamily::Packet) => Some(SockAddr::Link(LinkAddr( + ptr::read_unaligned(addr as *const _), + ))), + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "illumos", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + Some(AddressFamily::Link) => { + let ether_addr = + LinkAddr(ptr::read_unaligned(addr as *const _)); + if ether_addr.is_empty() { + None + } else { + Some(SockAddr::Link(ether_addr)) + } + } + #[cfg(any(target_os = "android", target_os = "linux"))] + Some(AddressFamily::Vsock) => Some(SockAddr::Vsock(VsockAddr( + ptr::read_unaligned(addr as *const _), + ))), + // Other address families are currently not supported and simply yield a None + // entry instead of a proper conversion to a `SockAddr`. + Some(_) | None => None, + } + } + } + + /// Conversion from nix's SockAddr type to the underlying libc sockaddr type. + /// + /// This is useful for interfacing with other libc functions that don't yet have nix wrappers. + /// Returns a reference to the underlying data type (as a sockaddr reference) along + /// with the size of the actual data type. sockaddr is commonly used as a proxy for + /// a superclass as C doesn't support inheritance, so many functions that take + /// a sockaddr * need to take the size of the underlying type as well and then internally cast it back. + pub fn as_ffi_pair(&self) -> (&libc::sockaddr, libc::socklen_t) { + match *self { + #[cfg(feature = "net")] + SockAddr::Inet(InetAddr::V4(ref addr)) => ( + // This cast is always allowed in C + unsafe { + &*(addr as *const libc::sockaddr_in + as *const libc::sockaddr) + }, + mem::size_of_val(addr) as libc::socklen_t, + ), + #[cfg(feature = "net")] + SockAddr::Inet(InetAddr::V6(ref addr)) => ( + // This cast is always allowed in C + unsafe { + &*(addr as *const libc::sockaddr_in6 + as *const libc::sockaddr) + }, + mem::size_of_val(addr) as libc::socklen_t, + ), + SockAddr::Unix(ref unix_addr) => ( + // This cast is always allowed in C + unsafe { + &*(&unix_addr.sun as *const libc::sockaddr_un + as *const libc::sockaddr) + }, + unix_addr.sun_len() as libc::socklen_t, + ), + #[cfg(any(target_os = "android", target_os = "linux"))] + SockAddr::Netlink(NetlinkAddr(ref sa)) => ( + // This cast is always allowed in C + unsafe { + &*(sa as *const libc::sockaddr_nl as *const libc::sockaddr) + }, + mem::size_of_val(sa) as libc::socklen_t, + ), + #[cfg(any(target_os = "android", target_os = "linux"))] + SockAddr::Alg(AlgAddr(ref sa)) => ( + // This cast is always allowed in C + unsafe { + &*(sa as *const libc::sockaddr_alg as *const libc::sockaddr) + }, + mem::size_of_val(sa) as libc::socklen_t, + ), + #[cfg(all( + feature = "ioctl", + any(target_os = "ios", target_os = "macos") + ))] + SockAddr::SysControl(SysControlAddr(ref sa)) => ( + // This cast is always allowed in C + unsafe { + &*(sa as *const libc::sockaddr_ctl as *const libc::sockaddr) + }, + mem::size_of_val(sa) as libc::socklen_t, + ), + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(feature = "net")] + SockAddr::Link(LinkAddr(ref addr)) => ( + // This cast is always allowed in C + unsafe { + &*(addr as *const libc::sockaddr_ll + as *const libc::sockaddr) + }, + mem::size_of_val(addr) as libc::socklen_t, + ), + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + SockAddr::Link(LinkAddr(ref addr)) => ( + // This cast is always allowed in C + unsafe { + &*(addr as *const libc::sockaddr_dl + as *const libc::sockaddr) + }, + mem::size_of_val(addr) as libc::socklen_t, + ), + #[cfg(any(target_os = "android", target_os = "linux"))] + SockAddr::Vsock(VsockAddr(ref sa)) => ( + // This cast is always allowed in C + unsafe { + &*(sa as *const libc::sockaddr_vm as *const libc::sockaddr) + }, + mem::size_of_val(sa) as libc::socklen_t, + ), + } + } +} + +#[allow(deprecated)] +impl fmt::Display for SockAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + #[cfg(feature = "net")] + SockAddr::Inet(ref inet) => inet.fmt(f), + SockAddr::Unix(ref unix) => unix.fmt(f), + #[cfg(any(target_os = "android", target_os = "linux"))] + SockAddr::Netlink(ref nl) => nl.fmt(f), + #[cfg(any(target_os = "android", target_os = "linux"))] + SockAddr::Alg(ref nl) => nl.fmt(f), + #[cfg(all( + feature = "ioctl", + any(target_os = "ios", target_os = "macos") + ))] + SockAddr::SysControl(ref sc) => sc.fmt(f), + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "illumos", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + SockAddr::Link(ref ether_addr) => ether_addr.fmt(f), + #[cfg(any(target_os = "android", target_os = "linux"))] + SockAddr::Vsock(ref svm) => svm.fmt(f), + } + } +} + +#[cfg(not(target_os = "fuchsia"))] +#[cfg(feature = "net")] +#[allow(deprecated)] +impl private::SockaddrLikePriv for SockAddr {} +#[cfg(not(target_os = "fuchsia"))] +#[cfg(feature = "net")] +#[allow(deprecated)] +impl SockaddrLike for SockAddr { + unsafe fn from_raw( + addr: *const libc::sockaddr, + _len: Option<libc::socklen_t>, + ) -> Option<Self> { + Self::from_libc_sockaddr(addr) + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub mod netlink { + use super::*; + use crate::sys::socket::addr::AddressFamily; + use libc::{sa_family_t, sockaddr_nl}; + use std::{fmt, mem}; + + /// Address for the Linux kernel user interface device. + /// + /// # References + /// + /// [netlink(7)](https://man7.org/linux/man-pages/man7/netlink.7.html) + #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] + #[repr(transparent)] + pub struct NetlinkAddr(pub(in super::super) sockaddr_nl); + + impl NetlinkAddr { + /// Construct a new socket address from its port ID and multicast groups + /// mask. + pub fn new(pid: u32, groups: u32) -> NetlinkAddr { + let mut addr: sockaddr_nl = unsafe { mem::zeroed() }; + addr.nl_family = AddressFamily::Netlink as sa_family_t; + addr.nl_pid = pid; + addr.nl_groups = groups; + + NetlinkAddr(addr) + } + + /// Return the socket's port ID. + pub const fn pid(&self) -> u32 { + self.0.nl_pid + } + + /// Return the socket's multicast groups mask + pub const fn groups(&self) -> u32 { + self.0.nl_groups + } + } + + impl private::SockaddrLikePriv for NetlinkAddr {} + impl SockaddrLike for NetlinkAddr { + unsafe fn from_raw( + addr: *const libc::sockaddr, + len: Option<libc::socklen_t>, + ) -> Option<Self> + where + Self: Sized, + { + if let Some(l) = len { + if l != mem::size_of::<libc::sockaddr_nl>() as libc::socklen_t { + return None; + } + } + if (*addr).sa_family as i32 != libc::AF_NETLINK { + return None; + } + Some(Self(ptr::read_unaligned(addr as *const _))) + } + } + + impl AsRef<libc::sockaddr_nl> for NetlinkAddr { + fn as_ref(&self) -> &libc::sockaddr_nl { + &self.0 + } + } + + impl fmt::Display for NetlinkAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "pid: {} groups: {}", self.pid(), self.groups()) + } + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub mod alg { + use super::*; + use libc::{c_char, sockaddr_alg, AF_ALG}; + use std::ffi::CStr; + use std::hash::{Hash, Hasher}; + use std::{fmt, mem, str}; + + /// Socket address for the Linux kernel crypto API + #[derive(Copy, Clone)] + #[repr(transparent)] + pub struct AlgAddr(pub(in super::super) sockaddr_alg); + + impl private::SockaddrLikePriv for AlgAddr {} + impl SockaddrLike for AlgAddr { + unsafe fn from_raw( + addr: *const libc::sockaddr, + l: Option<libc::socklen_t>, + ) -> Option<Self> + where + Self: Sized, + { + if let Some(l) = l { + if l != mem::size_of::<libc::sockaddr_alg>() as libc::socklen_t + { + return None; + } + } + if (*addr).sa_family as i32 != libc::AF_ALG { + return None; + } + Some(Self(ptr::read_unaligned(addr as *const _))) + } + } + + impl AsRef<libc::sockaddr_alg> for AlgAddr { + fn as_ref(&self) -> &libc::sockaddr_alg { + &self.0 + } + } + + // , PartialEq, Eq, Debug, Hash + impl PartialEq for AlgAddr { + fn eq(&self, other: &Self) -> bool { + let (inner, other) = (self.0, other.0); + ( + inner.salg_family, + &inner.salg_type[..], + inner.salg_feat, + inner.salg_mask, + &inner.salg_name[..], + ) == ( + other.salg_family, + &other.salg_type[..], + other.salg_feat, + other.salg_mask, + &other.salg_name[..], + ) + } + } + + impl Eq for AlgAddr {} + + impl Hash for AlgAddr { + fn hash<H: Hasher>(&self, s: &mut H) { + let inner = self.0; + ( + inner.salg_family, + &inner.salg_type[..], + inner.salg_feat, + inner.salg_mask, + &inner.salg_name[..], + ) + .hash(s); + } + } + + impl AlgAddr { + /// Construct an `AF_ALG` socket from its cipher name and type. + pub fn new(alg_type: &str, alg_name: &str) -> AlgAddr { + let mut addr: sockaddr_alg = unsafe { mem::zeroed() }; + addr.salg_family = AF_ALG as u16; + addr.salg_type[..alg_type.len()] + .copy_from_slice(alg_type.to_string().as_bytes()); + addr.salg_name[..alg_name.len()] + .copy_from_slice(alg_name.to_string().as_bytes()); + + AlgAddr(addr) + } + + /// Return the socket's cipher type, for example `hash` or `aead`. + pub fn alg_type(&self) -> &CStr { + unsafe { + CStr::from_ptr(self.0.salg_type.as_ptr() as *const c_char) + } + } + + /// Return the socket's cipher name, for example `sha1`. + pub fn alg_name(&self) -> &CStr { + unsafe { + CStr::from_ptr(self.0.salg_name.as_ptr() as *const c_char) + } + } + } + + impl fmt::Display for AlgAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "type: {} alg: {}", + self.alg_name().to_string_lossy(), + self.alg_type().to_string_lossy() + ) + } + } + + impl fmt::Debug for AlgAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) + } + } +} + +feature! { +#![feature = "ioctl"] +#[cfg(any(target_os = "ios", target_os = "macos"))] +pub mod sys_control { + use crate::sys::socket::addr::AddressFamily; + use libc::{self, c_uchar}; + use std::{fmt, mem, ptr}; + use std::os::unix::io::RawFd; + use crate::{Errno, Result}; + use super::{private, SockaddrLike}; + + // FIXME: Move type into `libc` + #[repr(C)] + #[derive(Clone, Copy)] + #[allow(missing_debug_implementations)] + pub struct ctl_ioc_info { + pub ctl_id: u32, + pub ctl_name: [c_uchar; MAX_KCTL_NAME], + } + + const CTL_IOC_MAGIC: u8 = b'N'; + const CTL_IOC_INFO: u8 = 3; + const MAX_KCTL_NAME: usize = 96; + + ioctl_readwrite!(ctl_info, CTL_IOC_MAGIC, CTL_IOC_INFO, ctl_ioc_info); + + /// Apple system control socket + /// + /// # References + /// + /// <https://developer.apple.com/documentation/kernel/sockaddr_ctl> + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + #[repr(transparent)] + pub struct SysControlAddr(pub(in super::super) libc::sockaddr_ctl); + + impl private::SockaddrLikePriv for SysControlAddr {} + impl SockaddrLike for SysControlAddr { + unsafe fn from_raw(addr: *const libc::sockaddr, len: Option<libc::socklen_t>) + -> Option<Self> where Self: Sized + { + if let Some(l) = len { + if l != mem::size_of::<libc::sockaddr_ctl>() as libc::socklen_t { + return None; + } + } + if (*addr).sa_family as i32 != libc::AF_SYSTEM { + return None; + } + Some(Self(ptr::read_unaligned(addr as *const _))) + } + } + + impl AsRef<libc::sockaddr_ctl> for SysControlAddr { + fn as_ref(&self) -> &libc::sockaddr_ctl { + &self.0 + } + } + + impl SysControlAddr { + /// Construct a new `SysControlAddr` from its kernel unique identifier + /// and unit number. + pub const fn new(id: u32, unit: u32) -> SysControlAddr { + let addr = libc::sockaddr_ctl { + sc_len: mem::size_of::<libc::sockaddr_ctl>() as c_uchar, + sc_family: AddressFamily::System as c_uchar, + ss_sysaddr: libc::AF_SYS_CONTROL as u16, + sc_id: id, + sc_unit: unit, + sc_reserved: [0; 5] + }; + + SysControlAddr(addr) + } + + /// Construct a new `SysControlAddr` from its human readable name and + /// unit number. + pub fn from_name(sockfd: RawFd, name: &str, unit: u32) -> Result<SysControlAddr> { + if name.len() > MAX_KCTL_NAME { + return Err(Errno::ENAMETOOLONG); + } + + let mut ctl_name = [0; MAX_KCTL_NAME]; + ctl_name[..name.len()].clone_from_slice(name.as_bytes()); + let mut info = ctl_ioc_info { ctl_id: 0, ctl_name }; + + unsafe { ctl_info(sockfd, &mut info)?; } + + Ok(SysControlAddr::new(info.ctl_id, unit)) + } + + /// Return the kernel unique identifier + pub const fn id(&self) -> u32 { + self.0.sc_id + } + + /// Return the kernel controller private unit number. + pub const fn unit(&self) -> u32 { + self.0.sc_unit + } + } + + impl fmt::Display for SysControlAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self, f) + } + } +} +} + +#[cfg(any(target_os = "android", target_os = "linux", target_os = "fuchsia"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +mod datalink { + feature! { + #![feature = "net"] + use super::{fmt, mem, private, ptr, SockaddrLike}; + + /// Hardware Address + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + #[repr(transparent)] + pub struct LinkAddr(pub(in super::super) libc::sockaddr_ll); + + impl LinkAddr { + /// Physical-layer protocol + pub fn protocol(&self) -> u16 { + self.0.sll_protocol + } + + /// Interface number + pub fn ifindex(&self) -> usize { + self.0.sll_ifindex as usize + } + + /// ARP hardware type + pub fn hatype(&self) -> u16 { + self.0.sll_hatype + } + + /// Packet type + pub fn pkttype(&self) -> u8 { + self.0.sll_pkttype + } + + /// Length of MAC address + pub fn halen(&self) -> usize { + self.0.sll_halen as usize + } + + /// Physical-layer address (MAC) + // Returns an Option just for cross-platform compatibility + pub fn addr(&self) -> Option<[u8; 6]> { + Some([ + self.0.sll_addr[0], + self.0.sll_addr[1], + self.0.sll_addr[2], + self.0.sll_addr[3], + self.0.sll_addr[4], + self.0.sll_addr[5], + ]) + } + } + + impl fmt::Display for LinkAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(addr) = self.addr() { + write!(f, "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + addr[0], + addr[1], + addr[2], + addr[3], + addr[4], + addr[5]) + } else { + Ok(()) + } + } + } + impl private::SockaddrLikePriv for LinkAddr {} + impl SockaddrLike for LinkAddr { + unsafe fn from_raw(addr: *const libc::sockaddr, + len: Option<libc::socklen_t>) + -> Option<Self> where Self: Sized + { + if let Some(l) = len { + if l != mem::size_of::<libc::sockaddr_ll>() as libc::socklen_t { + return None; + } + } + if (*addr).sa_family as i32 != libc::AF_PACKET { + return None; + } + Some(Self(ptr::read_unaligned(addr as *const _))) + } + } + + impl AsRef<libc::sockaddr_ll> for LinkAddr { + fn as_ref(&self) -> &libc::sockaddr_ll { + &self.0 + } + } + + } +} + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "haiku", + target_os = "openbsd" +))] +#[cfg_attr(docsrs, doc(cfg(all())))] +mod datalink { + feature! { + #![feature = "net"] + use super::{fmt, mem, private, ptr, SockaddrLike}; + + /// Hardware Address + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + #[repr(transparent)] + pub struct LinkAddr(pub(in super::super) libc::sockaddr_dl); + + impl LinkAddr { + /// interface index, if != 0, system given index for interface + #[cfg(not(target_os = "haiku"))] + pub fn ifindex(&self) -> usize { + self.0.sdl_index as usize + } + + /// Datalink type + #[cfg(not(target_os = "haiku"))] + pub fn datalink_type(&self) -> u8 { + self.0.sdl_type + } + + /// MAC address start position + pub fn nlen(&self) -> usize { + self.0.sdl_nlen as usize + } + + /// link level address length + pub fn alen(&self) -> usize { + self.0.sdl_alen as usize + } + + /// link layer selector length + #[cfg(not(target_os = "haiku"))] + pub fn slen(&self) -> usize { + self.0.sdl_slen as usize + } + + /// if link level address length == 0, + /// or `sdl_data` not be larger. + pub fn is_empty(&self) -> bool { + let nlen = self.nlen(); + let alen = self.alen(); + let data_len = self.0.sdl_data.len(); + + alen == 0 || nlen + alen >= data_len + } + + /// Physical-layer address (MAC) + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + pub fn addr(&self) -> Option<[u8; 6]> { + let nlen = self.nlen(); + let data = self.0.sdl_data; + + if self.is_empty() { + None + } else { + Some([ + data[nlen] as u8, + data[nlen + 1] as u8, + data[nlen + 2] as u8, + data[nlen + 3] as u8, + data[nlen + 4] as u8, + data[nlen + 5] as u8, + ]) + } + } + } + + impl fmt::Display for LinkAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(addr) = self.addr() { + write!(f, "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + addr[0], + addr[1], + addr[2], + addr[3], + addr[4], + addr[5]) + } else { + Ok(()) + } + } + } + impl private::SockaddrLikePriv for LinkAddr {} + impl SockaddrLike for LinkAddr { + unsafe fn from_raw(addr: *const libc::sockaddr, + len: Option<libc::socklen_t>) + -> Option<Self> where Self: Sized + { + if let Some(l) = len { + if l != mem::size_of::<libc::sockaddr_dl>() as libc::socklen_t { + return None; + } + } + if (*addr).sa_family as i32 != libc::AF_LINK { + return None; + } + Some(Self(ptr::read_unaligned(addr as *const _))) + } + } + + impl AsRef<libc::sockaddr_dl> for LinkAddr { + fn as_ref(&self) -> &libc::sockaddr_dl { + &self.0 + } + } + + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub mod vsock { + use super::*; + use crate::sys::socket::addr::AddressFamily; + use libc::{sa_family_t, sockaddr_vm}; + use std::hash::{Hash, Hasher}; + use std::{fmt, mem}; + + /// Socket address for VMWare VSockets protocol + /// + /// # References + /// + /// [vsock(7)](https://man7.org/linux/man-pages/man7/vsock.7.html) + #[derive(Copy, Clone)] + #[repr(transparent)] + pub struct VsockAddr(pub(in super::super) sockaddr_vm); + + impl private::SockaddrLikePriv for VsockAddr {} + impl SockaddrLike for VsockAddr { + unsafe fn from_raw( + addr: *const libc::sockaddr, + len: Option<libc::socklen_t>, + ) -> Option<Self> + where + Self: Sized, + { + if let Some(l) = len { + if l != mem::size_of::<libc::sockaddr_vm>() as libc::socklen_t { + return None; + } + } + if (*addr).sa_family as i32 != libc::AF_VSOCK { + return None; + } + Some(Self(ptr::read_unaligned(addr as *const _))) + } + } + + impl AsRef<libc::sockaddr_vm> for VsockAddr { + fn as_ref(&self) -> &libc::sockaddr_vm { + &self.0 + } + } + + impl PartialEq for VsockAddr { + fn eq(&self, other: &Self) -> bool { + let (inner, other) = (self.0, other.0); + (inner.svm_family, inner.svm_cid, inner.svm_port) + == (other.svm_family, other.svm_cid, other.svm_port) + } + } + + impl Eq for VsockAddr {} + + impl Hash for VsockAddr { + fn hash<H: Hasher>(&self, s: &mut H) { + let inner = self.0; + (inner.svm_family, inner.svm_cid, inner.svm_port).hash(s); + } + } + + /// VSOCK Address + /// + /// The address for AF_VSOCK socket is defined as a combination of a + /// 32-bit Context Identifier (CID) and a 32-bit port number. + impl VsockAddr { + /// Construct a `VsockAddr` from its raw fields. + pub fn new(cid: u32, port: u32) -> VsockAddr { + let mut addr: sockaddr_vm = unsafe { mem::zeroed() }; + addr.svm_family = AddressFamily::Vsock as sa_family_t; + addr.svm_cid = cid; + addr.svm_port = port; + + VsockAddr(addr) + } + + /// Context Identifier (CID) + pub fn cid(&self) -> u32 { + self.0.svm_cid + } + + /// Port number + pub fn port(&self) -> u32 { + self.0.svm_port + } + } + + impl fmt::Display for VsockAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "cid: {} port: {}", self.cid(), self.port()) + } + } + + impl fmt::Debug for VsockAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + mod types { + use super::*; + + #[test] + fn test_ipv4addr_to_libc() { + let s = std::net::Ipv4Addr::new(1, 2, 3, 4); + let l = ipv4addr_to_libc(s); + assert_eq!(l.s_addr, u32::to_be(0x01020304)); + } + + #[test] + fn test_ipv6addr_to_libc() { + let s = std::net::Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8); + let l = ipv6addr_to_libc(&s); + assert_eq!( + l.s6_addr, + [0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8] + ); + } + } + + mod link { + #![allow(clippy::cast_ptr_alignment)] + + #[cfg(any( + target_os = "ios", + target_os = "macos", + target_os = "illumos" + ))] + use super::super::super::socklen_t; + use super::*; + + /// Don't panic when trying to display an empty datalink address + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[test] + fn test_datalink_display() { + use super::super::LinkAddr; + use std::mem; + + let la = LinkAddr(libc::sockaddr_dl { + sdl_len: 56, + sdl_family: 18, + sdl_index: 5, + sdl_type: 24, + sdl_nlen: 3, + sdl_alen: 0, + sdl_slen: 0, + ..unsafe { mem::zeroed() } + }); + format!("{}", la); + } + + #[cfg(all( + any( + target_os = "android", + target_os = "fuchsia", + target_os = "linux" + ), + target_endian = "little" + ))] + #[test] + fn linux_loopback() { + #[repr(align(2))] + struct Raw([u8; 20]); + + let bytes = Raw([ + 17u8, 0, 0, 0, 1, 0, 0, 0, 4, 3, 0, 6, 1, 2, 3, 4, 5, 6, 0, 0, + ]); + let sa = bytes.0.as_ptr() as *const libc::sockaddr; + let len = None; + let sock_addr = + unsafe { SockaddrStorage::from_raw(sa, len) }.unwrap(); + assert_eq!(sock_addr.family(), Some(AddressFamily::Packet)); + match sock_addr.as_link_addr() { + Some(dl) => assert_eq!(dl.addr(), Some([1, 2, 3, 4, 5, 6])), + None => panic!("Can't unwrap sockaddr storage"), + } + } + + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[test] + fn macos_loopback() { + let bytes = + [20i8, 18, 1, 0, 24, 3, 0, 0, 108, 111, 48, 0, 0, 0, 0, 0]; + let sa = bytes.as_ptr() as *const libc::sockaddr; + let len = Some(bytes.len() as socklen_t); + let sock_addr = + unsafe { SockaddrStorage::from_raw(sa, len) }.unwrap(); + assert_eq!(sock_addr.family(), Some(AddressFamily::Link)); + match sock_addr.as_link_addr() { + Some(dl) => { + assert!(dl.addr().is_none()); + } + None => panic!("Can't unwrap sockaddr storage"), + } + } + + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[test] + fn macos_tap() { + let bytes = [ + 20i8, 18, 7, 0, 6, 3, 6, 0, 101, 110, 48, 24, 101, -112, -35, + 76, -80, + ]; + let ptr = bytes.as_ptr(); + let sa = ptr as *const libc::sockaddr; + let len = Some(bytes.len() as socklen_t); + + let sock_addr = + unsafe { SockaddrStorage::from_raw(sa, len).unwrap() }; + assert_eq!(sock_addr.family(), Some(AddressFamily::Link)); + match sock_addr.as_link_addr() { + Some(dl) => { + assert_eq!(dl.addr(), Some([24u8, 101, 144, 221, 76, 176])) + } + None => panic!("Can't unwrap sockaddr storage"), + } + } + + #[cfg(target_os = "illumos")] + #[test] + fn illumos_tap() { + let bytes = [25u8, 0, 0, 0, 6, 0, 6, 0, 24, 101, 144, 221, 76, 176]; + let ptr = bytes.as_ptr(); + let sa = ptr as *const libc::sockaddr; + let len = Some(bytes.len() as socklen_t); + let _sock_addr = unsafe { SockaddrStorage::from_raw(sa, len) }; + + assert!(_sock_addr.is_some()); + + let sock_addr = _sock_addr.unwrap(); + + assert_eq!(sock_addr.family().unwrap(), AddressFamily::Link); + + assert_eq!( + sock_addr.as_link_addr().unwrap().addr(), + Some([24u8, 101, 144, 221, 76, 176]) + ); + } + + #[test] + fn size() { + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "illumos", + target_os = "openbsd", + target_os = "haiku" + ))] + let l = mem::size_of::<libc::sockaddr_dl>(); + #[cfg(any( + target_os = "android", + target_os = "fuchsia", + target_os = "linux" + ))] + let l = mem::size_of::<libc::sockaddr_ll>(); + assert_eq!(LinkAddr::size() as usize, l); + } + } + + mod sockaddr_in { + use super::*; + use std::str::FromStr; + + #[test] + fn display() { + let s = "127.0.0.1:8080"; + let addr = SockaddrIn::from_str(s).unwrap(); + assert_eq!(s, format!("{}", addr)); + } + + #[test] + fn size() { + assert_eq!( + mem::size_of::<libc::sockaddr_in>(), + SockaddrIn::size() as usize + ); + } + } + + mod sockaddr_in6 { + use super::*; + use std::str::FromStr; + + #[test] + fn display() { + let s = "[1234:5678:90ab:cdef::1111:2222]:8080"; + let addr = SockaddrIn6::from_str(s).unwrap(); + assert_eq!(s, format!("{}", addr)); + } + + #[test] + fn size() { + assert_eq!( + mem::size_of::<libc::sockaddr_in6>(), + SockaddrIn6::size() as usize + ); + } + + #[test] + // Ensure that we can convert to-and-from std::net variants without change. + fn to_and_from() { + let s = "[1234:5678:90ab:cdef::1111:2222]:8080"; + let mut nix_sin6 = SockaddrIn6::from_str(s).unwrap(); + nix_sin6.0.sin6_flowinfo = 0x12345678; + nix_sin6.0.sin6_scope_id = 0x9abcdef0; + + let std_sin6 : std::net::SocketAddrV6 = nix_sin6.into(); + assert_eq!(nix_sin6, std_sin6.into()); + } + } + + mod sockaddr_storage { + use super::*; + + #[test] + fn from_sockaddr_un_named() { + let ua = UnixAddr::new("/var/run/mysock").unwrap(); + let ptr = ua.as_ptr() as *const libc::sockaddr; + let ss = unsafe { + SockaddrStorage::from_raw(ptr, Some(ua.len())) + }.unwrap(); + assert_eq!(ss.len(), ua.len()); + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[test] + fn from_sockaddr_un_abstract_named() { + let name = String::from("nix\0abstract\0test"); + let ua = UnixAddr::new_abstract(name.as_bytes()).unwrap(); + let ptr = ua.as_ptr() as *const libc::sockaddr; + let ss = unsafe { + SockaddrStorage::from_raw(ptr, Some(ua.len())) + }.unwrap(); + assert_eq!(ss.len(), ua.len()); + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[test] + fn from_sockaddr_un_abstract_unnamed() { + let ua = UnixAddr::new_unnamed(); + let ptr = ua.as_ptr() as *const libc::sockaddr; + let ss = unsafe { + SockaddrStorage::from_raw(ptr, Some(ua.len())) + }.unwrap(); + assert_eq!(ss.len(), ua.len()); + } + } + + mod unixaddr { + use super::*; + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[test] + fn abstract_sun_path() { + let name = String::from("nix\0abstract\0test"); + let addr = UnixAddr::new_abstract(name.as_bytes()).unwrap(); + + let sun_path1 = + unsafe { &(*addr.as_ptr()).sun_path[..addr.path_len()] }; + let sun_path2 = [ + 0, 110, 105, 120, 0, 97, 98, 115, 116, 114, 97, 99, 116, 0, + 116, 101, 115, 116, + ]; + assert_eq!(sun_path1, sun_path2); + } + + #[test] + fn size() { + assert_eq!( + mem::size_of::<libc::sockaddr_un>(), + UnixAddr::size() as usize + ); + } + } +} diff --git a/third_party/rust/nix/src/sys/socket/mod.rs b/third_party/rust/nix/src/sys/socket/mod.rs new file mode 100644 index 0000000000..8513b6fbe7 --- /dev/null +++ b/third_party/rust/nix/src/sys/socket/mod.rs @@ -0,0 +1,2487 @@ +//! Socket interface functions +//! +//! [Further reading](https://man7.org/linux/man-pages/man7/socket.7.html) +#[cfg(target_os = "linux")] +#[cfg(feature = "uio")] +use crate::sys::time::TimeSpec; +#[cfg(feature = "uio")] +use crate::sys::time::TimeVal; +use crate::{errno::Errno, Result}; +use cfg_if::cfg_if; +use libc::{ + self, c_int, c_void, iovec, size_t, socklen_t, CMSG_DATA, CMSG_FIRSTHDR, + CMSG_LEN, CMSG_NXTHDR, +}; +use std::convert::{TryFrom, TryInto}; +use std::io::{IoSlice, IoSliceMut}; +#[cfg(feature = "net")] +use std::net; +use std::os::unix::io::RawFd; +use std::{mem, ptr, slice}; + +#[deny(missing_docs)] +mod addr; +#[deny(missing_docs)] +pub mod sockopt; + +/* + * + * ===== Re-exports ===== + * + */ + +pub use self::addr::{SockaddrLike, SockaddrStorage}; + +#[cfg(not(any(target_os = "illumos", target_os = "solaris")))] +#[allow(deprecated)] +pub use self::addr::{AddressFamily, SockAddr, UnixAddr}; +#[cfg(any(target_os = "illumos", target_os = "solaris"))] +#[allow(deprecated)] +pub use self::addr::{AddressFamily, SockAddr, UnixAddr}; +#[allow(deprecated)] +#[cfg(not(any( + target_os = "illumos", + target_os = "solaris", + target_os = "haiku" +)))] +#[cfg(feature = "net")] +pub use self::addr::{ + InetAddr, IpAddr, Ipv4Addr, Ipv6Addr, LinkAddr, SockaddrIn, SockaddrIn6, +}; +#[allow(deprecated)] +#[cfg(any( + target_os = "illumos", + target_os = "solaris", + target_os = "haiku" +))] +#[cfg(feature = "net")] +pub use self::addr::{ + InetAddr, IpAddr, Ipv4Addr, Ipv6Addr, SockaddrIn, SockaddrIn6, +}; + +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use crate::sys::socket::addr::alg::AlgAddr; +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use crate::sys::socket::addr::netlink::NetlinkAddr; +#[cfg(any(target_os = "ios", target_os = "macos"))] +#[cfg(feature = "ioctl")] +pub use crate::sys::socket::addr::sys_control::SysControlAddr; +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use crate::sys::socket::addr::vsock::VsockAddr; + +#[cfg(feature = "uio")] +pub use libc::{cmsghdr, msghdr}; +pub use libc::{sa_family_t, sockaddr, sockaddr_storage, sockaddr_un}; +#[cfg(feature = "net")] +pub use libc::{sockaddr_in, sockaddr_in6}; + +// Needed by the cmsg_space macro +#[doc(hidden)] +pub use libc::{c_uint, CMSG_SPACE}; + +#[cfg(feature = "net")] +use crate::sys::socket::addr::{ipv4addr_to_libc, ipv6addr_to_libc}; + +/// These constants are used to specify the communication semantics +/// when creating a socket with [`socket()`](fn.socket.html) +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[repr(i32)] +#[non_exhaustive] +pub enum SockType { + /// Provides sequenced, reliable, two-way, connection- + /// based byte streams. An out-of-band data transmission + /// mechanism may be supported. + Stream = libc::SOCK_STREAM, + /// Supports datagrams (connectionless, unreliable + /// messages of a fixed maximum length). + Datagram = libc::SOCK_DGRAM, + /// Provides a sequenced, reliable, two-way connection- + /// based data transmission path for datagrams of fixed + /// maximum length; a consumer is required to read an + /// entire packet with each input system call. + SeqPacket = libc::SOCK_SEQPACKET, + /// Provides raw network protocol access. + Raw = libc::SOCK_RAW, + /// Provides a reliable datagram layer that does not + /// guarantee ordering. + #[cfg(not(any(target_os = "haiku")))] + Rdm = libc::SOCK_RDM, +} +// The TryFrom impl could've been derived using libc_enum!. But for +// backwards-compatibility with Nix-0.25.0 we manually implement it, so as to +// keep the old variant names. +impl TryFrom<i32> for SockType { + type Error = crate::Error; + + fn try_from(x: i32) -> Result<Self> { + match x { + libc::SOCK_STREAM => Ok(Self::Stream), + libc::SOCK_DGRAM => Ok(Self::Datagram), + libc::SOCK_SEQPACKET => Ok(Self::SeqPacket), + libc::SOCK_RAW => Ok(Self::Raw), + #[cfg(not(any(target_os = "haiku")))] + libc::SOCK_RDM => Ok(Self::Rdm), + _ => Err(Errno::EINVAL) + } + } +} + +/// Constants used in [`socket`](fn.socket.html) and [`socketpair`](fn.socketpair.html) +/// to specify the protocol to use. +#[repr(i32)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[non_exhaustive] +pub enum SockProtocol { + /// TCP protocol ([ip(7)](https://man7.org/linux/man-pages/man7/ip.7.html)) + Tcp = libc::IPPROTO_TCP, + /// UDP protocol ([ip(7)](https://man7.org/linux/man-pages/man7/ip.7.html)) + Udp = libc::IPPROTO_UDP, + /// Raw sockets ([raw(7)](https://man7.org/linux/man-pages/man7/raw.7.html)) + Raw = libc::IPPROTO_RAW, + /// Allows applications and other KEXTs to be notified when certain kernel events occur + /// ([ref](https://developer.apple.com/library/content/documentation/Darwin/Conceptual/NKEConceptual/control/control.html)) + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + KextEvent = libc::SYSPROTO_EVENT, + /// Allows applications to configure and control a KEXT + /// ([ref](https://developer.apple.com/library/content/documentation/Darwin/Conceptual/NKEConceptual/control/control.html)) + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + KextControl = libc::SYSPROTO_CONTROL, + /// Receives routing and link updates and may be used to modify the routing tables (both IPv4 and IPv6), IP addresses, link + // parameters, neighbor setups, queueing disciplines, traffic classes and packet classifiers + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkRoute = libc::NETLINK_ROUTE, + /// Reserved for user-mode socket protocols + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkUserSock = libc::NETLINK_USERSOCK, + /// Query information about sockets of various protocol families from the kernel + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkSockDiag = libc::NETLINK_SOCK_DIAG, + /// SELinux event notifications. + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkSELinux = libc::NETLINK_SELINUX, + /// Open-iSCSI + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkISCSI = libc::NETLINK_ISCSI, + /// Auditing + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkAudit = libc::NETLINK_AUDIT, + /// Access to FIB lookup from user space + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkFIBLookup = libc::NETLINK_FIB_LOOKUP, + /// Netfilter subsystem + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkNetFilter = libc::NETLINK_NETFILTER, + /// SCSI Transports + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkSCSITransport = libc::NETLINK_SCSITRANSPORT, + /// Infiniband RDMA + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkRDMA = libc::NETLINK_RDMA, + /// Transport IPv6 packets from netfilter to user space. Used by ip6_queue kernel module. + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkIPv6Firewall = libc::NETLINK_IP6_FW, + /// DECnet routing messages + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkDECNetRoutingMessage = libc::NETLINK_DNRTMSG, + /// Kernel messages to user space + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkKObjectUEvent = libc::NETLINK_KOBJECT_UEVENT, + /// Netlink interface to request information about ciphers registered with the kernel crypto API as well as allow + /// configuration of the kernel crypto API. + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkCrypto = libc::NETLINK_CRYPTO, + /// Non-DIX type protocol number defined for the Ethernet IEEE 802.3 interface that allows packets of all protocols + /// defined in the interface to be received. + /// ([ref](https://man7.org/linux/man-pages/man7/packet.7.html)) + // The protocol number is fed into the socket syscall in network byte order. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + EthAll = libc::ETH_P_ALL.to_be(), +} + +#[cfg(any(target_os = "linux"))] +libc_bitflags! { + /// Configuration flags for `SO_TIMESTAMPING` interface + /// + /// For use with [`Timestamping`][sockopt::Timestamping]. + /// [Further reading](https://www.kernel.org/doc/html/latest/networking/timestamping.html) + pub struct TimestampingFlag: c_uint { + /// Report any software timestamps when available. + SOF_TIMESTAMPING_SOFTWARE; + /// Report hardware timestamps as generated by SOF_TIMESTAMPING_TX_HARDWARE when available. + SOF_TIMESTAMPING_RAW_HARDWARE; + /// Collect transmiting timestamps as reported by hardware + SOF_TIMESTAMPING_TX_HARDWARE; + /// Collect transmiting timestamps as reported by software + SOF_TIMESTAMPING_TX_SOFTWARE; + /// Collect receiving timestamps as reported by hardware + SOF_TIMESTAMPING_RX_HARDWARE; + /// Collect receiving timestamps as reported by software + SOF_TIMESTAMPING_RX_SOFTWARE; + } +} + +libc_bitflags! { + /// Additional socket options + pub struct SockFlag: c_int { + /// Set non-blocking mode on the new socket + #[cfg(any(target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + SOCK_NONBLOCK; + /// Set close-on-exec on the new descriptor + #[cfg(any(target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + SOCK_CLOEXEC; + /// Return `EPIPE` instead of raising `SIGPIPE` + #[cfg(target_os = "netbsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + SOCK_NOSIGPIPE; + /// For domains `AF_INET(6)`, only allow `connect(2)`, `sendto(2)`, or `sendmsg(2)` + /// to the DNS port (typically 53) + #[cfg(target_os = "openbsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + SOCK_DNS; + } +} + +libc_bitflags! { + /// Flags for send/recv and their relatives + pub struct MsgFlags: c_int { + /// Sends or requests out-of-band data on sockets that support this notion + /// (e.g., of type [`Stream`](enum.SockType.html)); the underlying protocol must also + /// support out-of-band data. + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_OOB; + /// Peeks at an incoming message. The data is treated as unread and the next + /// [`recv()`](fn.recv.html) + /// or similar function shall still return this data. + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_PEEK; + /// Receive operation blocks until the full amount of data can be + /// returned. The function may return smaller amount of data if a signal + /// is caught, an error or disconnect occurs. + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_WAITALL; + /// Enables nonblocking operation; if the operation would block, + /// `EAGAIN` or `EWOULDBLOCK` is returned. This provides similar + /// behavior to setting the `O_NONBLOCK` flag + /// (via the [`fcntl`](../../fcntl/fn.fcntl.html) + /// `F_SETFL` operation), but differs in that `MSG_DONTWAIT` is a per- + /// call option, whereas `O_NONBLOCK` is a setting on the open file + /// description (see [open(2)](https://man7.org/linux/man-pages/man2/open.2.html)), + /// which will affect all threads in + /// the calling process and as well as other processes that hold + /// file descriptors referring to the same open file description. + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_DONTWAIT; + /// Receive flags: Control Data was discarded (buffer too small) + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_CTRUNC; + /// For raw ([`Packet`](addr/enum.AddressFamily.html)), Internet datagram + /// (since Linux 2.4.27/2.6.8), + /// netlink (since Linux 2.6.22) and UNIX datagram (since Linux 3.4) + /// sockets: return the real length of the packet or datagram, even + /// when it was longer than the passed buffer. Not implemented for UNIX + /// domain ([unix(7)](https://linux.die.net/man/7/unix)) sockets. + /// + /// For use with Internet stream sockets, see [tcp(7)](https://linux.die.net/man/7/tcp). + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_TRUNC; + /// Terminates a record (when this notion is supported, as for + /// sockets of type [`SeqPacket`](enum.SockType.html)). + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_EOR; + /// This flag specifies that queued errors should be received from + /// the socket error queue. (For more details, see + /// [recvfrom(2)](https://linux.die.net/man/2/recvfrom)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_ERRQUEUE; + /// Set the `close-on-exec` flag for the file descriptor received via a UNIX domain + /// file descriptor using the `SCM_RIGHTS` operation (described in + /// [unix(7)](https://linux.die.net/man/7/unix)). + /// This flag is useful for the same reasons as the `O_CLOEXEC` flag of + /// [open(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html). + /// + /// Only used in [`recvmsg`](fn.recvmsg.html) function. + #[cfg(any(target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_CMSG_CLOEXEC; + /// Requests not to send `SIGPIPE` errors when the other end breaks the connection. + /// (For more details, see [send(2)](https://linux.die.net/man/2/send)). + #[cfg(any(target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "haiku", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_NOSIGNAL; + } +} + +cfg_if! { + if #[cfg(any(target_os = "android", target_os = "linux"))] { + /// Unix credentials of the sending process. + /// + /// This struct is used with the `SO_PEERCRED` ancillary message + /// and the `SCM_CREDENTIALS` control message for UNIX sockets. + #[repr(transparent)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct UnixCredentials(libc::ucred); + + impl UnixCredentials { + /// Creates a new instance with the credentials of the current process + pub fn new() -> Self { + // Safe because these FFI functions are inherently safe + unsafe { + UnixCredentials(libc::ucred { + pid: libc::getpid(), + uid: libc::getuid(), + gid: libc::getgid() + }) + } + } + + /// Returns the process identifier + pub fn pid(&self) -> libc::pid_t { + self.0.pid + } + + /// Returns the user identifier + pub fn uid(&self) -> libc::uid_t { + self.0.uid + } + + /// Returns the group identifier + pub fn gid(&self) -> libc::gid_t { + self.0.gid + } + } + + impl Default for UnixCredentials { + fn default() -> Self { + Self::new() + } + } + + impl From<libc::ucred> for UnixCredentials { + fn from(cred: libc::ucred) -> Self { + UnixCredentials(cred) + } + } + + impl From<UnixCredentials> for libc::ucred { + fn from(uc: UnixCredentials) -> Self { + uc.0 + } + } + } else if #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] { + /// Unix credentials of the sending process. + /// + /// This struct is used with the `SCM_CREDS` ancillary message for UNIX sockets. + #[repr(transparent)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct UnixCredentials(libc::cmsgcred); + + impl UnixCredentials { + /// Returns the process identifier + pub fn pid(&self) -> libc::pid_t { + self.0.cmcred_pid + } + + /// Returns the real user identifier + pub fn uid(&self) -> libc::uid_t { + self.0.cmcred_uid + } + + /// Returns the effective user identifier + pub fn euid(&self) -> libc::uid_t { + self.0.cmcred_euid + } + + /// Returns the real group identifier + pub fn gid(&self) -> libc::gid_t { + self.0.cmcred_gid + } + + /// Returns a list group identifiers (the first one being the effective GID) + pub fn groups(&self) -> &[libc::gid_t] { + unsafe { + slice::from_raw_parts( + self.0.cmcred_groups.as_ptr() as *const libc::gid_t, + self.0.cmcred_ngroups as _ + ) + } + } + } + + impl From<libc::cmsgcred> for UnixCredentials { + fn from(cred: libc::cmsgcred) -> Self { + UnixCredentials(cred) + } + } + } +} + +cfg_if! { + if #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "ios" + ))] { + /// Return type of [`LocalPeerCred`](crate::sys::socket::sockopt::LocalPeerCred) + #[repr(transparent)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct XuCred(libc::xucred); + + impl XuCred { + /// Structure layout version + pub fn version(&self) -> u32 { + self.0.cr_version + } + + /// Effective user ID + pub fn uid(&self) -> libc::uid_t { + self.0.cr_uid + } + + /// Returns a list of group identifiers (the first one being the + /// effective GID) + pub fn groups(&self) -> &[libc::gid_t] { + &self.0.cr_groups + } + } + } +} + +feature! { +#![feature = "net"] +/// Request for multicast socket operations +/// +/// This is a wrapper type around `ip_mreq`. +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct IpMembershipRequest(libc::ip_mreq); + +impl IpMembershipRequest { + /// Instantiate a new `IpMembershipRequest` + /// + /// If `interface` is `None`, then `Ipv4Addr::any()` will be used for the interface. + pub fn new(group: net::Ipv4Addr, interface: Option<net::Ipv4Addr>) + -> Self + { + let imr_addr = match interface { + None => net::Ipv4Addr::UNSPECIFIED, + Some(addr) => addr + }; + IpMembershipRequest(libc::ip_mreq { + imr_multiaddr: ipv4addr_to_libc(group), + imr_interface: ipv4addr_to_libc(imr_addr) + }) + } +} + +/// Request for ipv6 multicast socket operations +/// +/// This is a wrapper type around `ipv6_mreq`. +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Ipv6MembershipRequest(libc::ipv6_mreq); + +impl Ipv6MembershipRequest { + /// Instantiate a new `Ipv6MembershipRequest` + pub const fn new(group: net::Ipv6Addr) -> Self { + Ipv6MembershipRequest(libc::ipv6_mreq { + ipv6mr_multiaddr: ipv6addr_to_libc(&group), + ipv6mr_interface: 0, + }) + } +} +} + +feature! { +#![feature = "uio"] + +/// Create a buffer large enough for storing some control messages as returned +/// by [`recvmsg`](fn.recvmsg.html). +/// +/// # Examples +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// # use nix::sys::time::TimeVal; +/// # use std::os::unix::io::RawFd; +/// # fn main() { +/// // Create a buffer for a `ControlMessageOwned::ScmTimestamp` message +/// let _ = cmsg_space!(TimeVal); +/// // Create a buffer big enough for a `ControlMessageOwned::ScmRights` message +/// // with two file descriptors +/// let _ = cmsg_space!([RawFd; 2]); +/// // Create a buffer big enough for a `ControlMessageOwned::ScmRights` message +/// // and a `ControlMessageOwned::ScmTimestamp` message +/// let _ = cmsg_space!(RawFd, TimeVal); +/// # } +/// ``` +// Unfortunately, CMSG_SPACE isn't a const_fn, or else we could return a +// stack-allocated array. +#[macro_export] +macro_rules! cmsg_space { + ( $( $x:ty ),* ) => { + { + let mut space = 0; + $( + // CMSG_SPACE is always safe + space += unsafe { + $crate::sys::socket::CMSG_SPACE(::std::mem::size_of::<$x>() as $crate::sys::socket::c_uint) + } as usize; + )* + Vec::<u8>::with_capacity(space) + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +/// Contains outcome of sending or receiving a message +/// +/// Use [`cmsgs`][RecvMsg::cmsgs] to access all the control messages present, and +/// [`iovs`][RecvMsg::iovs`] to access underlying io slices. +pub struct RecvMsg<'a, 's, S> { + pub bytes: usize, + cmsghdr: Option<&'a cmsghdr>, + pub address: Option<S>, + pub flags: MsgFlags, + iobufs: std::marker::PhantomData<& 's()>, + mhdr: msghdr, +} + +impl<'a, S> RecvMsg<'a, '_, S> { + /// Iterate over the valid control messages pointed to by this + /// msghdr. + pub fn cmsgs(&self) -> CmsgIterator { + CmsgIterator { + cmsghdr: self.cmsghdr, + mhdr: &self.mhdr + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct CmsgIterator<'a> { + /// Control message buffer to decode from. Must adhere to cmsg alignment. + cmsghdr: Option<&'a cmsghdr>, + mhdr: &'a msghdr +} + +impl<'a> Iterator for CmsgIterator<'a> { + type Item = ControlMessageOwned; + + fn next(&mut self) -> Option<ControlMessageOwned> { + match self.cmsghdr { + None => None, // No more messages + Some(hdr) => { + // Get the data. + // Safe if cmsghdr points to valid data returned by recvmsg(2) + let cm = unsafe { Some(ControlMessageOwned::decode_from(hdr))}; + // Advance the internal pointer. Safe if mhdr and cmsghdr point + // to valid data returned by recvmsg(2) + self.cmsghdr = unsafe { + let p = CMSG_NXTHDR(self.mhdr as *const _, hdr as *const _); + p.as_ref() + }; + cm + } + } + } +} + +/// A type-safe wrapper around a single control message, as used with +/// [`recvmsg`](#fn.recvmsg). +/// +/// [Further reading](https://man7.org/linux/man-pages/man3/cmsg.3.html) +// Nix version 0.13.0 and earlier used ControlMessage for both recvmsg and +// sendmsg. However, on some platforms the messages returned by recvmsg may be +// unaligned. ControlMessageOwned takes those messages by copy, obviating any +// alignment issues. +// +// See https://github.com/nix-rust/nix/issues/999 +#[derive(Clone, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum ControlMessageOwned { + /// Received version of [`ControlMessage::ScmRights`] + ScmRights(Vec<RawFd>), + /// Received version of [`ControlMessage::ScmCredentials`] + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ScmCredentials(UnixCredentials), + /// Received version of [`ControlMessage::ScmCreds`] + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ScmCreds(UnixCredentials), + /// A message of type `SCM_TIMESTAMP`, containing the time the + /// packet was received by the kernel. + /// + /// See the kernel's explanation in "SO_TIMESTAMP" of + /// [networking/timestamping](https://www.kernel.org/doc/Documentation/networking/timestamping.txt). + /// + /// # Examples + /// + /// ``` + /// # #[macro_use] extern crate nix; + /// # use nix::sys::socket::*; + /// # use nix::sys::time::*; + /// # use std::io::{IoSlice, IoSliceMut}; + /// # use std::time::*; + /// # use std::str::FromStr; + /// # fn main() { + /// // Set up + /// let message = "Ohayō!".as_bytes(); + /// let in_socket = socket( + /// AddressFamily::Inet, + /// SockType::Datagram, + /// SockFlag::empty(), + /// None).unwrap(); + /// setsockopt(in_socket, sockopt::ReceiveTimestamp, &true).unwrap(); + /// let localhost = SockaddrIn::from_str("127.0.0.1:0").unwrap(); + /// bind(in_socket, &localhost).unwrap(); + /// let address: SockaddrIn = getsockname(in_socket).unwrap(); + /// // Get initial time + /// let time0 = SystemTime::now(); + /// // Send the message + /// let iov = [IoSlice::new(message)]; + /// let flags = MsgFlags::empty(); + /// let l = sendmsg(in_socket, &iov, &[], flags, Some(&address)).unwrap(); + /// assert_eq!(message.len(), l); + /// // Receive the message + /// let mut buffer = vec![0u8; message.len()]; + /// let mut cmsgspace = cmsg_space!(TimeVal); + /// let mut iov = [IoSliceMut::new(&mut buffer)]; + /// let r = recvmsg::<SockaddrIn>(in_socket, &mut iov, Some(&mut cmsgspace), flags) + /// .unwrap(); + /// let rtime = match r.cmsgs().next() { + /// Some(ControlMessageOwned::ScmTimestamp(rtime)) => rtime, + /// Some(_) => panic!("Unexpected control message"), + /// None => panic!("No control message") + /// }; + /// // Check the final time + /// let time1 = SystemTime::now(); + /// // the packet's received timestamp should lie in-between the two system + /// // times, unless the system clock was adjusted in the meantime. + /// let rduration = Duration::new(rtime.tv_sec() as u64, + /// rtime.tv_usec() as u32 * 1000); + /// assert!(time0.duration_since(UNIX_EPOCH).unwrap() <= rduration); + /// assert!(rduration <= time1.duration_since(UNIX_EPOCH).unwrap()); + /// // Close socket + /// nix::unistd::close(in_socket).unwrap(); + /// # } + /// ``` + ScmTimestamp(TimeVal), + /// A set of nanosecond resolution timestamps + /// + /// [Further reading](https://www.kernel.org/doc/html/latest/networking/timestamping.html) + #[cfg(all(target_os = "linux"))] + ScmTimestampsns(Timestamps), + /// Nanoseconds resolution timestamp + /// + /// [Further reading](https://www.kernel.org/doc/html/latest/networking/timestamping.html) + #[cfg(all(target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ScmTimestampns(TimeSpec), + #[cfg(any( + target_os = "android", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + ))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4PacketInfo(libc::in_pktinfo), + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "openbsd", + target_os = "netbsd", + ))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv6PacketInfo(libc::in6_pktinfo), + #[cfg(any( + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + ))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4RecvIf(libc::sockaddr_dl), + #[cfg(any( + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + ))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4RecvDstAddr(libc::in_addr), + #[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4OrigDstAddr(libc::sockaddr_in), + #[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv6OrigDstAddr(libc::sockaddr_in6), + + /// UDP Generic Receive Offload (GRO) allows receiving multiple UDP + /// packets from a single sender. + /// Fixed-size payloads are following one by one in a receive buffer. + /// This Control Message indicates the size of all smaller packets, + /// except, maybe, the last one. + /// + /// `UdpGroSegment` socket option should be enabled on a socket + /// to allow receiving GRO packets. + #[cfg(target_os = "linux")] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + UdpGroSegments(u16), + + /// SO_RXQ_OVFL indicates that an unsigned 32 bit value + /// ancilliary msg (cmsg) should be attached to recieved + /// skbs indicating the number of packets dropped by the + /// socket between the last recieved packet and this + /// received packet. + /// + /// `RxqOvfl` socket option should be enabled on a socket + /// to allow receiving the drop counter. + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + RxqOvfl(u32), + + /// Socket error queue control messages read with the `MSG_ERRQUEUE` flag. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4RecvErr(libc::sock_extended_err, Option<sockaddr_in>), + /// Socket error queue control messages read with the `MSG_ERRQUEUE` flag. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv6RecvErr(libc::sock_extended_err, Option<sockaddr_in6>), + + /// Catch-all variant for unimplemented cmsg types. + #[doc(hidden)] + Unknown(UnknownCmsg), +} + +/// For representing packet timestamps via `SO_TIMESTAMPING` interface +#[cfg(all(target_os = "linux"))] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Timestamps { + /// software based timestamp, usually one containing data + pub system: TimeSpec, + /// legacy timestamp, usually empty + pub hw_trans: TimeSpec, + /// hardware based timestamp + pub hw_raw: TimeSpec, +} + +impl ControlMessageOwned { + /// Decodes a `ControlMessageOwned` from raw bytes. + /// + /// This is only safe to call if the data is correct for the message type + /// specified in the header. Normally, the kernel ensures that this is the + /// case. "Correct" in this case includes correct length, alignment and + /// actual content. + // Clippy complains about the pointer alignment of `p`, not understanding + // that it's being fed to a function that can handle that. + #[allow(clippy::cast_ptr_alignment)] + unsafe fn decode_from(header: &cmsghdr) -> ControlMessageOwned + { + let p = CMSG_DATA(header); + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + let len = header as *const _ as usize + header.cmsg_len as usize + - p as usize; + match (header.cmsg_level, header.cmsg_type) { + (libc::SOL_SOCKET, libc::SCM_RIGHTS) => { + let n = len / mem::size_of::<RawFd>(); + let mut fds = Vec::with_capacity(n); + for i in 0..n { + let fdp = (p as *const RawFd).add(i); + fds.push(ptr::read_unaligned(fdp)); + } + ControlMessageOwned::ScmRights(fds) + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + (libc::SOL_SOCKET, libc::SCM_CREDENTIALS) => { + let cred: libc::ucred = ptr::read_unaligned(p as *const _); + ControlMessageOwned::ScmCredentials(cred.into()) + } + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + (libc::SOL_SOCKET, libc::SCM_CREDS) => { + let cred: libc::cmsgcred = ptr::read_unaligned(p as *const _); + ControlMessageOwned::ScmCreds(cred.into()) + } + #[cfg(not(target_os = "haiku"))] + (libc::SOL_SOCKET, libc::SCM_TIMESTAMP) => { + let tv: libc::timeval = ptr::read_unaligned(p as *const _); + ControlMessageOwned::ScmTimestamp(TimeVal::from(tv)) + }, + #[cfg(all(target_os = "linux"))] + (libc::SOL_SOCKET, libc::SCM_TIMESTAMPNS) => { + let ts: libc::timespec = ptr::read_unaligned(p as *const _); + ControlMessageOwned::ScmTimestampns(TimeSpec::from(ts)) + } + #[cfg(all(target_os = "linux"))] + (libc::SOL_SOCKET, libc::SCM_TIMESTAMPING) => { + let tp = p as *const libc::timespec; + let ts: libc::timespec = ptr::read_unaligned(tp); + let system = TimeSpec::from(ts); + let ts: libc::timespec = ptr::read_unaligned(tp.add(1)); + let hw_trans = TimeSpec::from(ts); + let ts: libc::timespec = ptr::read_unaligned(tp.add(2)); + let hw_raw = TimeSpec::from(ts); + let timestamping = Timestamps { system, hw_trans, hw_raw }; + ControlMessageOwned::ScmTimestampsns(timestamping) + } + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos" + ))] + #[cfg(feature = "net")] + (libc::IPPROTO_IPV6, libc::IPV6_PKTINFO) => { + let info = ptr::read_unaligned(p as *const libc::in6_pktinfo); + ControlMessageOwned::Ipv6PacketInfo(info) + } + #[cfg(any( + target_os = "android", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + ))] + #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_PKTINFO) => { + let info = ptr::read_unaligned(p as *const libc::in_pktinfo); + ControlMessageOwned::Ipv4PacketInfo(info) + } + #[cfg(any( + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + ))] + #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_RECVIF) => { + let dl = ptr::read_unaligned(p as *const libc::sockaddr_dl); + ControlMessageOwned::Ipv4RecvIf(dl) + }, + #[cfg(any( + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + ))] + #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_RECVDSTADDR) => { + let dl = ptr::read_unaligned(p as *const libc::in_addr); + ControlMessageOwned::Ipv4RecvDstAddr(dl) + }, + #[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] + #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_ORIGDSTADDR) => { + let dl = ptr::read_unaligned(p as *const libc::sockaddr_in); + ControlMessageOwned::Ipv4OrigDstAddr(dl) + }, + #[cfg(target_os = "linux")] + #[cfg(feature = "net")] + (libc::SOL_UDP, libc::UDP_GRO) => { + let gso_size: u16 = ptr::read_unaligned(p as *const _); + ControlMessageOwned::UdpGroSegments(gso_size) + }, + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + (libc::SOL_SOCKET, libc::SO_RXQ_OVFL) => { + let drop_counter = ptr::read_unaligned(p as *const u32); + ControlMessageOwned::RxqOvfl(drop_counter) + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_RECVERR) => { + let (err, addr) = Self::recv_err_helper::<sockaddr_in>(p, len); + ControlMessageOwned::Ipv4RecvErr(err, addr) + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(feature = "net")] + (libc::IPPROTO_IPV6, libc::IPV6_RECVERR) => { + let (err, addr) = Self::recv_err_helper::<sockaddr_in6>(p, len); + ControlMessageOwned::Ipv6RecvErr(err, addr) + }, + #[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] + #[cfg(feature = "net")] + (libc::IPPROTO_IPV6, libc::IPV6_ORIGDSTADDR) => { + let dl = ptr::read_unaligned(p as *const libc::sockaddr_in6); + ControlMessageOwned::Ipv6OrigDstAddr(dl) + }, + (_, _) => { + let sl = slice::from_raw_parts(p, len); + let ucmsg = UnknownCmsg(*header, Vec::<u8>::from(sl)); + ControlMessageOwned::Unknown(ucmsg) + } + } + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(feature = "net")] + #[allow(clippy::cast_ptr_alignment)] // False positive + unsafe fn recv_err_helper<T>(p: *mut libc::c_uchar, len: usize) -> (libc::sock_extended_err, Option<T>) { + let ee = p as *const libc::sock_extended_err; + let err = ptr::read_unaligned(ee); + + // For errors originating on the network, SO_EE_OFFENDER(ee) points inside the p[..len] + // CMSG_DATA buffer. For local errors, there is no address included in the control + // message, and SO_EE_OFFENDER(ee) points beyond the end of the buffer. So, we need to + // validate that the address object is in-bounds before we attempt to copy it. + let addrp = libc::SO_EE_OFFENDER(ee) as *const T; + + if addrp.offset(1) as usize - (p as usize) > len { + (err, None) + } else { + (err, Some(ptr::read_unaligned(addrp))) + } + } +} + +/// A type-safe zero-copy wrapper around a single control message, as used wih +/// [`sendmsg`](#fn.sendmsg). More types may be added to this enum; do not +/// exhaustively pattern-match it. +/// +/// [Further reading](https://man7.org/linux/man-pages/man3/cmsg.3.html) +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum ControlMessage<'a> { + /// A message of type `SCM_RIGHTS`, containing an array of file + /// descriptors passed between processes. + /// + /// See the description in the "Ancillary messages" section of the + /// [unix(7) man page](https://man7.org/linux/man-pages/man7/unix.7.html). + /// + /// Using multiple `ScmRights` messages for a single `sendmsg` call isn't + /// recommended since it causes platform-dependent behaviour: It might + /// swallow all but the first `ScmRights` message or fail with `EINVAL`. + /// Instead, you can put all fds to be passed into a single `ScmRights` + /// message. + ScmRights(&'a [RawFd]), + /// A message of type `SCM_CREDENTIALS`, containing the pid, uid and gid of + /// a process connected to the socket. + /// + /// This is similar to the socket option `SO_PEERCRED`, but requires a + /// process to explicitly send its credentials. A process running as root is + /// allowed to specify any credentials, while credentials sent by other + /// processes are verified by the kernel. + /// + /// For further information, please refer to the + /// [`unix(7)`](https://man7.org/linux/man-pages/man7/unix.7.html) man page. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ScmCredentials(&'a UnixCredentials), + /// A message of type `SCM_CREDS`, containing the pid, uid, euid, gid and groups of + /// a process connected to the socket. + /// + /// This is similar to the socket options `LOCAL_CREDS` and `LOCAL_PEERCRED`, but + /// requires a process to explicitly send its credentials. + /// + /// Credentials are always overwritten by the kernel, so this variant does have + /// any data, unlike the receive-side + /// [`ControlMessageOwned::ScmCreds`]. + /// + /// For further information, please refer to the + /// [`unix(4)`](https://www.freebsd.org/cgi/man.cgi?query=unix) man page. + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ScmCreds, + + /// Set IV for `AF_ALG` crypto API. + /// + /// For further information, please refer to the + /// [`documentation`](https://kernel.readthedocs.io/en/sphinx-samples/crypto-API.html) + #[cfg(any( + target_os = "android", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + AlgSetIv(&'a [u8]), + /// Set crypto operation for `AF_ALG` crypto API. It may be one of + /// `ALG_OP_ENCRYPT` or `ALG_OP_DECRYPT` + /// + /// For further information, please refer to the + /// [`documentation`](https://kernel.readthedocs.io/en/sphinx-samples/crypto-API.html) + #[cfg(any( + target_os = "android", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + AlgSetOp(&'a libc::c_int), + /// Set the length of associated authentication data (AAD) (applicable only to AEAD algorithms) + /// for `AF_ALG` crypto API. + /// + /// For further information, please refer to the + /// [`documentation`](https://kernel.readthedocs.io/en/sphinx-samples/crypto-API.html) + #[cfg(any( + target_os = "android", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + AlgSetAeadAssoclen(&'a u32), + + /// UDP GSO makes it possible for applications to generate network packets + /// for a virtual MTU much greater than the real one. + /// The length of the send data no longer matches the expected length on + /// the wire. + /// The size of the datagram payload as it should appear on the wire may be + /// passed through this control message. + /// Send buffer should consist of multiple fixed-size wire payloads + /// following one by one, and the last, possibly smaller one. + #[cfg(target_os = "linux")] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + UdpGsoSegments(&'a u16), + + /// Configure the sending addressing and interface for v4 + /// + /// For further information, please refer to the + /// [`ip(7)`](https://man7.org/linux/man-pages/man7/ip.7.html) man page. + #[cfg(any(target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "android", + target_os = "ios",))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4PacketInfo(&'a libc::in_pktinfo), + + /// Configure the sending addressing and interface for v6 + /// + /// For further information, please refer to the + /// [`ipv6(7)`](https://man7.org/linux/man-pages/man7/ipv6.7.html) man page. + #[cfg(any(target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "freebsd", + target_os = "android", + target_os = "ios",))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv6PacketInfo(&'a libc::in6_pktinfo), + + /// Configure the IPv4 source address with `IP_SENDSRCADDR`. + #[cfg(any( + target_os = "netbsd", + target_os = "freebsd", + target_os = "openbsd", + target_os = "dragonfly", + ))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4SendSrcAddr(&'a libc::in_addr), + + /// SO_RXQ_OVFL indicates that an unsigned 32 bit value + /// ancilliary msg (cmsg) should be attached to recieved + /// skbs indicating the number of packets dropped by the + /// socket between the last recieved packet and this + /// received packet. + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + RxqOvfl(&'a u32), + + /// Configure the transmission time of packets. + /// + /// For further information, please refer to the + /// [`tc-etf(8)`](https://man7.org/linux/man-pages/man8/tc-etf.8.html) man + /// page. + #[cfg(target_os = "linux")] + TxTime(&'a u64), +} + +// An opaque structure used to prevent cmsghdr from being a public type +#[doc(hidden)] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct UnknownCmsg(cmsghdr, Vec<u8>); + +impl<'a> ControlMessage<'a> { + /// The value of CMSG_SPACE on this message. + /// Safe because CMSG_SPACE is always safe + fn space(&self) -> usize { + unsafe{CMSG_SPACE(self.len() as libc::c_uint) as usize} + } + + /// The value of CMSG_LEN on this message. + /// Safe because CMSG_LEN is always safe + #[cfg(any(target_os = "android", + all(target_os = "linux", not(target_env = "musl"))))] + fn cmsg_len(&self) -> usize { + unsafe{CMSG_LEN(self.len() as libc::c_uint) as usize} + } + + #[cfg(not(any(target_os = "android", + all(target_os = "linux", not(target_env = "musl")))))] + fn cmsg_len(&self) -> libc::c_uint { + unsafe{CMSG_LEN(self.len() as libc::c_uint)} + } + + /// Return a reference to the payload data as a byte pointer + fn copy_to_cmsg_data(&self, cmsg_data: *mut u8) { + let data_ptr = match *self { + ControlMessage::ScmRights(fds) => { + fds as *const _ as *const u8 + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::ScmCredentials(creds) => { + &creds.0 as *const libc::ucred as *const u8 + } + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + ControlMessage::ScmCreds => { + // The kernel overwrites the data, we just zero it + // to make sure it's not uninitialized memory + unsafe { ptr::write_bytes(cmsg_data, 0, self.len()) }; + return + } + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetIv(iv) => { + #[allow(deprecated)] // https://github.com/rust-lang/libc/issues/1501 + let af_alg_iv = libc::af_alg_iv { + ivlen: iv.len() as u32, + iv: [0u8; 0], + }; + + let size = mem::size_of_val(&af_alg_iv); + + unsafe { + ptr::copy_nonoverlapping( + &af_alg_iv as *const _ as *const u8, + cmsg_data, + size, + ); + ptr::copy_nonoverlapping( + iv.as_ptr(), + cmsg_data.add(size), + iv.len() + ); + }; + + return + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetOp(op) => { + op as *const _ as *const u8 + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetAeadAssoclen(len) => { + len as *const _ as *const u8 + }, + #[cfg(target_os = "linux")] + #[cfg(feature = "net")] + ControlMessage::UdpGsoSegments(gso_size) => { + gso_size as *const _ as *const u8 + }, + #[cfg(any(target_os = "linux", target_os = "macos", + target_os = "netbsd", target_os = "android", + target_os = "ios",))] + #[cfg(feature = "net")] + ControlMessage::Ipv4PacketInfo(info) => info as *const _ as *const u8, + #[cfg(any(target_os = "linux", target_os = "macos", + target_os = "netbsd", target_os = "freebsd", + target_os = "android", target_os = "ios",))] + #[cfg(feature = "net")] + ControlMessage::Ipv6PacketInfo(info) => info as *const _ as *const u8, + #[cfg(any(target_os = "netbsd", target_os = "freebsd", + target_os = "openbsd", target_os = "dragonfly"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4SendSrcAddr(addr) => addr as *const _ as *const u8, + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + ControlMessage::RxqOvfl(drop_count) => { + drop_count as *const _ as *const u8 + }, + #[cfg(target_os = "linux")] + ControlMessage::TxTime(tx_time) => { + tx_time as *const _ as *const u8 + }, + }; + unsafe { + ptr::copy_nonoverlapping( + data_ptr, + cmsg_data, + self.len() + ) + }; + } + + /// The size of the payload, excluding its cmsghdr + fn len(&self) -> usize { + match *self { + ControlMessage::ScmRights(fds) => { + mem::size_of_val(fds) + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::ScmCredentials(creds) => { + mem::size_of_val(creds) + } + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + ControlMessage::ScmCreds => { + mem::size_of::<libc::cmsgcred>() + } + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetIv(iv) => { + mem::size_of::<&[u8]>() + iv.len() + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetOp(op) => { + mem::size_of_val(op) + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetAeadAssoclen(len) => { + mem::size_of_val(len) + }, + #[cfg(target_os = "linux")] + #[cfg(feature = "net")] + ControlMessage::UdpGsoSegments(gso_size) => { + mem::size_of_val(gso_size) + }, + #[cfg(any(target_os = "linux", target_os = "macos", + target_os = "netbsd", target_os = "android", + target_os = "ios",))] + #[cfg(feature = "net")] + ControlMessage::Ipv4PacketInfo(info) => mem::size_of_val(info), + #[cfg(any(target_os = "linux", target_os = "macos", + target_os = "netbsd", target_os = "freebsd", + target_os = "android", target_os = "ios",))] + #[cfg(feature = "net")] + ControlMessage::Ipv6PacketInfo(info) => mem::size_of_val(info), + #[cfg(any(target_os = "netbsd", target_os = "freebsd", + target_os = "openbsd", target_os = "dragonfly"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4SendSrcAddr(addr) => mem::size_of_val(addr), + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + ControlMessage::RxqOvfl(drop_count) => { + mem::size_of_val(drop_count) + }, + #[cfg(target_os = "linux")] + ControlMessage::TxTime(tx_time) => { + mem::size_of_val(tx_time) + }, + } + } + + /// Returns the value to put into the `cmsg_level` field of the header. + fn cmsg_level(&self) -> libc::c_int { + match *self { + ControlMessage::ScmRights(_) => libc::SOL_SOCKET, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::ScmCredentials(_) => libc::SOL_SOCKET, + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + ControlMessage::ScmCreds => libc::SOL_SOCKET, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetIv(_) | ControlMessage::AlgSetOp(_) | + ControlMessage::AlgSetAeadAssoclen(_) => libc::SOL_ALG, + #[cfg(target_os = "linux")] + #[cfg(feature = "net")] + ControlMessage::UdpGsoSegments(_) => libc::SOL_UDP, + #[cfg(any(target_os = "linux", target_os = "macos", + target_os = "netbsd", target_os = "android", + target_os = "ios",))] + #[cfg(feature = "net")] + ControlMessage::Ipv4PacketInfo(_) => libc::IPPROTO_IP, + #[cfg(any(target_os = "linux", target_os = "macos", + target_os = "netbsd", target_os = "freebsd", + target_os = "android", target_os = "ios",))] + #[cfg(feature = "net")] + ControlMessage::Ipv6PacketInfo(_) => libc::IPPROTO_IPV6, + #[cfg(any(target_os = "netbsd", target_os = "freebsd", + target_os = "openbsd", target_os = "dragonfly"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4SendSrcAddr(_) => libc::IPPROTO_IP, + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + ControlMessage::RxqOvfl(_) => libc::SOL_SOCKET, + #[cfg(target_os = "linux")] + ControlMessage::TxTime(_) => libc::SOL_SOCKET, + } + } + + /// Returns the value to put into the `cmsg_type` field of the header. + fn cmsg_type(&self) -> libc::c_int { + match *self { + ControlMessage::ScmRights(_) => libc::SCM_RIGHTS, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::ScmCredentials(_) => libc::SCM_CREDENTIALS, + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + ControlMessage::ScmCreds => libc::SCM_CREDS, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetIv(_) => { + libc::ALG_SET_IV + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetOp(_) => { + libc::ALG_SET_OP + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetAeadAssoclen(_) => { + libc::ALG_SET_AEAD_ASSOCLEN + }, + #[cfg(target_os = "linux")] + #[cfg(feature = "net")] + ControlMessage::UdpGsoSegments(_) => { + libc::UDP_SEGMENT + }, + #[cfg(any(target_os = "linux", target_os = "macos", + target_os = "netbsd", target_os = "android", + target_os = "ios",))] + #[cfg(feature = "net")] + ControlMessage::Ipv4PacketInfo(_) => libc::IP_PKTINFO, + #[cfg(any(target_os = "linux", target_os = "macos", + target_os = "netbsd", target_os = "freebsd", + target_os = "android", target_os = "ios",))] + #[cfg(feature = "net")] + ControlMessage::Ipv6PacketInfo(_) => libc::IPV6_PKTINFO, + #[cfg(any(target_os = "netbsd", target_os = "freebsd", + target_os = "openbsd", target_os = "dragonfly"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4SendSrcAddr(_) => libc::IP_SENDSRCADDR, + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + ControlMessage::RxqOvfl(_) => { + libc::SO_RXQ_OVFL + }, + #[cfg(target_os = "linux")] + ControlMessage::TxTime(_) => { + libc::SCM_TXTIME + }, + } + } + + // Unsafe: cmsg must point to a valid cmsghdr with enough space to + // encode self. + unsafe fn encode_into(&self, cmsg: *mut cmsghdr) { + (*cmsg).cmsg_level = self.cmsg_level(); + (*cmsg).cmsg_type = self.cmsg_type(); + (*cmsg).cmsg_len = self.cmsg_len(); + self.copy_to_cmsg_data(CMSG_DATA(cmsg)); + } +} + + +/// Send data in scatter-gather vectors to a socket, possibly accompanied +/// by ancillary data. Optionally direct the message at the given address, +/// as with sendto. +/// +/// Allocates if cmsgs is nonempty. +/// +/// # Examples +/// When not directing to any specific address, use `()` for the generic type +/// ``` +/// # use nix::sys::socket::*; +/// # use nix::unistd::pipe; +/// # use std::io::IoSlice; +/// let (fd1, fd2) = socketpair(AddressFamily::Unix, SockType::Stream, None, +/// SockFlag::empty()) +/// .unwrap(); +/// let (r, w) = pipe().unwrap(); +/// +/// let iov = [IoSlice::new(b"hello")]; +/// let fds = [r]; +/// let cmsg = ControlMessage::ScmRights(&fds); +/// sendmsg::<()>(fd1, &iov, &[cmsg], MsgFlags::empty(), None).unwrap(); +/// ``` +/// When directing to a specific address, the generic type will be inferred. +/// ``` +/// # use nix::sys::socket::*; +/// # use nix::unistd::pipe; +/// # use std::io::IoSlice; +/// # use std::str::FromStr; +/// let localhost = SockaddrIn::from_str("1.2.3.4:8080").unwrap(); +/// let fd = socket(AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), +/// None).unwrap(); +/// let (r, w) = pipe().unwrap(); +/// +/// let iov = [IoSlice::new(b"hello")]; +/// let fds = [r]; +/// let cmsg = ControlMessage::ScmRights(&fds); +/// sendmsg(fd, &iov, &[cmsg], MsgFlags::empty(), Some(&localhost)).unwrap(); +/// ``` +pub fn sendmsg<S>(fd: RawFd, iov: &[IoSlice<'_>], cmsgs: &[ControlMessage], + flags: MsgFlags, addr: Option<&S>) -> Result<usize> + where S: SockaddrLike +{ + let capacity = cmsgs.iter().map(|c| c.space()).sum(); + + // First size the buffer needed to hold the cmsgs. It must be zeroed, + // because subsequent code will not clear the padding bytes. + let mut cmsg_buffer = vec![0u8; capacity]; + + let mhdr = pack_mhdr_to_send(&mut cmsg_buffer[..], iov, cmsgs, addr); + + let ret = unsafe { libc::sendmsg(fd, &mhdr, flags.bits()) }; + + Errno::result(ret).map(|r| r as usize) +} + + +/// An extension of `sendmsg` that allows the caller to transmit multiple +/// messages on a socket using a single system call. This has performance +/// benefits for some applications. +/// +/// Allocations are performed for cmsgs and to build `msghdr` buffer +/// +/// # Arguments +/// +/// * `fd`: Socket file descriptor +/// * `data`: Struct that implements `IntoIterator` with `SendMmsgData` items +/// * `flags`: Optional flags passed directly to the operating system. +/// +/// # Returns +/// `Vec` with numbers of sent bytes on each sent message. +/// +/// # References +/// [`sendmsg`](fn.sendmsg.html) +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", +))] +pub fn sendmmsg<'a, XS, AS, C, I, S>( + fd: RawFd, + data: &'a mut MultiHeaders<S>, + slices: XS, + // one address per group of slices + addrs: AS, + // shared across all the messages + cmsgs: C, + flags: MsgFlags +) -> crate::Result<MultiResults<'a, S>> + where + XS: IntoIterator<Item = &'a I>, + AS: AsRef<[Option<S>]>, + I: AsRef<[IoSlice<'a>]> + 'a, + C: AsRef<[ControlMessage<'a>]> + 'a, + S: SockaddrLike + 'a +{ + + let mut count = 0; + + + for (i, ((slice, addr), mmsghdr)) in slices.into_iter().zip(addrs.as_ref()).zip(data.items.iter_mut() ).enumerate() { + let mut p = &mut mmsghdr.msg_hdr; + p.msg_iov = slice.as_ref().as_ptr() as *mut libc::iovec; + p.msg_iovlen = slice.as_ref().len() as _; + + p.msg_namelen = addr.as_ref().map_or(0, S::len); + p.msg_name = addr.as_ref().map_or(ptr::null(), S::as_ptr) as _; + + // Encode each cmsg. This must happen after initializing the header because + // CMSG_NEXT_HDR and friends read the msg_control and msg_controllen fields. + // CMSG_FIRSTHDR is always safe + let mut pmhdr: *mut cmsghdr = unsafe { CMSG_FIRSTHDR(p) }; + for cmsg in cmsgs.as_ref() { + assert_ne!(pmhdr, ptr::null_mut()); + // Safe because we know that pmhdr is valid, and we initialized it with + // sufficient space + unsafe { cmsg.encode_into(pmhdr) }; + // Safe because mhdr is valid + pmhdr = unsafe { CMSG_NXTHDR(p, pmhdr) }; + } + + count = i+1; + } + + let sent = Errno::result(unsafe { + libc::sendmmsg( + fd, + data.items.as_mut_ptr(), + count as _, + flags.bits() as _ + ) + })? as usize; + + Ok(MultiResults { + rmm: data, + current_index: 0, + received: sent + }) + +} + + +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", +))] +#[derive(Debug)] +/// Preallocated structures needed for [`recvmmsg`] and [`sendmmsg`] functions +pub struct MultiHeaders<S> { + // preallocated boxed slice of mmsghdr + items: Box<[libc::mmsghdr]>, + addresses: Box<[mem::MaybeUninit<S>]>, + // while we are not using it directly - this is used to store control messages + // and we retain pointers to them inside items array + #[allow(dead_code)] + cmsg_buffers: Option<Box<[u8]>>, + msg_controllen: usize, +} + +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", +))] +impl<S> MultiHeaders<S> { + /// Preallocate structure used by [`recvmmsg`] and [`sendmmsg`] takes number of headers to preallocate + /// + /// `cmsg_buffer` should be created with [`cmsg_space!`] if needed + pub fn preallocate(num_slices: usize, cmsg_buffer: Option<Vec<u8>>) -> Self + where + S: Copy + SockaddrLike, + { + // we will be storing pointers to addresses inside mhdr - convert it into boxed + // slice so it can'be changed later by pushing anything into self.addresses + let mut addresses = vec![std::mem::MaybeUninit::uninit(); num_slices].into_boxed_slice(); + + let msg_controllen = cmsg_buffer.as_ref().map_or(0, |v| v.capacity()); + + // we'll need a cmsg_buffer for each slice, we preallocate a vector and split + // it into "slices" parts + let cmsg_buffers = + cmsg_buffer.map(|v| vec![0u8; v.capacity() * num_slices].into_boxed_slice()); + + let items = addresses + .iter_mut() + .enumerate() + .map(|(ix, address)| { + let (ptr, cap) = match &cmsg_buffers { + Some(v) => ((&v[ix * msg_controllen] as *const u8), msg_controllen), + None => (std::ptr::null(), 0), + }; + let msg_hdr = unsafe { pack_mhdr_to_receive(std::ptr::null(), 0, ptr, cap, address.as_mut_ptr()) }; + libc::mmsghdr { + msg_hdr, + msg_len: 0, + } + }) + .collect::<Vec<_>>(); + + Self { + items: items.into_boxed_slice(), + addresses, + cmsg_buffers, + msg_controllen, + } + } +} + +/// An extension of recvmsg that allows the caller to receive multiple messages from a socket using a single system call. +/// +/// This has performance benefits for some applications. +/// +/// This method performs no allocations. +/// +/// Returns an iterator producing [`RecvMsg`], one per received messages. Each `RecvMsg` can produce +/// iterators over [`IoSlice`] with [`iovs`][RecvMsg::iovs`] and +/// `ControlMessageOwned` with [`cmsgs`][RecvMsg::cmsgs]. +/// +/// # Bugs (in underlying implementation, at least in Linux) +/// The timeout argument does not work as intended. The timeout is checked only after the receipt +/// of each datagram, so that if up to `vlen`-1 datagrams are received before the timeout expires, +/// but then no further datagrams are received, the call will block forever. +/// +/// If an error occurs after at least one message has been received, the call succeeds, and returns +/// the number of messages received. The error code is expected to be returned on a subsequent +/// call to recvmmsg(). In the current implementation, however, the error code can be +/// overwritten in the meantime by an unrelated network event on a socket, for example an +/// incoming ICMP packet. + +// On aarch64 linux using recvmmsg and trying to get hardware/kernel timestamps might not +// always produce the desired results - see https://github.com/nix-rust/nix/pull/1744 for more +// details + +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", +))] +pub fn recvmmsg<'a, XS, S, I>( + fd: RawFd, + data: &'a mut MultiHeaders<S>, + slices: XS, + flags: MsgFlags, + mut timeout: Option<crate::sys::time::TimeSpec>, +) -> crate::Result<MultiResults<'a, S>> +where + XS: IntoIterator<Item = &'a I>, + I: AsRef<[IoSliceMut<'a>]> + 'a, +{ + let mut count = 0; + for (i, (slice, mmsghdr)) in slices.into_iter().zip(data.items.iter_mut()).enumerate() { + let mut p = &mut mmsghdr.msg_hdr; + p.msg_iov = slice.as_ref().as_ptr() as *mut libc::iovec; + p.msg_iovlen = slice.as_ref().len() as _; + count = i + 1; + } + + let timeout_ptr = timeout + .as_mut() + .map_or_else(std::ptr::null_mut, |t| t as *mut _ as *mut libc::timespec); + + let received = Errno::result(unsafe { + libc::recvmmsg( + fd, + data.items.as_mut_ptr(), + count as _, + flags.bits() as _, + timeout_ptr, + ) + })? as usize; + + Ok(MultiResults { + rmm: data, + current_index: 0, + received, + }) +} + +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", +))] +#[derive(Debug)] +/// Iterator over results of [`recvmmsg`]/[`sendmmsg`] +/// +/// +pub struct MultiResults<'a, S> { + // preallocated structures + rmm: &'a MultiHeaders<S>, + current_index: usize, + received: usize, +} + +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", +))] +impl<'a, S> Iterator for MultiResults<'a, S> +where + S: Copy + SockaddrLike, +{ + type Item = RecvMsg<'a, 'a, S>; + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + fn next(&mut self) -> Option<Self::Item> { + if self.current_index >= self.received { + return None; + } + let mmsghdr = self.rmm.items[self.current_index]; + + // as long as we are not reading past the index writen by recvmmsg - address + // will be initialized + let address = unsafe { self.rmm.addresses[self.current_index].assume_init() }; + + self.current_index += 1; + Some(unsafe { + read_mhdr( + mmsghdr.msg_hdr, + mmsghdr.msg_len as isize, + self.rmm.msg_controllen, + address, + ) + }) + } +} + +impl<'a, S> RecvMsg<'_, 'a, S> { + /// Iterate over the filled io slices pointed by this msghdr + pub fn iovs(&self) -> IoSliceIterator<'a> { + IoSliceIterator { + index: 0, + remaining: self.bytes, + slices: unsafe { + // safe for as long as mgdr is properly initialized and references are valid. + // for multi messages API we initialize it with an empty + // slice and replace with a concrete buffer + // for single message API we hold a lifetime reference to ioslices + std::slice::from_raw_parts(self.mhdr.msg_iov as *const _, self.mhdr.msg_iovlen as _) + }, + } + } +} + +#[derive(Debug)] +pub struct IoSliceIterator<'a> { + index: usize, + remaining: usize, + slices: &'a [IoSlice<'a>], +} + +impl<'a> Iterator for IoSliceIterator<'a> { + type Item = &'a [u8]; + + fn next(&mut self) -> Option<Self::Item> { + if self.index >= self.slices.len() { + return None; + } + let slice = &self.slices[self.index][..self.remaining.min(self.slices[self.index].len())]; + self.remaining -= slice.len(); + self.index += 1; + if slice.is_empty() { + return None; + } + + Some(slice) + } +} + +// test contains both recvmmsg and timestaping which is linux only +// there are existing tests for recvmmsg only in tests/ +#[cfg(target_os = "linux")] +#[cfg(test)] +mod test { + use crate::sys::socket::{AddressFamily, ControlMessageOwned}; + use crate::*; + use std::str::FromStr; + + #[cfg_attr(qemu, ignore)] + #[test] + fn test_recvmm2() -> crate::Result<()> { + use crate::sys::socket::{ + sendmsg, setsockopt, socket, sockopt::Timestamping, MsgFlags, SockFlag, SockType, + SockaddrIn, TimestampingFlag, + }; + use std::io::{IoSlice, IoSliceMut}; + + let sock_addr = SockaddrIn::from_str("127.0.0.1:6790").unwrap(); + + let ssock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + )?; + + let rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::SOCK_NONBLOCK, + None, + )?; + + crate::sys::socket::bind(rsock, &sock_addr)?; + + setsockopt(rsock, Timestamping, &TimestampingFlag::all())?; + + let sbuf = (0..400).map(|i| i as u8).collect::<Vec<_>>(); + + let mut recv_buf = vec![0; 1024]; + + let mut recv_iovs = Vec::new(); + let mut pkt_iovs = Vec::new(); + + for (ix, chunk) in recv_buf.chunks_mut(256).enumerate() { + pkt_iovs.push(IoSliceMut::new(chunk)); + if ix % 2 == 1 { + recv_iovs.push(pkt_iovs); + pkt_iovs = Vec::new(); + } + } + drop(pkt_iovs); + + let flags = MsgFlags::empty(); + let iov1 = [IoSlice::new(&sbuf)]; + + let cmsg = cmsg_space!(crate::sys::socket::Timestamps); + sendmsg(ssock, &iov1, &[], flags, Some(&sock_addr)).unwrap(); + + let mut data = super::MultiHeaders::<()>::preallocate(recv_iovs.len(), Some(cmsg)); + + let t = sys::time::TimeSpec::from_duration(std::time::Duration::from_secs(10)); + + let recv = super::recvmmsg(rsock, &mut data, recv_iovs.iter(), flags, Some(t))?; + + for rmsg in recv { + #[cfg(not(any(qemu, target_arch = "aarch64")))] + let mut saw_time = false; + let mut recvd = 0; + for cmsg in rmsg.cmsgs() { + if let ControlMessageOwned::ScmTimestampsns(timestamps) = cmsg { + let ts = timestamps.system; + + let sys_time = + crate::time::clock_gettime(crate::time::ClockId::CLOCK_REALTIME)?; + let diff = if ts > sys_time { + ts - sys_time + } else { + sys_time - ts + }; + assert!(std::time::Duration::from(diff).as_secs() < 60); + #[cfg(not(any(qemu, target_arch = "aarch64")))] + { + saw_time = true; + } + } + } + + #[cfg(not(any(qemu, target_arch = "aarch64")))] + assert!(saw_time); + + for iov in rmsg.iovs() { + recvd += iov.len(); + } + assert_eq!(recvd, 400); + } + + Ok(()) + } +} +unsafe fn read_mhdr<'a, 'i, S>( + mhdr: msghdr, + r: isize, + msg_controllen: usize, + address: S, +) -> RecvMsg<'a, 'i, S> + where S: SockaddrLike +{ + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + let cmsghdr = { + if mhdr.msg_controllen > 0 { + debug_assert!(!mhdr.msg_control.is_null()); + debug_assert!(msg_controllen >= mhdr.msg_controllen as usize); + CMSG_FIRSTHDR(&mhdr as *const msghdr) + } else { + ptr::null() + }.as_ref() + }; + + RecvMsg { + bytes: r as usize, + cmsghdr, + address: Some(address), + flags: MsgFlags::from_bits_truncate(mhdr.msg_flags), + mhdr, + iobufs: std::marker::PhantomData, + } +} + +/// Pack pointers to various structures into into msghdr +/// +/// # Safety +/// `iov_buffer` and `iov_buffer_len` must point to a slice +/// of `IoSliceMut` and number of available elements or be a null pointer and 0 +/// +/// `cmsg_buffer` and `cmsg_capacity` must point to a byte buffer used +/// to store control headers later or be a null pointer and 0 if control +/// headers are not used +/// +/// Buffers must remain valid for the whole lifetime of msghdr +unsafe fn pack_mhdr_to_receive<S>( + iov_buffer: *const IoSliceMut, + iov_buffer_len: usize, + cmsg_buffer: *const u8, + cmsg_capacity: usize, + address: *mut S, +) -> msghdr + where + S: SockaddrLike +{ + // Musl's msghdr has private fields, so this is the only way to + // initialize it. + let mut mhdr = mem::MaybeUninit::<msghdr>::zeroed(); + let p = mhdr.as_mut_ptr(); + (*p).msg_name = (*address).as_mut_ptr() as *mut c_void; + (*p).msg_namelen = S::size(); + (*p).msg_iov = iov_buffer as *mut iovec; + (*p).msg_iovlen = iov_buffer_len as _; + (*p).msg_control = cmsg_buffer as *mut c_void; + (*p).msg_controllen = cmsg_capacity as _; + (*p).msg_flags = 0; + mhdr.assume_init() +} + +fn pack_mhdr_to_send<'a, I, C, S>( + cmsg_buffer: &mut [u8], + iov: I, + cmsgs: C, + addr: Option<&S> +) -> msghdr + where + I: AsRef<[IoSlice<'a>]>, + C: AsRef<[ControlMessage<'a>]>, + S: SockaddrLike + 'a +{ + let capacity = cmsg_buffer.len(); + + // The message header must be initialized before the individual cmsgs. + let cmsg_ptr = if capacity > 0 { + cmsg_buffer.as_ptr() as *mut c_void + } else { + ptr::null_mut() + }; + + let mhdr = unsafe { + // Musl's msghdr has private fields, so this is the only way to + // initialize it. + let mut mhdr = mem::MaybeUninit::<msghdr>::zeroed(); + let p = mhdr.as_mut_ptr(); + (*p).msg_name = addr.map(S::as_ptr).unwrap_or(ptr::null()) as *mut _; + (*p).msg_namelen = addr.map(S::len).unwrap_or(0); + // transmute iov into a mutable pointer. sendmsg doesn't really mutate + // the buffer, but the standard says that it takes a mutable pointer + (*p).msg_iov = iov.as_ref().as_ptr() as *mut _; + (*p).msg_iovlen = iov.as_ref().len() as _; + (*p).msg_control = cmsg_ptr; + (*p).msg_controllen = capacity as _; + (*p).msg_flags = 0; + mhdr.assume_init() + }; + + // Encode each cmsg. This must happen after initializing the header because + // CMSG_NEXT_HDR and friends read the msg_control and msg_controllen fields. + // CMSG_FIRSTHDR is always safe + let mut pmhdr: *mut cmsghdr = unsafe { CMSG_FIRSTHDR(&mhdr as *const msghdr) }; + for cmsg in cmsgs.as_ref() { + assert_ne!(pmhdr, ptr::null_mut()); + // Safe because we know that pmhdr is valid, and we initialized it with + // sufficient space + unsafe { cmsg.encode_into(pmhdr) }; + // Safe because mhdr is valid + pmhdr = unsafe { CMSG_NXTHDR(&mhdr as *const msghdr, pmhdr) }; + } + + mhdr +} + +/// Receive message in scatter-gather vectors from a socket, and +/// optionally receive ancillary data into the provided buffer. +/// If no ancillary data is desired, use () as the type parameter. +/// +/// # Arguments +/// +/// * `fd`: Socket file descriptor +/// * `iov`: Scatter-gather list of buffers to receive the message +/// * `cmsg_buffer`: Space to receive ancillary data. Should be created by +/// [`cmsg_space!`](../../macro.cmsg_space.html) +/// * `flags`: Optional flags passed directly to the operating system. +/// +/// # References +/// [recvmsg(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvmsg.html) +pub fn recvmsg<'a, 'outer, 'inner, S>(fd: RawFd, iov: &'outer mut [IoSliceMut<'inner>], + mut cmsg_buffer: Option<&'a mut Vec<u8>>, + flags: MsgFlags) -> Result<RecvMsg<'a, 'inner, S>> + where S: SockaddrLike + 'a, + 'inner: 'outer +{ + let mut address = mem::MaybeUninit::uninit(); + + let (msg_control, msg_controllen) = cmsg_buffer.as_mut() + .map(|v| (v.as_mut_ptr(), v.capacity())) + .unwrap_or((ptr::null_mut(), 0)); + let mut mhdr = unsafe { + pack_mhdr_to_receive(iov.as_ref().as_ptr(), iov.len(), msg_control, msg_controllen, address.as_mut_ptr()) + }; + + let ret = unsafe { libc::recvmsg(fd, &mut mhdr, flags.bits()) }; + + let r = Errno::result(ret)?; + + Ok(unsafe { read_mhdr(mhdr, r, msg_controllen, address.assume_init()) }) +} +} + +/// Create an endpoint for communication +/// +/// The `protocol` specifies a particular protocol to be used with the +/// socket. Normally only a single protocol exists to support a +/// particular socket type within a given protocol family, in which case +/// protocol can be specified as `None`. However, it is possible that many +/// protocols may exist, in which case a particular protocol must be +/// specified in this manner. +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html) +pub fn socket<T: Into<Option<SockProtocol>>>( + domain: AddressFamily, + ty: SockType, + flags: SockFlag, + protocol: T, +) -> Result<RawFd> { + let protocol = match protocol.into() { + None => 0, + Some(p) => p as c_int, + }; + + // SockFlags are usually embedded into `ty`, but we don't do that in `nix` because it's a + // little easier to understand by separating it out. So we have to merge these bitfields + // here. + let mut ty = ty as c_int; + ty |= flags.bits(); + + let res = unsafe { libc::socket(domain as c_int, ty, protocol) }; + + Errno::result(res) +} + +/// Create a pair of connected sockets +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/socketpair.html) +pub fn socketpair<T: Into<Option<SockProtocol>>>( + domain: AddressFamily, + ty: SockType, + protocol: T, + flags: SockFlag, +) -> Result<(RawFd, RawFd)> { + let protocol = match protocol.into() { + None => 0, + Some(p) => p as c_int, + }; + + // SockFlags are usually embedded into `ty`, but we don't do that in `nix` because it's a + // little easier to understand by separating it out. So we have to merge these bitfields + // here. + let mut ty = ty as c_int; + ty |= flags.bits(); + + let mut fds = [-1, -1]; + + let res = unsafe { + libc::socketpair(domain as c_int, ty, protocol, fds.as_mut_ptr()) + }; + Errno::result(res)?; + + Ok((fds[0], fds[1])) +} + +/// Listen for connections on a socket +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/listen.html) +pub fn listen(sockfd: RawFd, backlog: usize) -> Result<()> { + let res = unsafe { libc::listen(sockfd, backlog as c_int) }; + + Errno::result(res).map(drop) +} + +/// Bind a name to a socket +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html) +pub fn bind(fd: RawFd, addr: &dyn SockaddrLike) -> Result<()> { + let res = unsafe { libc::bind(fd, addr.as_ptr(), addr.len()) }; + + Errno::result(res).map(drop) +} + +/// Accept a connection on a socket +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html) +pub fn accept(sockfd: RawFd) -> Result<RawFd> { + let res = unsafe { libc::accept(sockfd, ptr::null_mut(), ptr::null_mut()) }; + + Errno::result(res) +} + +/// Accept a connection on a socket +/// +/// [Further reading](https://man7.org/linux/man-pages/man2/accept.2.html) +#[cfg(any( + all( + target_os = "android", + any( + target_arch = "aarch64", + target_arch = "x86", + target_arch = "x86_64" + ) + ), + target_os = "dragonfly", + target_os = "emscripten", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd" +))] +pub fn accept4(sockfd: RawFd, flags: SockFlag) -> Result<RawFd> { + let res = unsafe { + libc::accept4(sockfd, ptr::null_mut(), ptr::null_mut(), flags.bits()) + }; + + Errno::result(res) +} + +/// Initiate a connection on a socket +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html) +pub fn connect(fd: RawFd, addr: &dyn SockaddrLike) -> Result<()> { + let res = unsafe { libc::connect(fd, addr.as_ptr(), addr.len()) }; + + Errno::result(res).map(drop) +} + +/// Receive data from a connection-oriented socket. Returns the number of +/// bytes read +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/recv.html) +pub fn recv(sockfd: RawFd, buf: &mut [u8], flags: MsgFlags) -> Result<usize> { + unsafe { + let ret = libc::recv( + sockfd, + buf.as_ptr() as *mut c_void, + buf.len() as size_t, + flags.bits(), + ); + + Errno::result(ret).map(|r| r as usize) + } +} + +/// Receive data from a connectionless or connection-oriented socket. Returns +/// the number of bytes read and, for connectionless sockets, the socket +/// address of the sender. +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvfrom.html) +pub fn recvfrom<T: SockaddrLike>( + sockfd: RawFd, + buf: &mut [u8], +) -> Result<(usize, Option<T>)> { + unsafe { + let mut addr = mem::MaybeUninit::<T>::uninit(); + let mut len = mem::size_of_val(&addr) as socklen_t; + + let ret = Errno::result(libc::recvfrom( + sockfd, + buf.as_ptr() as *mut c_void, + buf.len() as size_t, + 0, + addr.as_mut_ptr() as *mut libc::sockaddr, + &mut len as *mut socklen_t, + ))? as usize; + + Ok(( + ret, + T::from_raw( + addr.assume_init().as_ptr() as *const libc::sockaddr, + Some(len), + ), + )) + } +} + +/// Send a message to a socket +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendto.html) +pub fn sendto( + fd: RawFd, + buf: &[u8], + addr: &dyn SockaddrLike, + flags: MsgFlags, +) -> Result<usize> { + let ret = unsafe { + libc::sendto( + fd, + buf.as_ptr() as *const c_void, + buf.len() as size_t, + flags.bits(), + addr.as_ptr(), + addr.len(), + ) + }; + + Errno::result(ret).map(|r| r as usize) +} + +/// Send data to a connection-oriented socket. Returns the number of bytes read +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/send.html) +pub fn send(fd: RawFd, buf: &[u8], flags: MsgFlags) -> Result<usize> { + let ret = unsafe { + libc::send( + fd, + buf.as_ptr() as *const c_void, + buf.len() as size_t, + flags.bits(), + ) + }; + + Errno::result(ret).map(|r| r as usize) +} + +/* + * + * ===== Socket Options ===== + * + */ + +/// Represents a socket option that can be retrieved. +pub trait GetSockOpt: Copy { + type Val; + + /// Look up the value of this socket option on the given socket. + fn get(&self, fd: RawFd) -> Result<Self::Val>; +} + +/// Represents a socket option that can be set. +pub trait SetSockOpt: Clone { + type Val; + + /// Set the value of this socket option on the given socket. + fn set(&self, fd: RawFd, val: &Self::Val) -> Result<()>; +} + +/// Get the current value for the requested socket option +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockopt.html) +pub fn getsockopt<O: GetSockOpt>(fd: RawFd, opt: O) -> Result<O::Val> { + opt.get(fd) +} + +/// Sets the value for the requested socket option +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/setsockopt.html) +/// +/// # Examples +/// +/// ``` +/// use nix::sys::socket::setsockopt; +/// use nix::sys::socket::sockopt::KeepAlive; +/// use std::net::TcpListener; +/// use std::os::unix::io::AsRawFd; +/// +/// let listener = TcpListener::bind("0.0.0.0:0").unwrap(); +/// let fd = listener.as_raw_fd(); +/// let res = setsockopt(fd, KeepAlive, &true); +/// assert!(res.is_ok()); +/// ``` +pub fn setsockopt<O: SetSockOpt>( + fd: RawFd, + opt: O, + val: &O::Val, +) -> Result<()> { + opt.set(fd, val) +} + +/// Get the address of the peer connected to the socket `fd`. +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html) +pub fn getpeername<T: SockaddrLike>(fd: RawFd) -> Result<T> { + unsafe { + let mut addr = mem::MaybeUninit::<T>::uninit(); + let mut len = T::size(); + + let ret = libc::getpeername( + fd, + addr.as_mut_ptr() as *mut libc::sockaddr, + &mut len, + ); + + Errno::result(ret)?; + + T::from_raw(addr.assume_init().as_ptr(), Some(len)).ok_or(Errno::EINVAL) + } +} + +/// Get the current address to which the socket `fd` is bound. +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html) +pub fn getsockname<T: SockaddrLike>(fd: RawFd) -> Result<T> { + unsafe { + let mut addr = mem::MaybeUninit::<T>::uninit(); + let mut len = T::size(); + + let ret = libc::getsockname( + fd, + addr.as_mut_ptr() as *mut libc::sockaddr, + &mut len, + ); + + Errno::result(ret)?; + + T::from_raw(addr.assume_init().as_ptr(), Some(len)).ok_or(Errno::EINVAL) + } +} + +/// Return the appropriate `SockAddr` type from a `sockaddr_storage` of a +/// certain size. +/// +/// In C this would usually be done by casting. The `len` argument +/// should be the number of bytes in the `sockaddr_storage` that are actually +/// allocated and valid. It must be at least as large as all the useful parts +/// of the structure. Note that in the case of a `sockaddr_un`, `len` need not +/// include the terminating null. +#[deprecated( + since = "0.24.0", + note = "use SockaddrLike or SockaddrStorage instead" +)] +#[allow(deprecated)] +pub fn sockaddr_storage_to_addr( + addr: &sockaddr_storage, + len: usize, +) -> Result<SockAddr> { + assert!(len <= mem::size_of::<sockaddr_storage>()); + if len < mem::size_of_val(&addr.ss_family) { + return Err(Errno::ENOTCONN); + } + + match c_int::from(addr.ss_family) { + #[cfg(feature = "net")] + libc::AF_INET => { + assert!(len >= mem::size_of::<sockaddr_in>()); + let sin = unsafe { + *(addr as *const sockaddr_storage as *const sockaddr_in) + }; + Ok(SockAddr::Inet(InetAddr::V4(sin))) + } + #[cfg(feature = "net")] + libc::AF_INET6 => { + assert!(len >= mem::size_of::<sockaddr_in6>()); + let sin6 = unsafe { *(addr as *const _ as *const sockaddr_in6) }; + Ok(SockAddr::Inet(InetAddr::V6(sin6))) + } + libc::AF_UNIX => unsafe { + let sun = *(addr as *const _ as *const sockaddr_un); + let sun_len = len.try_into().unwrap(); + Ok(SockAddr::Unix(UnixAddr::from_raw_parts(sun, sun_len))) + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(feature = "net")] + libc::AF_PACKET => { + use libc::sockaddr_ll; + // Don't assert anything about the size. + // Apparently the Linux kernel can return smaller sizes when + // the value in the last element of sockaddr_ll (`sll_addr`) is + // smaller than the declared size of that field + let sll = unsafe { *(addr as *const _ as *const sockaddr_ll) }; + Ok(SockAddr::Link(LinkAddr(sll))) + } + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_NETLINK => { + use libc::sockaddr_nl; + let snl = unsafe { *(addr as *const _ as *const sockaddr_nl) }; + Ok(SockAddr::Netlink(NetlinkAddr(snl))) + } + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_ALG => { + use libc::sockaddr_alg; + let salg = unsafe { *(addr as *const _ as *const sockaddr_alg) }; + Ok(SockAddr::Alg(AlgAddr(salg))) + } + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_VSOCK => { + use libc::sockaddr_vm; + let svm = unsafe { *(addr as *const _ as *const sockaddr_vm) }; + Ok(SockAddr::Vsock(VsockAddr(svm))) + } + af => panic!("unexpected address family {}", af), + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum Shutdown { + /// Further receptions will be disallowed. + Read, + /// Further transmissions will be disallowed. + Write, + /// Further receptions and transmissions will be disallowed. + Both, +} + +/// Shut down part of a full-duplex connection. +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/shutdown.html) +pub fn shutdown(df: RawFd, how: Shutdown) -> Result<()> { + unsafe { + use libc::shutdown; + + let how = match how { + Shutdown::Read => libc::SHUT_RD, + Shutdown::Write => libc::SHUT_WR, + Shutdown::Both => libc::SHUT_RDWR, + }; + + Errno::result(shutdown(df, how)).map(drop) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn can_use_cmsg_space() { + let _ = cmsg_space!(u8); + } +} diff --git a/third_party/rust/nix/src/sys/socket/sockopt.rs b/third_party/rust/nix/src/sys/socket/sockopt.rs new file mode 100644 index 0000000000..06e9ee4563 --- /dev/null +++ b/third_party/rust/nix/src/sys/socket/sockopt.rs @@ -0,0 +1,1422 @@ +//! Socket options as used by `setsockopt` and `getsockopt`. +use super::{GetSockOpt, SetSockOpt}; +use crate::errno::Errno; +use crate::sys::time::TimeVal; +use crate::Result; +use cfg_if::cfg_if; +use libc::{self, c_int, c_void, socklen_t}; +use std::ffi::{OsStr, OsString}; +use std::{ + convert::TryFrom, + mem::{self, MaybeUninit} +}; +#[cfg(target_family = "unix")] +use std::os::unix::ffi::OsStrExt; +use std::os::unix::io::RawFd; + +// Constants +// TCP_CA_NAME_MAX isn't defined in user space include files +#[cfg(any(target_os = "freebsd", target_os = "linux"))] +#[cfg(feature = "net")] +const TCP_CA_NAME_MAX: usize = 16; + +/// Helper for implementing `SetSockOpt` for a given socket option. See +/// [`::sys::socket::SetSockOpt`](sys/socket/trait.SetSockOpt.html). +/// +/// This macro aims to help implementing `SetSockOpt` for different socket options that accept +/// different kinds of data to be used with `setsockopt`. +/// +/// Instead of using this macro directly consider using `sockopt_impl!`, especially if the option +/// you are implementing represents a simple type. +/// +/// # Arguments +/// +/// * `$name:ident`: name of the type you want to implement `SetSockOpt` for. +/// * `$level:expr` : socket layer, or a `protocol level`: could be *raw sockets* +/// (`libc::SOL_SOCKET`), *ip protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`), +/// and more. Please refer to your system manual for more options. Will be passed as the second +/// argument (`level`) to the `setsockopt` call. +/// * `$flag:path`: a flag name to set. Some examples: `libc::SO_REUSEADDR`, `libc::TCP_NODELAY`, +/// `libc::IP_ADD_MEMBERSHIP` and others. Will be passed as the third argument (`option_name`) +/// to the `setsockopt` call. +/// * Type of the value that you are going to set. +/// * Type that implements the `Set` trait for the type from the previous item (like `SetBool` for +/// `bool`, `SetUsize` for `usize`, etc.). +macro_rules! setsockopt_impl { + ($name:ident, $level:expr, $flag:path, $ty:ty, $setter:ty) => { + impl SetSockOpt for $name { + type Val = $ty; + + fn set(&self, fd: RawFd, val: &$ty) -> Result<()> { + unsafe { + let setter: $setter = Set::new(val); + + let res = libc::setsockopt( + fd, + $level, + $flag, + setter.ffi_ptr(), + setter.ffi_len(), + ); + Errno::result(res).map(drop) + } + } + } + }; +} + +/// Helper for implementing `GetSockOpt` for a given socket option. See +/// [`::sys::socket::GetSockOpt`](sys/socket/trait.GetSockOpt.html). +/// +/// This macro aims to help implementing `GetSockOpt` for different socket options that accept +/// different kinds of data to be use with `getsockopt`. +/// +/// Instead of using this macro directly consider using `sockopt_impl!`, especially if the option +/// you are implementing represents a simple type. +/// +/// # Arguments +/// +/// * Name of the type you want to implement `GetSockOpt` for. +/// * Socket layer, or a `protocol level`: could be *raw sockets* (`lic::SOL_SOCKET`), *ip +/// protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`), and more. Please refer +/// to your system manual for more options. Will be passed as the second argument (`level`) to +/// the `getsockopt` call. +/// * A flag to set. Some examples: `libc::SO_REUSEADDR`, `libc::TCP_NODELAY`, +/// `libc::SO_ORIGINAL_DST` and others. Will be passed as the third argument (`option_name`) to +/// the `getsockopt` call. +/// * Type of the value that you are going to get. +/// * Type that implements the `Get` trait for the type from the previous item (`GetBool` for +/// `bool`, `GetUsize` for `usize`, etc.). +macro_rules! getsockopt_impl { + ($name:ident, $level:expr, $flag:path, $ty:ty, $getter:ty) => { + impl GetSockOpt for $name { + type Val = $ty; + + fn get(&self, fd: RawFd) -> Result<$ty> { + unsafe { + let mut getter: $getter = Get::uninit(); + + let res = libc::getsockopt( + fd, + $level, + $flag, + getter.ffi_ptr(), + getter.ffi_len(), + ); + Errno::result(res)?; + + match <$ty>::try_from(getter.assume_init()) { + Err(_) => Err(Errno::EINVAL), + Ok(r) => Ok(r) + } + } + } + } + }; +} + +/// Helper to generate the sockopt accessors. See +/// [`::sys::socket::GetSockOpt`](sys/socket/trait.GetSockOpt.html) and +/// [`::sys::socket::SetSockOpt`](sys/socket/trait.SetSockOpt.html). +/// +/// This macro aims to help implementing `GetSockOpt` and `SetSockOpt` for different socket options +/// that accept different kinds of data to be use with `getsockopt` and `setsockopt` respectively. +/// +/// Basically this macro wraps up the [`getsockopt_impl!`](macro.getsockopt_impl.html) and +/// [`setsockopt_impl!`](macro.setsockopt_impl.html) macros. +/// +/// # Arguments +/// +/// * `GetOnly`, `SetOnly` or `Both`: whether you want to implement only getter, only setter or +/// both of them. +/// * `$name:ident`: name of type `GetSockOpt`/`SetSockOpt` will be implemented for. +/// * `$level:expr` : socket layer, or a `protocol level`: could be *raw sockets* +/// (`lic::SOL_SOCKET`), *ip protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`), +/// and more. Please refer to your system manual for more options. Will be passed as the second +/// argument (`level`) to the `getsockopt`/`setsockopt` call. +/// * `$flag:path`: a flag name to set. Some examples: `libc::SO_REUSEADDR`, `libc::TCP_NODELAY`, +/// `libc::IP_ADD_MEMBERSHIP` and others. Will be passed as the third argument (`option_name`) +/// to the `setsockopt`/`getsockopt` call. +/// * `$ty:ty`: type of the value that will be get/set. +/// * `$getter:ty`: `Get` implementation; optional; only for `GetOnly` and `Both`. +/// * `$setter:ty`: `Set` implementation; optional; only for `SetOnly` and `Both`. +// Some targets don't use all rules. +#[allow(unknown_lints)] +#[allow(unused_macro_rules)] +macro_rules! sockopt_impl { + ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, bool) => { + sockopt_impl!($(#[$attr])* + $name, GetOnly, $level, $flag, bool, GetBool); + }; + + ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, u8) => { + sockopt_impl!($(#[$attr])* $name, GetOnly, $level, $flag, u8, GetU8); + }; + + ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, usize) => + { + sockopt_impl!($(#[$attr])* + $name, GetOnly, $level, $flag, usize, GetUsize); + }; + + ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, bool) => { + sockopt_impl!($(#[$attr])* + $name, SetOnly, $level, $flag, bool, SetBool); + }; + + ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, u8) => { + sockopt_impl!($(#[$attr])* $name, SetOnly, $level, $flag, u8, SetU8); + }; + + ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, usize) => + { + sockopt_impl!($(#[$attr])* + $name, SetOnly, $level, $flag, usize, SetUsize); + }; + + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, bool) => { + sockopt_impl!($(#[$attr])* + $name, Both, $level, $flag, bool, GetBool, SetBool); + }; + + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, u8) => { + sockopt_impl!($(#[$attr])* + $name, Both, $level, $flag, u8, GetU8, SetU8); + }; + + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, usize) => { + sockopt_impl!($(#[$attr])* + $name, Both, $level, $flag, usize, GetUsize, SetUsize); + }; + + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, + OsString<$array:ty>) => + { + sockopt_impl!($(#[$attr])* + $name, Both, $level, $flag, OsString, GetOsString<$array>, + SetOsString); + }; + + /* + * Matchers with generic getter types must be placed at the end, so + * they'll only match _after_ specialized matchers fail + */ + ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, $ty:ty) => + { + sockopt_impl!($(#[$attr])* + $name, GetOnly, $level, $flag, $ty, GetStruct<$ty>); + }; + + ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, $ty:ty, + $getter:ty) => + { + $(#[$attr])* + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + pub struct $name; + + getsockopt_impl!($name, $level, $flag, $ty, $getter); + }; + + ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, $ty:ty) => + { + sockopt_impl!($(#[$attr])* + $name, SetOnly, $level, $flag, $ty, SetStruct<$ty>); + }; + + ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, $ty:ty, + $setter:ty) => + { + $(#[$attr])* + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + pub struct $name; + + setsockopt_impl!($name, $level, $flag, $ty, $setter); + }; + + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, $ty:ty, + $getter:ty, $setter:ty) => + { + $(#[$attr])* + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + pub struct $name; + + setsockopt_impl!($name, $level, $flag, $ty, $setter); + getsockopt_impl!($name, $level, $flag, $ty, $getter); + }; + + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, $ty:ty) => { + sockopt_impl!($(#[$attr])* + $name, Both, $level, $flag, $ty, GetStruct<$ty>, + SetStruct<$ty>); + }; +} + +/* + * + * ===== Define sockopts ===== + * + */ + +sockopt_impl!( + /// Enables local address reuse + ReuseAddr, + Both, + libc::SOL_SOCKET, + libc::SO_REUSEADDR, + bool +); +#[cfg(not(any(target_os = "illumos", target_os = "solaris")))] +sockopt_impl!( + /// Permits multiple AF_INET or AF_INET6 sockets to be bound to an + /// identical socket address. + ReusePort, + Both, + libc::SOL_SOCKET, + libc::SO_REUSEPORT, + bool +); +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Under most circumstances, TCP sends data when it is presented; when + /// outstanding data has not yet been acknowledged, it gathers small amounts + /// of output to be sent in a single packet once an acknowledgement is + /// received. For a small number of clients, such as window systems that + /// send a stream of mouse events which receive no replies, this + /// packetization may cause significant delays. The boolean option + /// TCP_NODELAY defeats this algorithm. + TcpNoDelay, + Both, + libc::IPPROTO_TCP, + libc::TCP_NODELAY, + bool +); +sockopt_impl!( + /// When enabled, a close(2) or shutdown(2) will not return until all + /// queued messages for the socket have been successfully sent or the + /// linger timeout has been reached. + Linger, + Both, + libc::SOL_SOCKET, + libc::SO_LINGER, + libc::linger +); +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Join a multicast group + IpAddMembership, + SetOnly, + libc::IPPROTO_IP, + libc::IP_ADD_MEMBERSHIP, + super::IpMembershipRequest +); +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Leave a multicast group. + IpDropMembership, + SetOnly, + libc::IPPROTO_IP, + libc::IP_DROP_MEMBERSHIP, + super::IpMembershipRequest +); +cfg_if! { + if #[cfg(any(target_os = "android", target_os = "linux"))] { + #[cfg(feature = "net")] + sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Join an IPv6 multicast group. + Ipv6AddMembership, SetOnly, libc::IPPROTO_IPV6, libc::IPV6_ADD_MEMBERSHIP, super::Ipv6MembershipRequest); + #[cfg(feature = "net")] + sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Leave an IPv6 multicast group. + Ipv6DropMembership, SetOnly, libc::IPPROTO_IPV6, libc::IPV6_DROP_MEMBERSHIP, super::Ipv6MembershipRequest); + } else if #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris"))] { + #[cfg(feature = "net")] + sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Join an IPv6 multicast group. + Ipv6AddMembership, SetOnly, libc::IPPROTO_IPV6, + libc::IPV6_JOIN_GROUP, super::Ipv6MembershipRequest); + #[cfg(feature = "net")] + sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Leave an IPv6 multicast group. + Ipv6DropMembership, SetOnly, libc::IPPROTO_IPV6, + libc::IPV6_LEAVE_GROUP, super::Ipv6MembershipRequest); + } +} +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Set or read the time-to-live value of outgoing multicast packets for + /// this socket. + IpMulticastTtl, + Both, + libc::IPPROTO_IP, + libc::IP_MULTICAST_TTL, + u8 +); +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Set or read a boolean integer argument that determines whether sent + /// multicast packets should be looped back to the local sockets. + IpMulticastLoop, + Both, + libc::IPPROTO_IP, + libc::IP_MULTICAST_LOOP, + bool +); +#[cfg(target_os = "linux")] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Set the protocol-defined priority for all packets to be + /// sent on this socket + Priority, + Both, + libc::SOL_SOCKET, + libc::SO_PRIORITY, + libc::c_int +); +#[cfg(target_os = "linux")] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Set or receive the Type-Of-Service (TOS) field that is + /// sent with every IP packet originating from this socket + IpTos, + Both, + libc::IPPROTO_IP, + libc::IP_TOS, + libc::c_int +); +#[cfg(target_os = "linux")] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Traffic class associated with outgoing packets + Ipv6TClass, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_TCLASS, + libc::c_int +); +#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// If enabled, this boolean option allows binding to an IP address that + /// is nonlocal or does not (yet) exist. + IpFreebind, + Both, + libc::IPPROTO_IP, + libc::IP_FREEBIND, + bool +); +sockopt_impl!( + /// Specify the receiving timeout until reporting an error. + ReceiveTimeout, + Both, + libc::SOL_SOCKET, + libc::SO_RCVTIMEO, + TimeVal +); +sockopt_impl!( + /// Specify the sending timeout until reporting an error. + SendTimeout, + Both, + libc::SOL_SOCKET, + libc::SO_SNDTIMEO, + TimeVal +); +sockopt_impl!( + /// Set or get the broadcast flag. + Broadcast, + Both, + libc::SOL_SOCKET, + libc::SO_BROADCAST, + bool +); +sockopt_impl!( + /// If this option is enabled, out-of-band data is directly placed into + /// the receive data stream. + OobInline, + Both, + libc::SOL_SOCKET, + libc::SO_OOBINLINE, + bool +); +sockopt_impl!( + /// Get and clear the pending socket error. + SocketError, + GetOnly, + libc::SOL_SOCKET, + libc::SO_ERROR, + i32 +); +sockopt_impl!( + /// Set or get the don't route flag. + DontRoute, + Both, + libc::SOL_SOCKET, + libc::SO_DONTROUTE, + bool +); +sockopt_impl!( + /// Enable sending of keep-alive messages on connection-oriented sockets. + KeepAlive, + Both, + libc::SOL_SOCKET, + libc::SO_KEEPALIVE, + bool +); +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "ios" +))] +sockopt_impl!( + /// Get the credentials of the peer process of a connected unix domain + /// socket. + LocalPeerCred, + GetOnly, + 0, + libc::LOCAL_PEERCRED, + super::XuCred +); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!( + /// Return the credentials of the foreign process connected to this socket. + PeerCredentials, + GetOnly, + libc::SOL_SOCKET, + libc::SO_PEERCRED, + super::UnixCredentials +); +#[cfg(any(target_os = "ios", target_os = "macos"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Specify the amount of time, in seconds, that the connection must be idle + /// before keepalive probes (if enabled) are sent. + TcpKeepAlive, + Both, + libc::IPPROTO_TCP, + libc::TCP_KEEPALIVE, + u32 +); +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux" +))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// The time (in seconds) the connection needs to remain idle before TCP + /// starts sending keepalive probes + TcpKeepIdle, + Both, + libc::IPPROTO_TCP, + libc::TCP_KEEPIDLE, + u32 +); +cfg_if! { + if #[cfg(any(target_os = "android", target_os = "linux"))] { + sockopt_impl!( + /// The maximum segment size for outgoing TCP packets. + TcpMaxSeg, Both, libc::IPPROTO_TCP, libc::TCP_MAXSEG, u32); + } else { + sockopt_impl!( + /// The maximum segment size for outgoing TCP packets. + TcpMaxSeg, GetOnly, libc::IPPROTO_TCP, libc::TCP_MAXSEG, u32); + } +} +#[cfg(not(any(target_os = "openbsd", target_os = "haiku")))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// The maximum number of keepalive probes TCP should send before + /// dropping the connection. + TcpKeepCount, + Both, + libc::IPPROTO_TCP, + libc::TCP_KEEPCNT, + u32 +); +#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] +sockopt_impl!( + #[allow(missing_docs)] + // Not documented by Linux! + TcpRepair, + Both, + libc::IPPROTO_TCP, + libc::TCP_REPAIR, + u32 +); +#[cfg(not(any(target_os = "openbsd", target_os = "haiku")))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// The time (in seconds) between individual keepalive probes. + TcpKeepInterval, + Both, + libc::IPPROTO_TCP, + libc::TCP_KEEPINTVL, + u32 +); +#[cfg(any(target_os = "fuchsia", target_os = "linux"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Specifies the maximum amount of time in milliseconds that transmitted + /// data may remain unacknowledged before TCP will forcibly close the + /// corresponding connection + TcpUserTimeout, + Both, + libc::IPPROTO_TCP, + libc::TCP_USER_TIMEOUT, + u32 +); +sockopt_impl!( + /// Sets or gets the maximum socket receive buffer in bytes. + RcvBuf, + Both, + libc::SOL_SOCKET, + libc::SO_RCVBUF, + usize +); +sockopt_impl!( + /// Sets or gets the maximum socket send buffer in bytes. + SndBuf, + Both, + libc::SOL_SOCKET, + libc::SO_SNDBUF, + usize +); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!( + /// Using this socket option, a privileged (`CAP_NET_ADMIN`) process can + /// perform the same task as `SO_RCVBUF`, but the `rmem_max limit` can be + /// overridden. + RcvBufForce, + SetOnly, + libc::SOL_SOCKET, + libc::SO_RCVBUFFORCE, + usize +); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!( + /// Using this socket option, a privileged (`CAP_NET_ADMIN`) process can + /// perform the same task as `SO_SNDBUF`, but the `wmem_max` limit can be + /// overridden. + SndBufForce, + SetOnly, + libc::SOL_SOCKET, + libc::SO_SNDBUFFORCE, + usize +); +sockopt_impl!( + /// Gets the socket type as an integer. + SockType, + GetOnly, + libc::SOL_SOCKET, + libc::SO_TYPE, + super::SockType, + GetStruct<i32> +); +sockopt_impl!( + /// Returns a value indicating whether or not this socket has been marked to + /// accept connections with `listen(2)`. + AcceptConn, + GetOnly, + libc::SOL_SOCKET, + libc::SO_ACCEPTCONN, + bool +); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!( + /// Bind this socket to a particular device like “eth0”. + BindToDevice, + Both, + libc::SOL_SOCKET, + libc::SO_BINDTODEVICE, + OsString<[u8; libc::IFNAMSIZ]> +); +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + #[allow(missing_docs)] + // Not documented by Linux! + OriginalDst, + GetOnly, + libc::SOL_IP, + libc::SO_ORIGINAL_DST, + libc::sockaddr_in +); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!( + #[allow(missing_docs)] + // Not documented by Linux! + Ip6tOriginalDst, + GetOnly, + libc::SOL_IPV6, + libc::IP6T_SO_ORIGINAL_DST, + libc::sockaddr_in6 +); +#[cfg(any(target_os = "linux"))] +sockopt_impl!( + /// Specifies exact type of timestamping information collected by the kernel + /// [Further reading](https://www.kernel.org/doc/html/latest/networking/timestamping.html) + Timestamping, + Both, + libc::SOL_SOCKET, + libc::SO_TIMESTAMPING, + super::TimestampingFlag +); +#[cfg(not(target_os = "haiku"))] +sockopt_impl!( + /// Enable or disable the receiving of the `SO_TIMESTAMP` control message. + ReceiveTimestamp, + Both, + libc::SOL_SOCKET, + libc::SO_TIMESTAMP, + bool +); +#[cfg(all(target_os = "linux"))] +sockopt_impl!( + /// Enable or disable the receiving of the `SO_TIMESTAMPNS` control message. + ReceiveTimestampns, + Both, + libc::SOL_SOCKET, + libc::SO_TIMESTAMPNS, + bool +); +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Setting this boolean option enables transparent proxying on this socket. + IpTransparent, + Both, + libc::SOL_IP, + libc::IP_TRANSPARENT, + bool +); +#[cfg(target_os = "openbsd")] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Allows the socket to be bound to addresses which are not local to the + /// machine, so it can be used to make a transparent proxy. + BindAny, + Both, + libc::SOL_SOCKET, + libc::SO_BINDANY, + bool +); +#[cfg(target_os = "freebsd")] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Can `bind(2)` to any address, even one not bound to any available + /// network interface in the system. + BindAny, + Both, + libc::IPPROTO_IP, + libc::IP_BINDANY, + bool +); +#[cfg(target_os = "linux")] +sockopt_impl!( + /// Set the mark for each packet sent through this socket (similar to the + /// netfilter MARK target but socket-based). + Mark, + Both, + libc::SOL_SOCKET, + libc::SO_MARK, + u32 +); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!( + /// Enable or disable the receiving of the `SCM_CREDENTIALS` control + /// message. + PassCred, + Both, + libc::SOL_SOCKET, + libc::SO_PASSCRED, + bool +); +#[cfg(any(target_os = "freebsd", target_os = "linux"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// This option allows the caller to set the TCP congestion control + /// algorithm to be used, on a per-socket basis. + TcpCongestion, + Both, + libc::IPPROTO_TCP, + libc::TCP_CONGESTION, + OsString<[u8; TCP_CA_NAME_MAX]> +); +#[cfg(any( + target_os = "android", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", +))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Pass an `IP_PKTINFO` ancillary message that contains a pktinfo + /// structure that supplies some information about the incoming packet. + Ipv4PacketInfo, + Both, + libc::IPPROTO_IP, + libc::IP_PKTINFO, + bool +); +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", +))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Set delivery of the `IPV6_PKTINFO` control message on incoming + /// datagrams. + Ipv6RecvPacketInfo, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_RECVPKTINFO, + bool +); +#[cfg(any( + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", +))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// The `recvmsg(2)` call returns a `struct sockaddr_dl` corresponding to + /// the interface on which the packet was received. + Ipv4RecvIf, + Both, + libc::IPPROTO_IP, + libc::IP_RECVIF, + bool +); +#[cfg(any( + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", +))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// The `recvmsg(2)` call will return the destination IP address for a UDP + /// datagram. + Ipv4RecvDstAddr, + Both, + libc::IPPROTO_IP, + libc::IP_RECVDSTADDR, + bool +); +#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// The `recvmsg(2)` call will return the destination IP address for a UDP + /// datagram. + Ipv4OrigDstAddr, + Both, + libc::IPPROTO_IP, + libc::IP_ORIGDSTADDR, + bool +); +#[cfg(target_os = "linux")] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + #[allow(missing_docs)] + // Not documented by Linux! + UdpGsoSegment, + Both, + libc::SOL_UDP, + libc::UDP_SEGMENT, + libc::c_int +); +#[cfg(target_os = "linux")] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + #[allow(missing_docs)] + // Not documented by Linux! + UdpGroSegment, + Both, + libc::IPPROTO_UDP, + libc::UDP_GRO, + bool +); +#[cfg(target_os = "linux")] +sockopt_impl!( + /// Configures the behavior of time-based transmission of packets, for use + /// with the `TxTime` control message. + TxTime, + Both, + libc::SOL_SOCKET, + libc::SO_TXTIME, + libc::sock_txtime +); +#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] +sockopt_impl!( + /// Indicates that an unsigned 32-bit value ancillary message (cmsg) should + /// be attached to received skbs indicating the number of packets dropped by + /// the socket since its creation. + RxqOvfl, + Both, + libc::SOL_SOCKET, + libc::SO_RXQ_OVFL, + libc::c_int +); +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// The socket is restricted to sending and receiving IPv6 packets only. + Ipv6V6Only, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_V6ONLY, + bool +); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!( + /// Enable extended reliable error message passing. + Ipv4RecvErr, + Both, + libc::IPPROTO_IP, + libc::IP_RECVERR, + bool +); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!( + /// Control receiving of asynchronous error options. + Ipv6RecvErr, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_RECVERR, + bool +); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!( + /// Fetch the current system-estimated Path MTU. + IpMtu, + GetOnly, + libc::IPPROTO_IP, + libc::IP_MTU, + libc::c_int +); +#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +sockopt_impl!( + /// Set or retrieve the current time-to-live field that is used in every + /// packet sent from this socket. + Ipv4Ttl, + Both, + libc::IPPROTO_IP, + libc::IP_TTL, + libc::c_int +); +#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +sockopt_impl!( + /// Set the unicast hop limit for the socket. + Ipv6Ttl, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_UNICAST_HOPS, + libc::c_int +); +#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// The `recvmsg(2)` call will return the destination IP address for a UDP + /// datagram. + Ipv6OrigDstAddr, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_ORIGDSTADDR, + bool +); +#[cfg(any(target_os = "ios", target_os = "macos"))] +sockopt_impl!( + /// Set "don't fragment packet" flag on the IP packet. + IpDontFrag, + Both, + libc::IPPROTO_IP, + libc::IP_DONTFRAG, + bool +); +#[cfg(any( + target_os = "android", + target_os = "ios", + target_os = "linux", + target_os = "macos", +))] +sockopt_impl!( + /// Set "don't fragment packet" flag on the IPv6 packet. + Ipv6DontFrag, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_DONTFRAG, + bool +); + +#[allow(missing_docs)] +// Not documented by Linux! +#[cfg(any(target_os = "android", target_os = "linux"))] +#[derive(Copy, Clone, Debug)] +pub struct AlgSetAeadAuthSize; + +// ALG_SET_AEAD_AUTH_SIZE read the length from passed `option_len` +// See https://elixir.bootlin.com/linux/v4.4/source/crypto/af_alg.c#L222 +#[cfg(any(target_os = "android", target_os = "linux"))] +impl SetSockOpt for AlgSetAeadAuthSize { + type Val = usize; + + fn set(&self, fd: RawFd, val: &usize) -> Result<()> { + unsafe { + let res = libc::setsockopt( + fd, + libc::SOL_ALG, + libc::ALG_SET_AEAD_AUTHSIZE, + ::std::ptr::null(), + *val as libc::socklen_t, + ); + Errno::result(res).map(drop) + } + } +} + +#[allow(missing_docs)] +// Not documented by Linux! +#[cfg(any(target_os = "android", target_os = "linux"))] +#[derive(Clone, Debug)] +pub struct AlgSetKey<T>(::std::marker::PhantomData<T>); + +#[cfg(any(target_os = "android", target_os = "linux"))] +impl<T> Default for AlgSetKey<T> { + fn default() -> Self { + AlgSetKey(Default::default()) + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +impl<T> SetSockOpt for AlgSetKey<T> +where + T: AsRef<[u8]> + Clone, +{ + type Val = T; + + fn set(&self, fd: RawFd, val: &T) -> Result<()> { + unsafe { + let res = libc::setsockopt( + fd, + libc::SOL_ALG, + libc::ALG_SET_KEY, + val.as_ref().as_ptr() as *const _, + val.as_ref().len() as libc::socklen_t, + ); + Errno::result(res).map(drop) + } + } +} + +/* + * + * ===== Accessor helpers ===== + * + */ + +/// Helper trait that describes what is expected from a `GetSockOpt` getter. +trait Get<T> { + /// Returns an uninitialized value. + fn uninit() -> Self; + /// Returns a pointer to the stored value. This pointer will be passed to the system's + /// `getsockopt` call (`man 3p getsockopt`, argument `option_value`). + fn ffi_ptr(&mut self) -> *mut c_void; + /// Returns length of the stored value. This pointer will be passed to the system's + /// `getsockopt` call (`man 3p getsockopt`, argument `option_len`). + fn ffi_len(&mut self) -> *mut socklen_t; + /// Returns the hopefully initialized inner value. + unsafe fn assume_init(self) -> T; +} + +/// Helper trait that describes what is expected from a `SetSockOpt` setter. +trait Set<'a, T> { + /// Initialize the setter with a given value. + fn new(val: &'a T) -> Self; + /// Returns a pointer to the stored value. This pointer will be passed to the system's + /// `setsockopt` call (`man 3p setsockopt`, argument `option_value`). + fn ffi_ptr(&self) -> *const c_void; + /// Returns length of the stored value. This pointer will be passed to the system's + /// `setsockopt` call (`man 3p setsockopt`, argument `option_len`). + fn ffi_len(&self) -> socklen_t; +} + +/// Getter for an arbitrary `struct`. +struct GetStruct<T> { + len: socklen_t, + val: MaybeUninit<T>, +} + +impl<T> Get<T> for GetStruct<T> { + fn uninit() -> Self { + GetStruct { + len: mem::size_of::<T>() as socklen_t, + val: MaybeUninit::uninit(), + } + } + + fn ffi_ptr(&mut self) -> *mut c_void { + self.val.as_mut_ptr() as *mut c_void + } + + fn ffi_len(&mut self) -> *mut socklen_t { + &mut self.len + } + + unsafe fn assume_init(self) -> T { + assert_eq!( + self.len as usize, + mem::size_of::<T>(), + "invalid getsockopt implementation" + ); + self.val.assume_init() + } +} + +/// Setter for an arbitrary `struct`. +struct SetStruct<'a, T: 'static> { + ptr: &'a T, +} + +impl<'a, T> Set<'a, T> for SetStruct<'a, T> { + fn new(ptr: &'a T) -> SetStruct<'a, T> { + SetStruct { ptr } + } + + fn ffi_ptr(&self) -> *const c_void { + self.ptr as *const T as *const c_void + } + + fn ffi_len(&self) -> socklen_t { + mem::size_of::<T>() as socklen_t + } +} + +/// Getter for a boolean value. +struct GetBool { + len: socklen_t, + val: MaybeUninit<c_int>, +} + +impl Get<bool> for GetBool { + fn uninit() -> Self { + GetBool { + len: mem::size_of::<c_int>() as socklen_t, + val: MaybeUninit::uninit(), + } + } + + fn ffi_ptr(&mut self) -> *mut c_void { + self.val.as_mut_ptr() as *mut c_void + } + + fn ffi_len(&mut self) -> *mut socklen_t { + &mut self.len + } + + unsafe fn assume_init(self) -> bool { + assert_eq!( + self.len as usize, + mem::size_of::<c_int>(), + "invalid getsockopt implementation" + ); + self.val.assume_init() != 0 + } +} + +/// Setter for a boolean value. +struct SetBool { + val: c_int, +} + +impl<'a> Set<'a, bool> for SetBool { + fn new(val: &'a bool) -> SetBool { + SetBool { + val: i32::from(*val), + } + } + + fn ffi_ptr(&self) -> *const c_void { + &self.val as *const c_int as *const c_void + } + + fn ffi_len(&self) -> socklen_t { + mem::size_of::<c_int>() as socklen_t + } +} + +/// Getter for an `u8` value. +struct GetU8 { + len: socklen_t, + val: MaybeUninit<u8>, +} + +impl Get<u8> for GetU8 { + fn uninit() -> Self { + GetU8 { + len: mem::size_of::<u8>() as socklen_t, + val: MaybeUninit::uninit(), + } + } + + fn ffi_ptr(&mut self) -> *mut c_void { + self.val.as_mut_ptr() as *mut c_void + } + + fn ffi_len(&mut self) -> *mut socklen_t { + &mut self.len + } + + unsafe fn assume_init(self) -> u8 { + assert_eq!( + self.len as usize, + mem::size_of::<u8>(), + "invalid getsockopt implementation" + ); + self.val.assume_init() + } +} + +/// Setter for an `u8` value. +struct SetU8 { + val: u8, +} + +impl<'a> Set<'a, u8> for SetU8 { + fn new(val: &'a u8) -> SetU8 { + SetU8 { val: *val } + } + + fn ffi_ptr(&self) -> *const c_void { + &self.val as *const u8 as *const c_void + } + + fn ffi_len(&self) -> socklen_t { + mem::size_of::<c_int>() as socklen_t + } +} + +/// Getter for an `usize` value. +struct GetUsize { + len: socklen_t, + val: MaybeUninit<c_int>, +} + +impl Get<usize> for GetUsize { + fn uninit() -> Self { + GetUsize { + len: mem::size_of::<c_int>() as socklen_t, + val: MaybeUninit::uninit(), + } + } + + fn ffi_ptr(&mut self) -> *mut c_void { + self.val.as_mut_ptr() as *mut c_void + } + + fn ffi_len(&mut self) -> *mut socklen_t { + &mut self.len + } + + unsafe fn assume_init(self) -> usize { + assert_eq!( + self.len as usize, + mem::size_of::<c_int>(), + "invalid getsockopt implementation" + ); + self.val.assume_init() as usize + } +} + +/// Setter for an `usize` value. +struct SetUsize { + val: c_int, +} + +impl<'a> Set<'a, usize> for SetUsize { + fn new(val: &'a usize) -> SetUsize { + SetUsize { val: *val as c_int } + } + + fn ffi_ptr(&self) -> *const c_void { + &self.val as *const c_int as *const c_void + } + + fn ffi_len(&self) -> socklen_t { + mem::size_of::<c_int>() as socklen_t + } +} + +/// Getter for a `OsString` value. +struct GetOsString<T: AsMut<[u8]>> { + len: socklen_t, + val: MaybeUninit<T>, +} + +impl<T: AsMut<[u8]>> Get<OsString> for GetOsString<T> { + fn uninit() -> Self { + GetOsString { + len: mem::size_of::<T>() as socklen_t, + val: MaybeUninit::uninit(), + } + } + + fn ffi_ptr(&mut self) -> *mut c_void { + self.val.as_mut_ptr() as *mut c_void + } + + fn ffi_len(&mut self) -> *mut socklen_t { + &mut self.len + } + + unsafe fn assume_init(self) -> OsString { + let len = self.len as usize; + let mut v = self.val.assume_init(); + OsStr::from_bytes(&v.as_mut()[0..len]).to_owned() + } +} + +/// Setter for a `OsString` value. +struct SetOsString<'a> { + val: &'a OsStr, +} + +impl<'a> Set<'a, OsString> for SetOsString<'a> { + fn new(val: &'a OsString) -> SetOsString { + SetOsString { + val: val.as_os_str(), + } + } + + fn ffi_ptr(&self) -> *const c_void { + self.val.as_bytes().as_ptr() as *const c_void + } + + fn ffi_len(&self) -> socklen_t { + self.val.len() as socklen_t + } +} + +#[cfg(test)] +mod test { + #[cfg(any(target_os = "android", target_os = "linux"))] + #[test] + fn can_get_peercred_on_unix_socket() { + use super::super::*; + + let (a, b) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); + let a_cred = getsockopt(a, super::PeerCredentials).unwrap(); + let b_cred = getsockopt(b, super::PeerCredentials).unwrap(); + assert_eq!(a_cred, b_cred); + assert_ne!(a_cred.pid(), 0); + } + + #[test] + fn is_socket_type_unix() { + use super::super::*; + use crate::unistd::close; + + let (a, b) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); + let a_type = getsockopt(a, super::SockType).unwrap(); + assert_eq!(a_type, SockType::Stream); + close(a).unwrap(); + close(b).unwrap(); + } + + #[test] + fn is_socket_type_dgram() { + use super::super::*; + use crate::unistd::close; + + let s = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + let s_type = getsockopt(s, super::SockType).unwrap(); + assert_eq!(s_type, SockType::Datagram); + close(s).unwrap(); + } + + #[cfg(any(target_os = "freebsd", target_os = "linux"))] + #[test] + fn can_get_listen_on_tcp_socket() { + use super::super::*; + use crate::unistd::close; + + let s = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + None, + ) + .unwrap(); + let s_listening = getsockopt(s, super::AcceptConn).unwrap(); + assert!(!s_listening); + listen(s, 10).unwrap(); + let s_listening2 = getsockopt(s, super::AcceptConn).unwrap(); + assert!(s_listening2); + close(s).unwrap(); + } +} diff --git a/third_party/rust/nix/src/sys/stat.rs b/third_party/rust/nix/src/sys/stat.rs new file mode 100644 index 0000000000..78203bfbe3 --- /dev/null +++ b/third_party/rust/nix/src/sys/stat.rs @@ -0,0 +1,480 @@ +#[cfg(any(target_os = "macos", target_os = "ios", target_os = "openbsd"))] +pub use libc::c_uint; +#[cfg(any( + target_os = "netbsd", + target_os = "freebsd", + target_os = "dragonfly" +))] +pub use libc::c_ulong; +pub use libc::stat as FileStat; +pub use libc::{dev_t, mode_t}; + +#[cfg(not(target_os = "redox"))] +use crate::fcntl::{at_rawfd, AtFlags}; +use crate::sys::time::{TimeSpec, TimeVal}; +use crate::{errno::Errno, NixPath, Result}; +use std::mem; +use std::os::unix::io::RawFd; + +libc_bitflags!( + /// "File type" flags for `mknod` and related functions. + pub struct SFlag: mode_t { + S_IFIFO; + S_IFCHR; + S_IFDIR; + S_IFBLK; + S_IFREG; + S_IFLNK; + S_IFSOCK; + S_IFMT; + } +); + +libc_bitflags! { + /// "File mode / permissions" flags. + pub struct Mode: mode_t { + /// Read, write and execute for owner. + S_IRWXU; + /// Read for owner. + S_IRUSR; + /// Write for owner. + S_IWUSR; + /// Execute for owner. + S_IXUSR; + /// Read write and execute for group. + S_IRWXG; + /// Read fr group. + S_IRGRP; + /// Write for group. + S_IWGRP; + /// Execute for group. + S_IXGRP; + /// Read, write and execute for other. + S_IRWXO; + /// Read for other. + S_IROTH; + /// Write for other. + S_IWOTH; + /// Execute for other. + S_IXOTH; + /// Set user id on execution. + S_ISUID as mode_t; + /// Set group id on execution. + S_ISGID as mode_t; + S_ISVTX as mode_t; + } +} + +#[cfg(any(target_os = "macos", target_os = "ios", target_os = "openbsd"))] +pub type type_of_file_flag = c_uint; +#[cfg(any( + target_os = "netbsd", + target_os = "freebsd", + target_os = "dragonfly" +))] +pub type type_of_file_flag = c_ulong; + +#[cfg(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "macos", + target_os = "ios" +))] +libc_bitflags! { + /// File flags. + #[cfg_attr(docsrs, doc(cfg(all())))] + pub struct FileFlag: type_of_file_flag { + /// The file may only be appended to. + SF_APPEND; + /// The file has been archived. + SF_ARCHIVED; + #[cfg(any(target_os = "dragonfly"))] + SF_CACHE; + /// The file may not be changed. + SF_IMMUTABLE; + /// Indicates a WAPBL journal file. + #[cfg(any(target_os = "netbsd"))] + SF_LOG; + /// Do not retain history for file + #[cfg(any(target_os = "dragonfly"))] + SF_NOHISTORY; + /// The file may not be renamed or deleted. + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + SF_NOUNLINK; + /// Mask of superuser changeable flags + SF_SETTABLE; + /// Snapshot is invalid. + #[cfg(any(target_os = "netbsd"))] + SF_SNAPINVAL; + /// The file is a snapshot file. + #[cfg(any(target_os = "netbsd", target_os = "freebsd"))] + SF_SNAPSHOT; + #[cfg(any(target_os = "dragonfly"))] + SF_XLINK; + /// The file may only be appended to. + UF_APPEND; + /// The file needs to be archived. + #[cfg(any(target_os = "freebsd"))] + UF_ARCHIVE; + #[cfg(any(target_os = "dragonfly"))] + UF_CACHE; + /// File is compressed at the file system level. + #[cfg(any(target_os = "macos", target_os = "ios"))] + UF_COMPRESSED; + /// The file may be hidden from directory listings at the application's + /// discretion. + #[cfg(any( + target_os = "freebsd", + target_os = "macos", + target_os = "ios", + ))] + UF_HIDDEN; + /// The file may not be changed. + UF_IMMUTABLE; + /// Do not dump the file. + UF_NODUMP; + #[cfg(any(target_os = "dragonfly"))] + UF_NOHISTORY; + /// The file may not be renamed or deleted. + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + UF_NOUNLINK; + /// The file is offline, or has the Windows and CIFS + /// `FILE_ATTRIBUTE_OFFLINE` attribute. + #[cfg(any(target_os = "freebsd"))] + UF_OFFLINE; + /// The directory is opaque when viewed through a union stack. + UF_OPAQUE; + /// The file is read only, and may not be written or appended. + #[cfg(any(target_os = "freebsd"))] + UF_READONLY; + /// The file contains a Windows reparse point. + #[cfg(any(target_os = "freebsd"))] + UF_REPARSE; + /// Mask of owner changeable flags. + UF_SETTABLE; + /// The file has the Windows `FILE_ATTRIBUTE_SPARSE_FILE` attribute. + #[cfg(any(target_os = "freebsd"))] + UF_SPARSE; + /// The file has the DOS, Windows and CIFS `FILE_ATTRIBUTE_SYSTEM` + /// attribute. + #[cfg(any(target_os = "freebsd"))] + UF_SYSTEM; + /// File renames and deletes are tracked. + #[cfg(any(target_os = "macos", target_os = "ios"))] + UF_TRACKED; + #[cfg(any(target_os = "dragonfly"))] + UF_XLINK; + } +} + +/// Create a special or ordinary file, by pathname. +pub fn mknod<P: ?Sized + NixPath>( + path: &P, + kind: SFlag, + perm: Mode, + dev: dev_t, +) -> Result<()> { + let res = path.with_nix_path(|cstr| unsafe { + libc::mknod(cstr.as_ptr(), kind.bits | perm.bits() as mode_t, dev) + })?; + + Errno::result(res).map(drop) +} + +/// Create a special or ordinary file, relative to a given directory. +#[cfg(not(any( + target_os = "ios", + target_os = "macos", + target_os = "redox", + target_os = "haiku" +)))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn mknodat<P: ?Sized + NixPath>( + dirfd: RawFd, + path: &P, + kind: SFlag, + perm: Mode, + dev: dev_t, +) -> Result<()> { + let res = path.with_nix_path(|cstr| unsafe { + libc::mknodat( + dirfd, + cstr.as_ptr(), + kind.bits | perm.bits() as mode_t, + dev, + ) + })?; + + Errno::result(res).map(drop) +} + +#[cfg(target_os = "linux")] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub const fn major(dev: dev_t) -> u64 { + ((dev >> 32) & 0xffff_f000) | ((dev >> 8) & 0x0000_0fff) +} + +#[cfg(target_os = "linux")] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub const fn minor(dev: dev_t) -> u64 { + ((dev >> 12) & 0xffff_ff00) | ((dev) & 0x0000_00ff) +} + +#[cfg(target_os = "linux")] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub const fn makedev(major: u64, minor: u64) -> dev_t { + ((major & 0xffff_f000) << 32) + | ((major & 0x0000_0fff) << 8) + | ((minor & 0xffff_ff00) << 12) + | (minor & 0x0000_00ff) +} + +pub fn umask(mode: Mode) -> Mode { + let prev = unsafe { libc::umask(mode.bits() as mode_t) }; + Mode::from_bits(prev).expect("[BUG] umask returned invalid Mode") +} + +pub fn stat<P: ?Sized + NixPath>(path: &P) -> Result<FileStat> { + let mut dst = mem::MaybeUninit::uninit(); + let res = path.with_nix_path(|cstr| unsafe { + libc::stat(cstr.as_ptr(), dst.as_mut_ptr()) + })?; + + Errno::result(res)?; + + Ok(unsafe { dst.assume_init() }) +} + +pub fn lstat<P: ?Sized + NixPath>(path: &P) -> Result<FileStat> { + let mut dst = mem::MaybeUninit::uninit(); + let res = path.with_nix_path(|cstr| unsafe { + libc::lstat(cstr.as_ptr(), dst.as_mut_ptr()) + })?; + + Errno::result(res)?; + + Ok(unsafe { dst.assume_init() }) +} + +pub fn fstat(fd: RawFd) -> Result<FileStat> { + let mut dst = mem::MaybeUninit::uninit(); + let res = unsafe { libc::fstat(fd, dst.as_mut_ptr()) }; + + Errno::result(res)?; + + Ok(unsafe { dst.assume_init() }) +} + +#[cfg(not(target_os = "redox"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn fstatat<P: ?Sized + NixPath>( + dirfd: RawFd, + pathname: &P, + f: AtFlags, +) -> Result<FileStat> { + let mut dst = mem::MaybeUninit::uninit(); + let res = pathname.with_nix_path(|cstr| unsafe { + libc::fstatat( + dirfd, + cstr.as_ptr(), + dst.as_mut_ptr(), + f.bits() as libc::c_int, + ) + })?; + + Errno::result(res)?; + + Ok(unsafe { dst.assume_init() }) +} + +/// Change the file permission bits of the file specified by a file descriptor. +/// +/// # References +/// +/// [fchmod(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmod.html). +pub fn fchmod(fd: RawFd, mode: Mode) -> Result<()> { + let res = unsafe { libc::fchmod(fd, mode.bits() as mode_t) }; + + Errno::result(res).map(drop) +} + +/// Flags for `fchmodat` function. +#[derive(Clone, Copy, Debug)] +pub enum FchmodatFlags { + FollowSymlink, + NoFollowSymlink, +} + +/// Change the file permission bits. +/// +/// The file to be changed is determined relative to the directory associated +/// with the file descriptor `dirfd` or the current working directory +/// if `dirfd` is `None`. +/// +/// If `flag` is `FchmodatFlags::NoFollowSymlink` and `path` names a symbolic link, +/// then the mode of the symbolic link is changed. +/// +/// `fchmodat(None, path, mode, FchmodatFlags::FollowSymlink)` is identical to +/// a call `libc::chmod(path, mode)`. That's why `chmod` is unimplemented +/// in the `nix` crate. +/// +/// # References +/// +/// [fchmodat(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmodat.html). +#[cfg(not(target_os = "redox"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn fchmodat<P: ?Sized + NixPath>( + dirfd: Option<RawFd>, + path: &P, + mode: Mode, + flag: FchmodatFlags, +) -> Result<()> { + let atflag = match flag { + FchmodatFlags::FollowSymlink => AtFlags::empty(), + FchmodatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW, + }; + let res = path.with_nix_path(|cstr| unsafe { + libc::fchmodat( + at_rawfd(dirfd), + cstr.as_ptr(), + mode.bits() as mode_t, + atflag.bits() as libc::c_int, + ) + })?; + + Errno::result(res).map(drop) +} + +/// Change the access and modification times of a file. +/// +/// `utimes(path, times)` is identical to +/// `utimensat(None, path, times, UtimensatFlags::FollowSymlink)`. The former +/// is a deprecated API so prefer using the latter if the platforms you care +/// about support it. +/// +/// # References +/// +/// [utimes(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimes.html). +pub fn utimes<P: ?Sized + NixPath>( + path: &P, + atime: &TimeVal, + mtime: &TimeVal, +) -> Result<()> { + let times: [libc::timeval; 2] = [*atime.as_ref(), *mtime.as_ref()]; + let res = path.with_nix_path(|cstr| unsafe { + libc::utimes(cstr.as_ptr(), ×[0]) + })?; + + Errno::result(res).map(drop) +} + +/// Change the access and modification times of a file without following symlinks. +/// +/// `lutimes(path, times)` is identical to +/// `utimensat(None, path, times, UtimensatFlags::NoFollowSymlink)`. The former +/// is a deprecated API so prefer using the latter if the platforms you care +/// about support it. +/// +/// # References +/// +/// [lutimes(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/lutimes.html). +#[cfg(any( + target_os = "linux", + target_os = "haiku", + target_os = "ios", + target_os = "macos", + target_os = "freebsd", + target_os = "netbsd" +))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn lutimes<P: ?Sized + NixPath>( + path: &P, + atime: &TimeVal, + mtime: &TimeVal, +) -> Result<()> { + let times: [libc::timeval; 2] = [*atime.as_ref(), *mtime.as_ref()]; + let res = path.with_nix_path(|cstr| unsafe { + libc::lutimes(cstr.as_ptr(), ×[0]) + })?; + + Errno::result(res).map(drop) +} + +/// Change the access and modification times of the file specified by a file descriptor. +/// +/// # References +/// +/// [futimens(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html). +#[inline] +pub fn futimens(fd: RawFd, atime: &TimeSpec, mtime: &TimeSpec) -> Result<()> { + let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()]; + let res = unsafe { libc::futimens(fd, ×[0]) }; + + Errno::result(res).map(drop) +} + +/// Flags for `utimensat` function. +// TODO: replace with fcntl::AtFlags +#[derive(Clone, Copy, Debug)] +pub enum UtimensatFlags { + FollowSymlink, + NoFollowSymlink, +} + +/// Change the access and modification times of a file. +/// +/// The file to be changed is determined relative to the directory associated +/// with the file descriptor `dirfd` or the current working directory +/// if `dirfd` is `None`. +/// +/// If `flag` is `UtimensatFlags::NoFollowSymlink` and `path` names a symbolic link, +/// then the mode of the symbolic link is changed. +/// +/// `utimensat(None, path, times, UtimensatFlags::FollowSymlink)` is identical to +/// `utimes(path, times)`. The latter is a deprecated API so prefer using the +/// former if the platforms you care about support it. +/// +/// # References +/// +/// [utimensat(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimens.html). +#[cfg(not(target_os = "redox"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn utimensat<P: ?Sized + NixPath>( + dirfd: Option<RawFd>, + path: &P, + atime: &TimeSpec, + mtime: &TimeSpec, + flag: UtimensatFlags, +) -> Result<()> { + let atflag = match flag { + UtimensatFlags::FollowSymlink => AtFlags::empty(), + UtimensatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW, + }; + let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()]; + let res = path.with_nix_path(|cstr| unsafe { + libc::utimensat( + at_rawfd(dirfd), + cstr.as_ptr(), + ×[0], + atflag.bits() as libc::c_int, + ) + })?; + + Errno::result(res).map(drop) +} + +#[cfg(not(target_os = "redox"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn mkdirat<P: ?Sized + NixPath>( + fd: RawFd, + path: &P, + mode: Mode, +) -> Result<()> { + let res = path.with_nix_path(|cstr| unsafe { + libc::mkdirat(fd, cstr.as_ptr(), mode.bits() as mode_t) + })?; + + Errno::result(res).map(drop) +} diff --git a/third_party/rust/nix/src/sys/statfs.rs b/third_party/rust/nix/src/sys/statfs.rs new file mode 100644 index 0000000000..9be8ca6667 --- /dev/null +++ b/third_party/rust/nix/src/sys/statfs.rs @@ -0,0 +1,853 @@ +//! Get filesystem statistics, non-portably +//! +//! See [`statvfs`](crate::sys::statvfs) for a portable alternative. +#[cfg(not(any(target_os = "linux", target_os = "android")))] +use std::ffi::CStr; +use std::fmt::{self, Debug}; +use std::mem; +use std::os::unix::io::AsRawFd; + +use cfg_if::cfg_if; + +#[cfg(all( + feature = "mount", + any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ) +))] +use crate::mount::MntFlags; +#[cfg(target_os = "linux")] +use crate::sys::statvfs::FsFlags; +use crate::{errno::Errno, NixPath, Result}; + +/// Identifies a mounted file system +#[cfg(target_os = "android")] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub type fsid_t = libc::__fsid_t; +/// Identifies a mounted file system +#[cfg(not(target_os = "android"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub type fsid_t = libc::fsid_t; + +cfg_if! { + if #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] { + type type_of_statfs = libc::statfs64; + const LIBC_FSTATFS: unsafe extern fn + (fd: libc::c_int, buf: *mut type_of_statfs) -> libc::c_int + = libc::fstatfs64; + const LIBC_STATFS: unsafe extern fn + (path: *const libc::c_char, buf: *mut type_of_statfs) -> libc::c_int + = libc::statfs64; + } else { + type type_of_statfs = libc::statfs; + const LIBC_FSTATFS: unsafe extern fn + (fd: libc::c_int, buf: *mut type_of_statfs) -> libc::c_int + = libc::fstatfs; + const LIBC_STATFS: unsafe extern fn + (path: *const libc::c_char, buf: *mut type_of_statfs) -> libc::c_int + = libc::statfs; + } +} + +/// Describes a mounted file system +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct Statfs(type_of_statfs); + +#[cfg(target_os = "freebsd")] +type fs_type_t = u32; +#[cfg(target_os = "android")] +type fs_type_t = libc::c_ulong; +#[cfg(all(target_os = "linux", target_arch = "s390x"))] +type fs_type_t = libc::c_uint; +#[cfg(all(target_os = "linux", target_env = "musl"))] +type fs_type_t = libc::c_ulong; +#[cfg(all(target_os = "linux", target_env = "uclibc"))] +type fs_type_t = libc::c_int; +#[cfg(all( + target_os = "linux", + not(any( + target_arch = "s390x", + target_env = "musl", + target_env = "uclibc" + )) +))] +type fs_type_t = libc::__fsword_t; + +/// Describes the file system type as known by the operating system. +#[cfg(any( + target_os = "freebsd", + target_os = "android", + all(target_os = "linux", target_arch = "s390x"), + all(target_os = "linux", target_env = "musl"), + all( + target_os = "linux", + not(any(target_arch = "s390x", target_env = "musl")) + ), +))] +#[derive(Eq, Copy, Clone, PartialEq, Debug)] +pub struct FsType(pub fs_type_t); + +// These constants are defined without documentation in the Linux headers, so we +// can't very well document them here. +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const ADFS_SUPER_MAGIC: FsType = + FsType(libc::ADFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const AFFS_SUPER_MAGIC: FsType = + FsType(libc::AFFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const AFS_SUPER_MAGIC: FsType = FsType(libc::AFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const AUTOFS_SUPER_MAGIC: FsType = + FsType(libc::AUTOFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const BPF_FS_MAGIC: FsType = FsType(libc::BPF_FS_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const BTRFS_SUPER_MAGIC: FsType = + FsType(libc::BTRFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const CGROUP2_SUPER_MAGIC: FsType = + FsType(libc::CGROUP2_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const CGROUP_SUPER_MAGIC: FsType = + FsType(libc::CGROUP_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const CODA_SUPER_MAGIC: FsType = + FsType(libc::CODA_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const CRAMFS_MAGIC: FsType = FsType(libc::CRAMFS_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const DEBUGFS_MAGIC: FsType = FsType(libc::DEBUGFS_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const DEVPTS_SUPER_MAGIC: FsType = + FsType(libc::DEVPTS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const ECRYPTFS_SUPER_MAGIC: FsType = + FsType(libc::ECRYPTFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const EFS_SUPER_MAGIC: FsType = FsType(libc::EFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const EXT2_SUPER_MAGIC: FsType = + FsType(libc::EXT2_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const EXT3_SUPER_MAGIC: FsType = + FsType(libc::EXT3_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const EXT4_SUPER_MAGIC: FsType = + FsType(libc::EXT4_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const F2FS_SUPER_MAGIC: FsType = + FsType(libc::F2FS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const FUSE_SUPER_MAGIC: FsType = + FsType(libc::FUSE_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const FUTEXFS_SUPER_MAGIC: FsType = + FsType(libc::FUTEXFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const HOSTFS_SUPER_MAGIC: FsType = + FsType(libc::HOSTFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const HPFS_SUPER_MAGIC: FsType = + FsType(libc::HPFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const HUGETLBFS_MAGIC: FsType = FsType(libc::HUGETLBFS_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const ISOFS_SUPER_MAGIC: FsType = + FsType(libc::ISOFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const JFFS2_SUPER_MAGIC: FsType = + FsType(libc::JFFS2_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const MINIX2_SUPER_MAGIC2: FsType = + FsType(libc::MINIX2_SUPER_MAGIC2 as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const MINIX2_SUPER_MAGIC: FsType = + FsType(libc::MINIX2_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const MINIX3_SUPER_MAGIC: FsType = + FsType(libc::MINIX3_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const MINIX_SUPER_MAGIC2: FsType = + FsType(libc::MINIX_SUPER_MAGIC2 as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const MINIX_SUPER_MAGIC: FsType = + FsType(libc::MINIX_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const MSDOS_SUPER_MAGIC: FsType = + FsType(libc::MSDOS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const NCP_SUPER_MAGIC: FsType = FsType(libc::NCP_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const NFS_SUPER_MAGIC: FsType = FsType(libc::NFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const NILFS_SUPER_MAGIC: FsType = + FsType(libc::NILFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const OCFS2_SUPER_MAGIC: FsType = + FsType(libc::OCFS2_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const OPENPROM_SUPER_MAGIC: FsType = + FsType(libc::OPENPROM_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const OVERLAYFS_SUPER_MAGIC: FsType = + FsType(libc::OVERLAYFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const PROC_SUPER_MAGIC: FsType = + FsType(libc::PROC_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const QNX4_SUPER_MAGIC: FsType = + FsType(libc::QNX4_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const QNX6_SUPER_MAGIC: FsType = + FsType(libc::QNX6_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const RDTGROUP_SUPER_MAGIC: FsType = + FsType(libc::RDTGROUP_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const REISERFS_SUPER_MAGIC: FsType = + FsType(libc::REISERFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const SECURITYFS_MAGIC: FsType = + FsType(libc::SECURITYFS_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const SELINUX_MAGIC: FsType = FsType(libc::SELINUX_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const SMACK_MAGIC: FsType = FsType(libc::SMACK_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const SMB_SUPER_MAGIC: FsType = FsType(libc::SMB_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const SYSFS_MAGIC: FsType = FsType(libc::SYSFS_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const TMPFS_MAGIC: FsType = FsType(libc::TMPFS_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const TRACEFS_MAGIC: FsType = FsType(libc::TRACEFS_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const UDF_SUPER_MAGIC: FsType = FsType(libc::UDF_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const USBDEVICE_SUPER_MAGIC: FsType = + FsType(libc::USBDEVICE_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const XENFS_SUPER_MAGIC: FsType = + FsType(libc::XENFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const NSFS_MAGIC: FsType = FsType(libc::NSFS_MAGIC as fs_type_t); +#[cfg(all( + any(target_os = "linux", target_os = "android"), + not(target_env = "musl") +))] +#[allow(missing_docs)] +pub const XFS_SUPER_MAGIC: FsType = FsType(libc::XFS_SUPER_MAGIC as fs_type_t); + +impl Statfs { + /// Magic code defining system type + #[cfg(not(any( + target_os = "openbsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos" + )))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn filesystem_type(&self) -> FsType { + FsType(self.0.f_type) + } + + /// Magic code defining system type + #[cfg(not(any(target_os = "linux", target_os = "android")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn filesystem_type_name(&self) -> &str { + let c_str = unsafe { CStr::from_ptr(self.0.f_fstypename.as_ptr()) }; + c_str.to_str().unwrap() + } + + /// Optimal transfer block size + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn optimal_transfer_size(&self) -> i32 { + self.0.f_iosize + } + + /// Optimal transfer block size + #[cfg(target_os = "openbsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn optimal_transfer_size(&self) -> u32 { + self.0.f_iosize + } + + /// Optimal transfer block size + #[cfg(all(target_os = "linux", target_arch = "s390x"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn optimal_transfer_size(&self) -> u32 { + self.0.f_bsize + } + + /// Optimal transfer block size + #[cfg(any( + target_os = "android", + all(target_os = "linux", target_env = "musl") + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn optimal_transfer_size(&self) -> libc::c_ulong { + self.0.f_bsize + } + + /// Optimal transfer block size + #[cfg(all( + target_os = "linux", + not(any( + target_arch = "s390x", + target_env = "musl", + target_env = "uclibc" + )) + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn optimal_transfer_size(&self) -> libc::__fsword_t { + self.0.f_bsize + } + + /// Optimal transfer block size + #[cfg(all(target_os = "linux", target_env = "uclibc"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn optimal_transfer_size(&self) -> libc::c_int { + self.0.f_bsize + } + + /// Optimal transfer block size + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn optimal_transfer_size(&self) -> libc::c_long { + self.0.f_iosize + } + + /// Optimal transfer block size + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn optimal_transfer_size(&self) -> u64 { + self.0.f_iosize + } + + /// Size of a block + #[cfg(any(target_os = "ios", target_os = "macos", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn block_size(&self) -> u32 { + self.0.f_bsize + } + + /// Size of a block + // f_bsize on linux: https://github.com/torvalds/linux/blob/master/fs/nfs/super.c#L471 + #[cfg(all(target_os = "linux", target_arch = "s390x"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn block_size(&self) -> u32 { + self.0.f_bsize + } + + /// Size of a block + // f_bsize on linux: https://github.com/torvalds/linux/blob/master/fs/nfs/super.c#L471 + #[cfg(all(target_os = "linux", target_env = "musl"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn block_size(&self) -> libc::c_ulong { + self.0.f_bsize + } + + /// Size of a block + // f_bsize on linux: https://github.com/torvalds/linux/blob/master/fs/nfs/super.c#L471 + #[cfg(all(target_os = "linux", target_env = "uclibc"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn block_size(&self) -> libc::c_int { + self.0.f_bsize + } + + /// Size of a block + // f_bsize on linux: https://github.com/torvalds/linux/blob/master/fs/nfs/super.c#L471 + #[cfg(all( + target_os = "linux", + not(any( + target_arch = "s390x", + target_env = "musl", + target_env = "uclibc" + )) + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn block_size(&self) -> libc::__fsword_t { + self.0.f_bsize + } + + /// Size of a block + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn block_size(&self) -> u64 { + self.0.f_bsize + } + + /// Size of a block + #[cfg(target_os = "android")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn block_size(&self) -> libc::c_ulong { + self.0.f_bsize + } + + /// Size of a block + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn block_size(&self) -> libc::c_long { + self.0.f_bsize + } + + /// Get the mount flags + #[cfg(all( + feature = "mount", + any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ) + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + #[allow(clippy::unnecessary_cast)] // Not unnecessary on all arches + pub fn flags(&self) -> MntFlags { + MntFlags::from_bits_truncate(self.0.f_flags as i32) + } + + /// Get the mount flags + // The f_flags field exists on Android and Fuchsia too, but without man + // pages I can't tell if it can be cast to FsFlags. + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn flags(&self) -> FsFlags { + FsFlags::from_bits_truncate(self.0.f_flags as libc::c_ulong) + } + + /// Maximum length of filenames + #[cfg(any(target_os = "freebsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn maximum_name_length(&self) -> u32 { + self.0.f_namemax + } + + /// Maximum length of filenames + #[cfg(all(target_os = "linux", target_arch = "s390x"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn maximum_name_length(&self) -> u32 { + self.0.f_namelen + } + + /// Maximum length of filenames + #[cfg(all(target_os = "linux", target_env = "musl"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn maximum_name_length(&self) -> libc::c_ulong { + self.0.f_namelen + } + + /// Maximum length of filenames + #[cfg(all(target_os = "linux", target_env = "uclibc"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn maximum_name_length(&self) -> libc::c_int { + self.0.f_namelen + } + + /// Maximum length of filenames + #[cfg(all( + target_os = "linux", + not(any( + target_arch = "s390x", + target_env = "musl", + target_env = "uclibc" + )) + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn maximum_name_length(&self) -> libc::__fsword_t { + self.0.f_namelen + } + + /// Maximum length of filenames + #[cfg(target_os = "android")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn maximum_name_length(&self) -> libc::c_ulong { + self.0.f_namelen + } + + /// Total data blocks in filesystem + #[cfg(any( + target_os = "ios", + target_os = "macos", + target_os = "android", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "openbsd", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks(&self) -> u64 { + self.0.f_blocks + } + + /// Total data blocks in filesystem + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks(&self) -> libc::c_long { + self.0.f_blocks + } + + /// Total data blocks in filesystem + #[cfg(target_os = "emscripten")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks(&self) -> u32 { + self.0.f_blocks + } + + /// Free blocks in filesystem + #[cfg(any( + target_os = "ios", + target_os = "macos", + target_os = "android", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "openbsd", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks_free(&self) -> u64 { + self.0.f_bfree + } + + /// Free blocks in filesystem + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks_free(&self) -> libc::c_long { + self.0.f_bfree + } + + /// Free blocks in filesystem + #[cfg(target_os = "emscripten")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks_free(&self) -> u32 { + self.0.f_bfree + } + + /// Free blocks available to unprivileged user + #[cfg(any( + target_os = "ios", + target_os = "macos", + target_os = "android", + target_os = "fuchsia", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks_available(&self) -> u64 { + self.0.f_bavail + } + + /// Free blocks available to unprivileged user + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks_available(&self) -> libc::c_long { + self.0.f_bavail + } + + /// Free blocks available to unprivileged user + #[cfg(any(target_os = "freebsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks_available(&self) -> i64 { + self.0.f_bavail + } + + /// Free blocks available to unprivileged user + #[cfg(target_os = "emscripten")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks_available(&self) -> u32 { + self.0.f_bavail + } + + /// Total file nodes in filesystem + #[cfg(any( + target_os = "ios", + target_os = "macos", + target_os = "android", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "openbsd", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn files(&self) -> u64 { + self.0.f_files + } + + /// Total file nodes in filesystem + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn files(&self) -> libc::c_long { + self.0.f_files + } + + /// Total file nodes in filesystem + #[cfg(target_os = "emscripten")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn files(&self) -> u32 { + self.0.f_files + } + + /// Free file nodes in filesystem + #[cfg(any( + target_os = "ios", + target_os = "macos", + target_os = "android", + target_os = "fuchsia", + target_os = "openbsd", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn files_free(&self) -> u64 { + self.0.f_ffree + } + + /// Free file nodes in filesystem + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn files_free(&self) -> libc::c_long { + self.0.f_ffree + } + + /// Free file nodes in filesystem + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn files_free(&self) -> i64 { + self.0.f_ffree + } + + /// Free file nodes in filesystem + #[cfg(target_os = "emscripten")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn files_free(&self) -> u32 { + self.0.f_ffree + } + + /// Filesystem ID + pub fn filesystem_id(&self) -> fsid_t { + self.0.f_fsid + } +} + +impl Debug for Statfs { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut ds = f.debug_struct("Statfs"); + ds.field("optimal_transfer_size", &self.optimal_transfer_size()); + ds.field("block_size", &self.block_size()); + ds.field("blocks", &self.blocks()); + ds.field("blocks_free", &self.blocks_free()); + ds.field("blocks_available", &self.blocks_available()); + ds.field("files", &self.files()); + ds.field("files_free", &self.files_free()); + ds.field("filesystem_id", &self.filesystem_id()); + #[cfg(all( + feature = "mount", + any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ) + ))] + ds.field("flags", &self.flags()); + ds.finish() + } +} + +/// Describes a mounted file system. +/// +/// The result is OS-dependent. For a portable alternative, see +/// [`statvfs`](crate::sys::statvfs::statvfs). +/// +/// # Arguments +/// +/// `path` - Path to any file within the file system to describe +pub fn statfs<P: ?Sized + NixPath>(path: &P) -> Result<Statfs> { + unsafe { + let mut stat = mem::MaybeUninit::<type_of_statfs>::uninit(); + let res = path.with_nix_path(|path| { + LIBC_STATFS(path.as_ptr(), stat.as_mut_ptr()) + })?; + Errno::result(res).map(|_| Statfs(stat.assume_init())) + } +} + +/// Describes a mounted file system. +/// +/// The result is OS-dependent. For a portable alternative, see +/// [`fstatvfs`](crate::sys::statvfs::fstatvfs). +/// +/// # Arguments +/// +/// `fd` - File descriptor of any open file within the file system to describe +pub fn fstatfs<T: AsRawFd>(fd: &T) -> Result<Statfs> { + unsafe { + let mut stat = mem::MaybeUninit::<type_of_statfs>::uninit(); + Errno::result(LIBC_FSTATFS(fd.as_raw_fd(), stat.as_mut_ptr())) + .map(|_| Statfs(stat.assume_init())) + } +} + +#[cfg(test)] +mod test { + use std::fs::File; + + use crate::sys::statfs::*; + use crate::sys::statvfs::*; + use std::path::Path; + + #[test] + fn statfs_call() { + check_statfs("/tmp"); + check_statfs("/dev"); + check_statfs("/run"); + check_statfs("/"); + } + + #[test] + fn fstatfs_call() { + check_fstatfs("/tmp"); + check_fstatfs("/dev"); + check_fstatfs("/run"); + check_fstatfs("/"); + } + + fn check_fstatfs(path: &str) { + if !Path::new(path).exists() { + return; + } + let vfs = statvfs(path.as_bytes()).unwrap(); + let file = File::open(path).unwrap(); + let fs = fstatfs(&file).unwrap(); + assert_fs_equals(fs, vfs); + } + + fn check_statfs(path: &str) { + if !Path::new(path).exists() { + return; + } + let vfs = statvfs(path.as_bytes()).unwrap(); + let fs = statfs(path.as_bytes()).unwrap(); + assert_fs_equals(fs, vfs); + } + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + fn assert_fs_equals(fs: Statfs, vfs: Statvfs) { + assert_eq!(fs.files() as u64, vfs.files() as u64); + assert_eq!(fs.blocks() as u64, vfs.blocks() as u64); + assert_eq!(fs.block_size() as u64, vfs.fragment_size() as u64); + } + + // This test is ignored because files_free/blocks_free can change after statvfs call and before + // statfs call. + #[test] + #[ignore] + fn statfs_call_strict() { + check_statfs_strict("/tmp"); + check_statfs_strict("/dev"); + check_statfs_strict("/run"); + check_statfs_strict("/"); + } + + // This test is ignored because files_free/blocks_free can change after statvfs call and before + // fstatfs call. + #[test] + #[ignore] + fn fstatfs_call_strict() { + check_fstatfs_strict("/tmp"); + check_fstatfs_strict("/dev"); + check_fstatfs_strict("/run"); + check_fstatfs_strict("/"); + } + + fn check_fstatfs_strict(path: &str) { + if !Path::new(path).exists() { + return; + } + let vfs = statvfs(path.as_bytes()); + let file = File::open(path).unwrap(); + let fs = fstatfs(&file); + assert_fs_equals_strict(fs.unwrap(), vfs.unwrap()) + } + + fn check_statfs_strict(path: &str) { + if !Path::new(path).exists() { + return; + } + let vfs = statvfs(path.as_bytes()); + let fs = statfs(path.as_bytes()); + assert_fs_equals_strict(fs.unwrap(), vfs.unwrap()) + } + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + fn assert_fs_equals_strict(fs: Statfs, vfs: Statvfs) { + assert_eq!(fs.files_free() as u64, vfs.files_free() as u64); + assert_eq!(fs.blocks_free() as u64, vfs.blocks_free() as u64); + assert_eq!(fs.blocks_available() as u64, vfs.blocks_available() as u64); + assert_eq!(fs.files() as u64, vfs.files() as u64); + assert_eq!(fs.blocks() as u64, vfs.blocks() as u64); + assert_eq!(fs.block_size() as u64, vfs.fragment_size() as u64); + } +} diff --git a/third_party/rust/nix/src/sys/statvfs.rs b/third_party/rust/nix/src/sys/statvfs.rs new file mode 100644 index 0000000000..8de369f421 --- /dev/null +++ b/third_party/rust/nix/src/sys/statvfs.rs @@ -0,0 +1,173 @@ +//! Get filesystem statistics +//! +//! See [the man pages](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatvfs.html) +//! for more details. +use std::mem; +use std::os::unix::io::AsRawFd; + +use libc::{self, c_ulong}; + +use crate::{errno::Errno, NixPath, Result}; + +#[cfg(not(target_os = "redox"))] +libc_bitflags!( + /// File system mount Flags + #[repr(C)] + #[derive(Default)] + pub struct FsFlags: c_ulong { + /// Read Only + #[cfg(not(target_os = "haiku"))] + ST_RDONLY; + /// Do not allow the set-uid bits to have an effect + #[cfg(not(target_os = "haiku"))] + ST_NOSUID; + /// Do not interpret character or block-special devices + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_NODEV; + /// Do not allow execution of binaries on the filesystem + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_NOEXEC; + /// All IO should be done synchronously + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_SYNCHRONOUS; + /// Allow mandatory locks on the filesystem + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_MANDLOCK; + /// Write on file/directory/symlink + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_WRITE; + /// Append-only file + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_APPEND; + /// Immutable file + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_IMMUTABLE; + /// Do not update access times on files + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_NOATIME; + /// Do not update access times on files + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_NODIRATIME; + /// Update access time relative to modify/change time + #[cfg(any(target_os = "android", all(target_os = "linux", not(target_env = "musl"))))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_RELATIME; + } +); + +/// Wrapper around the POSIX `statvfs` struct +/// +/// For more information see the [`statvfs(3)` man pages](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_statvfs.h.html). +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct Statvfs(libc::statvfs); + +impl Statvfs { + /// get the file system block size + pub fn block_size(&self) -> c_ulong { + self.0.f_bsize + } + + /// Get the fundamental file system block size + pub fn fragment_size(&self) -> c_ulong { + self.0.f_frsize + } + + /// Get the number of blocks. + /// + /// Units are in units of `fragment_size()` + pub fn blocks(&self) -> libc::fsblkcnt_t { + self.0.f_blocks + } + + /// Get the number of free blocks in the file system + pub fn blocks_free(&self) -> libc::fsblkcnt_t { + self.0.f_bfree + } + + /// Get the number of free blocks for unprivileged users + pub fn blocks_available(&self) -> libc::fsblkcnt_t { + self.0.f_bavail + } + + /// Get the total number of file inodes + pub fn files(&self) -> libc::fsfilcnt_t { + self.0.f_files + } + + /// Get the number of free file inodes + pub fn files_free(&self) -> libc::fsfilcnt_t { + self.0.f_ffree + } + + /// Get the number of free file inodes for unprivileged users + pub fn files_available(&self) -> libc::fsfilcnt_t { + self.0.f_favail + } + + /// Get the file system id + pub fn filesystem_id(&self) -> c_ulong { + self.0.f_fsid + } + + /// Get the mount flags + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn flags(&self) -> FsFlags { + FsFlags::from_bits_truncate(self.0.f_flag) + } + + /// Get the maximum filename length + pub fn name_max(&self) -> c_ulong { + self.0.f_namemax + } +} + +/// Return a `Statvfs` object with information about the `path` +pub fn statvfs<P: ?Sized + NixPath>(path: &P) -> Result<Statvfs> { + unsafe { + Errno::clear(); + let mut stat = mem::MaybeUninit::<libc::statvfs>::uninit(); + let res = path.with_nix_path(|path| { + libc::statvfs(path.as_ptr(), stat.as_mut_ptr()) + })?; + + Errno::result(res).map(|_| Statvfs(stat.assume_init())) + } +} + +/// Return a `Statvfs` object with information about `fd` +pub fn fstatvfs<T: AsRawFd>(fd: &T) -> Result<Statvfs> { + unsafe { + Errno::clear(); + let mut stat = mem::MaybeUninit::<libc::statvfs>::uninit(); + Errno::result(libc::fstatvfs(fd.as_raw_fd(), stat.as_mut_ptr())) + .map(|_| Statvfs(stat.assume_init())) + } +} + +#[cfg(test)] +mod test { + use crate::sys::statvfs::*; + use std::fs::File; + + #[test] + fn statvfs_call() { + statvfs(&b"/"[..]).unwrap(); + } + + #[test] + fn fstatvfs_call() { + let root = File::open("/").unwrap(); + fstatvfs(&root).unwrap(); + } +} diff --git a/third_party/rust/nix/src/sys/sysinfo.rs b/third_party/rust/nix/src/sys/sysinfo.rs new file mode 100644 index 0000000000..e8aa00b00d --- /dev/null +++ b/third_party/rust/nix/src/sys/sysinfo.rs @@ -0,0 +1,83 @@ +use libc::{self, SI_LOAD_SHIFT}; +use std::time::Duration; +use std::{cmp, mem}; + +use crate::errno::Errno; +use crate::Result; + +/// System info structure returned by `sysinfo`. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +#[repr(transparent)] +pub struct SysInfo(libc::sysinfo); + +// The fields are c_ulong on 32-bit linux, u64 on 64-bit linux; x32's ulong is u32 +#[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))] +type mem_blocks_t = u64; +#[cfg(not(all(target_arch = "x86_64", target_pointer_width = "32")))] +type mem_blocks_t = libc::c_ulong; + +impl SysInfo { + /// Returns the load average tuple. + /// + /// The returned values represent the load average over time intervals of + /// 1, 5, and 15 minutes, respectively. + pub fn load_average(&self) -> (f64, f64, f64) { + ( + self.0.loads[0] as f64 / (1 << SI_LOAD_SHIFT) as f64, + self.0.loads[1] as f64 / (1 << SI_LOAD_SHIFT) as f64, + self.0.loads[2] as f64 / (1 << SI_LOAD_SHIFT) as f64, + ) + } + + /// Returns the time since system boot. + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + pub fn uptime(&self) -> Duration { + // Truncate negative values to 0 + Duration::from_secs(cmp::max(self.0.uptime, 0) as u64) + } + + /// Current number of processes. + pub fn process_count(&self) -> u16 { + self.0.procs + } + + /// Returns the amount of swap memory in Bytes. + pub fn swap_total(&self) -> u64 { + self.scale_mem(self.0.totalswap) + } + + /// Returns the amount of unused swap memory in Bytes. + pub fn swap_free(&self) -> u64 { + self.scale_mem(self.0.freeswap) + } + + /// Returns the total amount of installed RAM in Bytes. + pub fn ram_total(&self) -> u64 { + self.scale_mem(self.0.totalram) + } + + /// Returns the amount of completely unused RAM in Bytes. + /// + /// "Unused" in this context means that the RAM in neither actively used by + /// programs, nor by the operating system as disk cache or buffer. It is + /// "wasted" RAM since it currently serves no purpose. + pub fn ram_unused(&self) -> u64 { + self.scale_mem(self.0.freeram) + } + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + fn scale_mem(&self, units: mem_blocks_t) -> u64 { + units as u64 * self.0.mem_unit as u64 + } +} + +/// Returns system information. +/// +/// [See `sysinfo(2)`](https://man7.org/linux/man-pages/man2/sysinfo.2.html). +pub fn sysinfo() -> Result<SysInfo> { + let mut info = mem::MaybeUninit::uninit(); + let res = unsafe { libc::sysinfo(info.as_mut_ptr()) }; + Errno::result(res).map(|_| unsafe { SysInfo(info.assume_init()) }) +} diff --git a/third_party/rust/nix/src/sys/termios.rs b/third_party/rust/nix/src/sys/termios.rs new file mode 100644 index 0000000000..fba2cd8268 --- /dev/null +++ b/third_party/rust/nix/src/sys/termios.rs @@ -0,0 +1,1227 @@ +//! An interface for controlling asynchronous communication ports +//! +//! This interface provides a safe wrapper around the termios subsystem defined by POSIX. The +//! underlying types are all implemented in libc for most platforms and either wrapped in safer +//! types here or exported directly. +//! +//! If you are unfamiliar with the `termios` API, you should first read the +//! [API documentation](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/termios.h.html) and +//! then come back to understand how `nix` safely wraps it. +//! +//! It should be noted that this API incurs some runtime overhead above the base `libc` definitions. +//! As this interface is not used with high-bandwidth information, this should be fine in most +//! cases. The primary cost when using this API is that the `Termios` datatype here duplicates the +//! standard fields of the underlying `termios` struct and uses safe type wrappers for those fields. +//! This means that when crossing the FFI interface to the underlying C library, data is first +//! copied into the underlying `termios` struct, then the operation is done, and the data is copied +//! back (with additional sanity checking) into the safe wrapper types. The `termios` struct is +//! relatively small across all platforms (on the order of 32-64 bytes). +//! +//! The following examples highlight some of the API use cases such that users coming from using C +//! or reading the standard documentation will understand how to use the safe API exposed here. +//! +//! Example disabling processing of the end-of-file control character: +//! +//! ``` +//! # use self::nix::sys::termios::SpecialCharacterIndices::VEOF; +//! # use self::nix::sys::termios::{_POSIX_VDISABLE, Termios}; +//! # let mut termios: Termios = unsafe { std::mem::zeroed() }; +//! termios.control_chars[VEOF as usize] = _POSIX_VDISABLE; +//! ``` +//! +//! The flags within `Termios` are defined as bitfields using the `bitflags` crate. This provides +//! an interface for working with bitfields that is similar to working with the raw unsigned +//! integer types but offers type safety because of the internal checking that values will always +//! be a valid combination of the defined flags. +//! +//! An example showing some of the basic operations for interacting with the control flags: +//! +//! ``` +//! # use self::nix::sys::termios::{ControlFlags, Termios}; +//! # let mut termios: Termios = unsafe { std::mem::zeroed() }; +//! termios.control_flags & ControlFlags::CSIZE == ControlFlags::CS5; +//! termios.control_flags |= ControlFlags::CS5; +//! ``` +//! +//! # Baud rates +//! +//! This API is not consistent across platforms when it comes to `BaudRate`: Android and Linux both +//! only support the rates specified by the `BaudRate` enum through their termios API while the BSDs +//! support arbitrary baud rates as the values of the `BaudRate` enum constants are the same integer +//! value of the constant (`B9600` == `9600`). Therefore the `nix::termios` API uses the following +//! conventions: +//! +//! * `cfgetispeed()` - Returns `u32` on BSDs, `BaudRate` on Android/Linux +//! * `cfgetospeed()` - Returns `u32` on BSDs, `BaudRate` on Android/Linux +//! * `cfsetispeed()` - Takes `u32` or `BaudRate` on BSDs, `BaudRate` on Android/Linux +//! * `cfsetospeed()` - Takes `u32` or `BaudRate` on BSDs, `BaudRate` on Android/Linux +//! * `cfsetspeed()` - Takes `u32` or `BaudRate` on BSDs, `BaudRate` on Android/Linux +//! +//! The most common use case of specifying a baud rate using the enum will work the same across +//! platforms: +//! +//! ```rust +//! # use nix::sys::termios::{BaudRate, cfsetispeed, cfsetospeed, cfsetspeed, Termios}; +//! # fn main() { +//! # let mut t: Termios = unsafe { std::mem::zeroed() }; +//! cfsetispeed(&mut t, BaudRate::B9600).unwrap(); +//! cfsetospeed(&mut t, BaudRate::B9600).unwrap(); +//! cfsetspeed(&mut t, BaudRate::B9600).unwrap(); +//! # } +//! ``` +//! +//! Additionally round-tripping baud rates is consistent across platforms: +//! +//! ```rust +//! # use nix::sys::termios::{BaudRate, cfgetispeed, cfgetospeed, cfsetispeed, cfsetspeed, Termios}; +//! # fn main() { +//! # let mut t: Termios = unsafe { std::mem::zeroed() }; +//! # cfsetspeed(&mut t, BaudRate::B9600).unwrap(); +//! let speed = cfgetispeed(&t); +//! assert_eq!(speed, cfgetospeed(&t)); +//! cfsetispeed(&mut t, speed).unwrap(); +//! # } +//! ``` +//! +//! On non-BSDs, `cfgetispeed()` and `cfgetospeed()` both return a `BaudRate`: +//! +#![cfg_attr( + any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ), + doc = " ```rust,ignore" +)] +#![cfg_attr( + not(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + )), + doc = " ```rust" +)] +//! # use nix::sys::termios::{BaudRate, cfgetispeed, cfgetospeed, cfsetspeed, Termios}; +//! # fn main() { +//! # let mut t: Termios = unsafe { std::mem::zeroed() }; +//! # cfsetspeed(&mut t, BaudRate::B9600); +//! assert_eq!(cfgetispeed(&t), BaudRate::B9600); +//! assert_eq!(cfgetospeed(&t), BaudRate::B9600); +//! # } +//! ``` +//! +//! But on the BSDs, `cfgetispeed()` and `cfgetospeed()` both return `u32`s: +//! +#![cfg_attr( + any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ), + doc = " ```rust" +)] +#![cfg_attr( + not(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + )), + doc = " ```rust,ignore" +)] +//! # use nix::sys::termios::{BaudRate, cfgetispeed, cfgetospeed, cfsetspeed, Termios}; +//! # fn main() { +//! # let mut t: Termios = unsafe { std::mem::zeroed() }; +//! # cfsetspeed(&mut t, 9600u32); +//! assert_eq!(cfgetispeed(&t), 9600u32); +//! assert_eq!(cfgetospeed(&t), 9600u32); +//! # } +//! ``` +//! +//! It's trivial to convert from a `BaudRate` to a `u32` on BSDs: +//! +#![cfg_attr( + any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ), + doc = " ```rust" +)] +#![cfg_attr( + not(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + )), + doc = " ```rust,ignore" +)] +//! # use nix::sys::termios::{BaudRate, cfgetispeed, cfsetspeed, Termios}; +//! # fn main() { +//! # let mut t: Termios = unsafe { std::mem::zeroed() }; +//! # cfsetspeed(&mut t, 9600u32); +//! assert_eq!(cfgetispeed(&t), BaudRate::B9600.into()); +//! assert_eq!(u32::from(BaudRate::B9600), 9600u32); +//! # } +//! ``` +//! +//! And on BSDs you can specify arbitrary baud rates (**note** this depends on hardware support) +//! by specifying baud rates directly using `u32`s: +//! +#![cfg_attr( + any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ), + doc = " ```rust" +)] +#![cfg_attr( + not(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + )), + doc = " ```rust,ignore" +)] +//! # use nix::sys::termios::{cfsetispeed, cfsetospeed, cfsetspeed, Termios}; +//! # fn main() { +//! # let mut t: Termios = unsafe { std::mem::zeroed() }; +//! cfsetispeed(&mut t, 9600u32); +//! cfsetospeed(&mut t, 9600u32); +//! cfsetspeed(&mut t, 9600u32); +//! # } +//! ``` +use crate::errno::Errno; +use crate::Result; +use cfg_if::cfg_if; +use libc::{self, c_int, tcflag_t}; +use std::cell::{Ref, RefCell}; +use std::convert::From; +use std::mem; +use std::os::unix::io::RawFd; + +#[cfg(feature = "process")] +use crate::unistd::Pid; + +/// Stores settings for the termios API +/// +/// This is a wrapper around the `libc::termios` struct that provides a safe interface for the +/// standard fields. The only safe way to obtain an instance of this struct is to extract it from +/// an open port using `tcgetattr()`. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Termios { + inner: RefCell<libc::termios>, + /// Input mode flags (see `termios.c_iflag` documentation) + pub input_flags: InputFlags, + /// Output mode flags (see `termios.c_oflag` documentation) + pub output_flags: OutputFlags, + /// Control mode flags (see `termios.c_cflag` documentation) + pub control_flags: ControlFlags, + /// Local mode flags (see `termios.c_lflag` documentation) + pub local_flags: LocalFlags, + /// Control characters (see `termios.c_cc` documentation) + pub control_chars: [libc::cc_t; NCCS], + /// Line discipline (see `termios.c_line` documentation) + #[cfg(any(target_os = "linux", target_os = "android",))] + pub line_discipline: libc::cc_t, + /// Line discipline (see `termios.c_line` documentation) + #[cfg(target_os = "haiku")] + pub line_discipline: libc::c_char, +} + +impl Termios { + /// Exposes an immutable reference to the underlying `libc::termios` data structure. + /// + /// This is not part of `nix`'s public API because it requires additional work to maintain type + /// safety. + pub(crate) fn get_libc_termios(&self) -> Ref<libc::termios> { + { + let mut termios = self.inner.borrow_mut(); + termios.c_iflag = self.input_flags.bits(); + termios.c_oflag = self.output_flags.bits(); + termios.c_cflag = self.control_flags.bits(); + termios.c_lflag = self.local_flags.bits(); + termios.c_cc = self.control_chars; + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "haiku", + ))] + { + termios.c_line = self.line_discipline; + } + } + self.inner.borrow() + } + + /// Exposes the inner `libc::termios` datastore within `Termios`. + /// + /// This is unsafe because if this is used to modify the inner `libc::termios` struct, it will + /// not automatically update the safe wrapper type around it. In this case it should also be + /// paired with a call to `update_wrapper()` so that the wrapper-type and internal + /// representation stay consistent. + pub(crate) unsafe fn get_libc_termios_mut(&mut self) -> *mut libc::termios { + { + let mut termios = self.inner.borrow_mut(); + termios.c_iflag = self.input_flags.bits(); + termios.c_oflag = self.output_flags.bits(); + termios.c_cflag = self.control_flags.bits(); + termios.c_lflag = self.local_flags.bits(); + termios.c_cc = self.control_chars; + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "haiku", + ))] + { + termios.c_line = self.line_discipline; + } + } + self.inner.as_ptr() + } + + /// Updates the wrapper values from the internal `libc::termios` data structure. + pub(crate) fn update_wrapper(&mut self) { + let termios = *self.inner.borrow_mut(); + self.input_flags = InputFlags::from_bits_truncate(termios.c_iflag); + self.output_flags = OutputFlags::from_bits_truncate(termios.c_oflag); + self.control_flags = ControlFlags::from_bits_truncate(termios.c_cflag); + self.local_flags = LocalFlags::from_bits_truncate(termios.c_lflag); + self.control_chars = termios.c_cc; + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "haiku", + ))] + { + self.line_discipline = termios.c_line; + } + } +} + +impl From<libc::termios> for Termios { + fn from(termios: libc::termios) -> Self { + Termios { + inner: RefCell::new(termios), + input_flags: InputFlags::from_bits_truncate(termios.c_iflag), + output_flags: OutputFlags::from_bits_truncate(termios.c_oflag), + control_flags: ControlFlags::from_bits_truncate(termios.c_cflag), + local_flags: LocalFlags::from_bits_truncate(termios.c_lflag), + control_chars: termios.c_cc, + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "haiku", + ))] + line_discipline: termios.c_line, + } + } +} + +impl From<Termios> for libc::termios { + fn from(termios: Termios) -> Self { + termios.inner.into_inner() + } +} + +libc_enum! { + /// Baud rates supported by the system. + /// + /// For the BSDs, arbitrary baud rates can be specified by using `u32`s directly instead of this + /// enum. + /// + /// B0 is special and will disable the port. + #[cfg_attr(all(any(target_os = "haiku"), target_pointer_width = "64"), repr(u8))] + #[cfg_attr(all(any(target_os = "ios", target_os = "macos"), target_pointer_width = "64"), repr(u64))] + #[cfg_attr(not(all(any(target_os = "ios", target_os = "macos", target_os = "haiku"), target_pointer_width = "64")), repr(u32))] + #[non_exhaustive] + pub enum BaudRate { + B0, + B50, + B75, + B110, + B134, + B150, + B200, + B300, + B600, + B1200, + B1800, + B2400, + B4800, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B7200, + B9600, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B14400, + B19200, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B28800, + B38400, + B57600, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B76800, + B115200, + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B153600, + B230400, + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B307200, + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B460800, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B500000, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B576000, + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B921600, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B1000000, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B1152000, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B1500000, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B2000000, + #[cfg(any(target_os = "android", all(target_os = "linux", not(target_arch = "sparc64"))))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B2500000, + #[cfg(any(target_os = "android", all(target_os = "linux", not(target_arch = "sparc64"))))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B3000000, + #[cfg(any(target_os = "android", all(target_os = "linux", not(target_arch = "sparc64"))))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B3500000, + #[cfg(any(target_os = "android", all(target_os = "linux", not(target_arch = "sparc64"))))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B4000000, + } + impl TryFrom<libc::speed_t> +} + +#[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" +))] +impl From<BaudRate> for u32 { + fn from(b: BaudRate) -> u32 { + b as u32 + } +} + +#[cfg(target_os = "haiku")] +impl From<BaudRate> for u8 { + fn from(b: BaudRate) -> u8 { + b as u8 + } +} + +// TODO: Add TCSASOFT, which will require treating this as a bitfield. +libc_enum! { + /// Specify when a port configuration change should occur. + /// + /// Used as an argument to `tcsetattr()` + #[repr(i32)] + #[non_exhaustive] + pub enum SetArg { + /// The change will occur immediately + TCSANOW, + /// The change occurs after all output has been written + TCSADRAIN, + /// Same as `TCSADRAIN`, but will also flush the input buffer + TCSAFLUSH, + } +} + +libc_enum! { + /// Specify a combination of the input and output buffers to flush + /// + /// Used as an argument to `tcflush()`. + #[repr(i32)] + #[non_exhaustive] + pub enum FlushArg { + /// Flush data that was received but not read + TCIFLUSH, + /// Flush data written but not transmitted + TCOFLUSH, + /// Flush both received data not read and written data not transmitted + TCIOFLUSH, + } +} + +libc_enum! { + /// Specify how transmission flow should be altered + /// + /// Used as an argument to `tcflow()`. + #[repr(i32)] + #[non_exhaustive] + pub enum FlowArg { + /// Suspend transmission + TCOOFF, + /// Resume transmission + TCOON, + /// Transmit a STOP character, which should disable a connected terminal device + TCIOFF, + /// Transmit a START character, which should re-enable a connected terminal device + TCION, + } +} + +// TODO: Make this usable directly as a slice index. +#[cfg(not(target_os = "haiku"))] +libc_enum! { + /// Indices into the `termios.c_cc` array for special characters. + #[repr(usize)] + #[non_exhaustive] + pub enum SpecialCharacterIndices { + VDISCARD, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + VDSUSP, + VEOF, + VEOL, + VEOL2, + VERASE, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + VERASE2, + VINTR, + VKILL, + VLNEXT, + #[cfg(not(any(all(target_os = "linux", target_arch = "sparc64"), + target_os = "illumos", target_os = "solaris")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + VMIN, + VQUIT, + VREPRINT, + VSTART, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + VSTATUS, + VSTOP, + VSUSP, + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + VSWTC, + #[cfg(any(target_os = "haiku", target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + VSWTCH, + #[cfg(not(any(all(target_os = "linux", target_arch = "sparc64"), + target_os = "illumos", target_os = "solaris")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + VTIME, + VWERASE, + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + VCHECKPT, + } +} + +#[cfg(any( + all(target_os = "linux", target_arch = "sparc64"), + target_os = "illumos", + target_os = "solaris" +))] +impl SpecialCharacterIndices { + pub const VMIN: SpecialCharacterIndices = SpecialCharacterIndices::VEOF; + pub const VTIME: SpecialCharacterIndices = SpecialCharacterIndices::VEOL; +} + +pub use libc::NCCS; +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" +))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub use libc::_POSIX_VDISABLE; + +libc_bitflags! { + /// Flags for configuring the input mode of a terminal + pub struct InputFlags: tcflag_t { + IGNBRK; + BRKINT; + IGNPAR; + PARMRK; + INPCK; + ISTRIP; + INLCR; + IGNCR; + ICRNL; + IXON; + IXOFF; + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IXANY; + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IMAXBEL; + #[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IUTF8; + } +} + +libc_bitflags! { + /// Flags for configuring the output mode of a terminal + pub struct OutputFlags: tcflag_t { + OPOST; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "linux", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + OLCUC; + ONLCR; + OCRNL as tcflag_t; + ONOCR as tcflag_t; + ONLRET as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + OFILL as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + OFDEL as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NL0 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NL1 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CR0 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CR1 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CR2 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CR3 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + TAB0 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + TAB1 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + TAB2 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + TAB3 as tcflag_t; + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + XTABS; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + BS0 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + BS1 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + VT0 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + VT1 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + FF0 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + FF1 as tcflag_t; + #[cfg(any(target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + OXTABS; + #[cfg(any(target_os = "freebsd", + target_os = "dragonfly", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ONOEOT as tcflag_t; + + // Bitmasks for use with OutputFlags to select specific settings + // These should be moved to be a mask once https://github.com/rust-lang-nursery/bitflags/issues/110 + // is resolved. + + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NLDLY as tcflag_t; // FIXME: Datatype needs to be corrected in libc for mac + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CRDLY as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + TABDLY as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + BSDLY as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + VTDLY as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + FFDLY as tcflag_t; + } +} + +libc_bitflags! { + /// Flags for setting the control mode of a terminal + pub struct ControlFlags: tcflag_t { + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CIGNORE; + CS5; + CS6; + CS7; + CS8; + CSTOPB; + CREAD; + PARENB; + PARODD; + HUPCL; + CLOCAL; + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CRTSCTS; + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CBAUD; + #[cfg(any(target_os = "android", all(target_os = "linux", not(target_arch = "mips"))))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CMSPAR; + #[cfg(any(target_os = "android", + all(target_os = "linux", + not(any(target_arch = "powerpc", target_arch = "powerpc64")))))] + CIBAUD; + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CBAUDEX; + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MDMBUF; + #[cfg(any(target_os = "netbsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CHWFLOW; + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CCTS_OFLOW; + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CRTS_IFLOW; + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CDTR_IFLOW; + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CDSR_OFLOW; + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CCAR_OFLOW; + + // Bitmasks for use with ControlFlags to select specific settings + // These should be moved to be a mask once https://github.com/rust-lang-nursery/bitflags/issues/110 + // is resolved. + + CSIZE; + } +} + +libc_bitflags! { + /// Flags for setting any local modes + pub struct LocalFlags: tcflag_t { + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ECHOKE; + ECHOE; + ECHOK; + ECHO; + ECHONL; + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ECHOPRT; + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ECHOCTL; + ISIG; + ICANON; + #[cfg(any(target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ALTWERASE; + IEXTEN; + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + EXTPROC; + TOSTOP; + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + FLUSHO; + #[cfg(any(target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NOKERNINFO; + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + PENDIN; + NOFLSH; + } +} + +cfg_if! { + if #[cfg(any(target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] { + /// Get input baud rate (see + /// [cfgetispeed(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfgetispeed.html)). + /// + /// `cfgetispeed()` extracts the input baud rate from the given `Termios` structure. + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + pub fn cfgetispeed(termios: &Termios) -> u32 { + let inner_termios = termios.get_libc_termios(); + unsafe { libc::cfgetispeed(&*inner_termios) as u32 } + } + + /// Get output baud rate (see + /// [cfgetospeed(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfgetospeed.html)). + /// + /// `cfgetospeed()` extracts the output baud rate from the given `Termios` structure. + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + pub fn cfgetospeed(termios: &Termios) -> u32 { + let inner_termios = termios.get_libc_termios(); + unsafe { libc::cfgetospeed(&*inner_termios) as u32 } + } + + /// Set input baud rate (see + /// [cfsetispeed(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfsetispeed.html)). + /// + /// `cfsetispeed()` sets the intput baud rate in the given `Termios` structure. + pub fn cfsetispeed<T: Into<u32>>(termios: &mut Termios, baud: T) -> Result<()> { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + let res = unsafe { libc::cfsetispeed(inner_termios, baud.into() as libc::speed_t) }; + termios.update_wrapper(); + Errno::result(res).map(drop) + } + + /// Set output baud rate (see + /// [cfsetospeed(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfsetospeed.html)). + /// + /// `cfsetospeed()` sets the output baud rate in the given termios structure. + pub fn cfsetospeed<T: Into<u32>>(termios: &mut Termios, baud: T) -> Result<()> { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + let res = unsafe { libc::cfsetospeed(inner_termios, baud.into() as libc::speed_t) }; + termios.update_wrapper(); + Errno::result(res).map(drop) + } + + /// Set both the input and output baud rates (see + /// [termios(3)](https://www.freebsd.org/cgi/man.cgi?query=cfsetspeed)). + /// + /// `cfsetspeed()` sets the input and output baud rate in the given termios structure. Note that + /// this is part of the 4.4BSD standard and not part of POSIX. + pub fn cfsetspeed<T: Into<u32>>(termios: &mut Termios, baud: T) -> Result<()> { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + let res = unsafe { libc::cfsetspeed(inner_termios, baud.into() as libc::speed_t) }; + termios.update_wrapper(); + Errno::result(res).map(drop) + } + } else { + use std::convert::TryInto; + + /// Get input baud rate (see + /// [cfgetispeed(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfgetispeed.html)). + /// + /// `cfgetispeed()` extracts the input baud rate from the given `Termios` structure. + pub fn cfgetispeed(termios: &Termios) -> BaudRate { + let inner_termios = termios.get_libc_termios(); + unsafe { libc::cfgetispeed(&*inner_termios) }.try_into().unwrap() + } + + /// Get output baud rate (see + /// [cfgetospeed(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfgetospeed.html)). + /// + /// `cfgetospeed()` extracts the output baud rate from the given `Termios` structure. + pub fn cfgetospeed(termios: &Termios) -> BaudRate { + let inner_termios = termios.get_libc_termios(); + unsafe { libc::cfgetospeed(&*inner_termios) }.try_into().unwrap() + } + + /// Set input baud rate (see + /// [cfsetispeed(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfsetispeed.html)). + /// + /// `cfsetispeed()` sets the intput baud rate in the given `Termios` structure. + pub fn cfsetispeed(termios: &mut Termios, baud: BaudRate) -> Result<()> { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + let res = unsafe { libc::cfsetispeed(inner_termios, baud as libc::speed_t) }; + termios.update_wrapper(); + Errno::result(res).map(drop) + } + + /// Set output baud rate (see + /// [cfsetospeed(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfsetospeed.html)). + /// + /// `cfsetospeed()` sets the output baud rate in the given `Termios` structure. + pub fn cfsetospeed(termios: &mut Termios, baud: BaudRate) -> Result<()> { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + let res = unsafe { libc::cfsetospeed(inner_termios, baud as libc::speed_t) }; + termios.update_wrapper(); + Errno::result(res).map(drop) + } + + /// Set both the input and output baud rates (see + /// [termios(3)](https://www.freebsd.org/cgi/man.cgi?query=cfsetspeed)). + /// + /// `cfsetspeed()` sets the input and output baud rate in the given `Termios` structure. Note that + /// this is part of the 4.4BSD standard and not part of POSIX. + #[cfg(not(target_os = "haiku"))] + pub fn cfsetspeed(termios: &mut Termios, baud: BaudRate) -> Result<()> { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + let res = unsafe { libc::cfsetspeed(inner_termios, baud as libc::speed_t) }; + termios.update_wrapper(); + Errno::result(res).map(drop) + } + } +} + +/// Configures the port to something like the "raw" mode of the old Version 7 terminal driver (see +/// [termios(3)](https://man7.org/linux/man-pages/man3/termios.3.html)). +/// +/// `cfmakeraw()` configures the termios structure such that input is available character-by- +/// character, echoing is disabled, and all special input and output processing is disabled. Note +/// that this is a non-standard function, but is available on Linux and BSDs. +pub fn cfmakeraw(termios: &mut Termios) { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + unsafe { + libc::cfmakeraw(inner_termios); + } + termios.update_wrapper(); +} + +/// Configures the port to "sane" mode (like the configuration of a newly created terminal) (see +/// [tcsetattr(3)](https://www.freebsd.org/cgi/man.cgi?query=tcsetattr)). +/// +/// Note that this is a non-standard function, available on FreeBSD. +#[cfg(target_os = "freebsd")] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn cfmakesane(termios: &mut Termios) { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + unsafe { + libc::cfmakesane(inner_termios); + } + termios.update_wrapper(); +} + +/// Return the configuration of a port +/// [tcgetattr(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetattr.html)). +/// +/// `tcgetattr()` returns a `Termios` structure with the current configuration for a port. Modifying +/// this structure *will not* reconfigure the port, instead the modifications should be done to +/// the `Termios` structure and then the port should be reconfigured using `tcsetattr()`. +pub fn tcgetattr(fd: RawFd) -> Result<Termios> { + let mut termios = mem::MaybeUninit::uninit(); + + let res = unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) }; + + Errno::result(res)?; + + unsafe { Ok(termios.assume_init().into()) } +} + +/// Set the configuration for a terminal (see +/// [tcsetattr(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsetattr.html)). +/// +/// `tcsetattr()` reconfigures the given port based on a given `Termios` structure. This change +/// takes affect at a time specified by `actions`. Note that this function may return success if +/// *any* of the parameters were successfully set, not only if all were set successfully. +pub fn tcsetattr(fd: RawFd, actions: SetArg, termios: &Termios) -> Result<()> { + let inner_termios = termios.get_libc_termios(); + Errno::result(unsafe { + libc::tcsetattr(fd, actions as c_int, &*inner_termios) + }) + .map(drop) +} + +/// Block until all output data is written (see +/// [tcdrain(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcdrain.html)). +pub fn tcdrain(fd: RawFd) -> Result<()> { + Errno::result(unsafe { libc::tcdrain(fd) }).map(drop) +} + +/// Suspend or resume the transmission or reception of data (see +/// [tcflow(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcflow.html)). +/// +/// `tcflow()` suspends of resumes the transmission or reception of data for the given port +/// depending on the value of `action`. +pub fn tcflow(fd: RawFd, action: FlowArg) -> Result<()> { + Errno::result(unsafe { libc::tcflow(fd, action as c_int) }).map(drop) +} + +/// Discard data in the output or input queue (see +/// [tcflush(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcflush.html)). +/// +/// `tcflush()` will discard data for a terminal port in the input queue, output queue, or both +/// depending on the value of `action`. +pub fn tcflush(fd: RawFd, action: FlushArg) -> Result<()> { + Errno::result(unsafe { libc::tcflush(fd, action as c_int) }).map(drop) +} + +/// Send a break for a specific duration (see +/// [tcsendbreak(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsendbreak.html)). +/// +/// When using asynchronous data transmission `tcsendbreak()` will transmit a continuous stream +/// of zero-valued bits for an implementation-defined duration. +pub fn tcsendbreak(fd: RawFd, duration: c_int) -> Result<()> { + Errno::result(unsafe { libc::tcsendbreak(fd, duration) }).map(drop) +} + +feature! { +#![feature = "process"] +/// Get the session controlled by the given terminal (see +/// [tcgetsid(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetsid.html)). +pub fn tcgetsid(fd: RawFd) -> Result<Pid> { + let res = unsafe { libc::tcgetsid(fd) }; + + Errno::result(res).map(Pid::from_raw) +} +} + +#[cfg(test)] +mod test { + use super::*; + use std::convert::TryFrom; + + #[test] + fn try_from() { + assert_eq!(Ok(BaudRate::B0), BaudRate::try_from(libc::B0)); + #[cfg(not(target_os = "haiku"))] + BaudRate::try_from(999999999).expect_err("assertion failed"); + #[cfg(target_os = "haiku")] + BaudRate::try_from(99).expect_err("assertion failed"); + } +} diff --git a/third_party/rust/nix/src/sys/time.rs b/third_party/rust/nix/src/sys/time.rs new file mode 100644 index 0000000000..0042c45084 --- /dev/null +++ b/third_party/rust/nix/src/sys/time.rs @@ -0,0 +1,811 @@ +#[cfg_attr(target_env = "musl", allow(deprecated))] +// https://github.com/rust-lang/libc/issues/1848 +pub use libc::{suseconds_t, time_t}; +use libc::{timespec, timeval}; +use std::convert::From; +use std::time::Duration; +use std::{cmp, fmt, ops}; + +const fn zero_init_timespec() -> timespec { + // `std::mem::MaybeUninit::zeroed()` is not yet a const fn + // (https://github.com/rust-lang/rust/issues/91850) so we will instead initialize an array of + // the appropriate size to zero and then transmute it to a timespec value. + unsafe { std::mem::transmute([0u8; std::mem::size_of::<timespec>()]) } +} + +#[cfg(any( + all(feature = "time", any(target_os = "android", target_os = "linux")), + all( + any( + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd" + ), + feature = "time", + feature = "signal" + ) +))] +pub(crate) mod timer { + use crate::sys::time::{zero_init_timespec, TimeSpec}; + use bitflags::bitflags; + + #[derive(Debug, Clone, Copy)] + pub(crate) struct TimerSpec(libc::itimerspec); + + impl TimerSpec { + pub const fn none() -> Self { + Self(libc::itimerspec { + it_interval: zero_init_timespec(), + it_value: zero_init_timespec(), + }) + } + } + + impl AsMut<libc::itimerspec> for TimerSpec { + fn as_mut(&mut self) -> &mut libc::itimerspec { + &mut self.0 + } + } + + impl AsRef<libc::itimerspec> for TimerSpec { + fn as_ref(&self) -> &libc::itimerspec { + &self.0 + } + } + + impl From<Expiration> for TimerSpec { + fn from(expiration: Expiration) -> TimerSpec { + match expiration { + Expiration::OneShot(t) => TimerSpec(libc::itimerspec { + it_interval: zero_init_timespec(), + it_value: *t.as_ref(), + }), + Expiration::IntervalDelayed(start, interval) => { + TimerSpec(libc::itimerspec { + it_interval: *interval.as_ref(), + it_value: *start.as_ref(), + }) + } + Expiration::Interval(t) => TimerSpec(libc::itimerspec { + it_interval: *t.as_ref(), + it_value: *t.as_ref(), + }), + } + } + } + + /// An enumeration allowing the definition of the expiration time of an alarm, + /// recurring or not. + #[derive(Debug, Clone, Copy, Eq, PartialEq)] + pub enum Expiration { + /// Alarm will trigger once after the time given in `TimeSpec` + OneShot(TimeSpec), + /// Alarm will trigger after a specified delay and then every interval of + /// time. + IntervalDelayed(TimeSpec, TimeSpec), + /// Alarm will trigger every specified interval of time. + Interval(TimeSpec), + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + bitflags! { + /// Flags that are used for arming the timer. + pub struct TimerSetTimeFlags: libc::c_int { + const TFD_TIMER_ABSTIME = libc::TFD_TIMER_ABSTIME; + } + } + #[cfg(any( + target_os = "freebsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "illumos" + ))] + bitflags! { + /// Flags that are used for arming the timer. + pub struct TimerSetTimeFlags: libc::c_int { + const TFD_TIMER_ABSTIME = libc::TIMER_ABSTIME; + } + } + + impl From<TimerSpec> for Expiration { + fn from(timerspec: TimerSpec) -> Expiration { + match timerspec { + TimerSpec(libc::itimerspec { + it_interval: + libc::timespec { + tv_sec: 0, + tv_nsec: 0, + .. + }, + it_value: ts, + }) => Expiration::OneShot(ts.into()), + TimerSpec(libc::itimerspec { + it_interval: int_ts, + it_value: val_ts, + }) => { + if (int_ts.tv_sec == val_ts.tv_sec) + && (int_ts.tv_nsec == val_ts.tv_nsec) + { + Expiration::Interval(int_ts.into()) + } else { + Expiration::IntervalDelayed( + val_ts.into(), + int_ts.into(), + ) + } + } + } + } + } +} + +pub trait TimeValLike: Sized { + #[inline] + fn zero() -> Self { + Self::seconds(0) + } + + #[inline] + fn hours(hours: i64) -> Self { + let secs = hours + .checked_mul(SECS_PER_HOUR) + .expect("TimeValLike::hours ouf of bounds"); + Self::seconds(secs) + } + + #[inline] + fn minutes(minutes: i64) -> Self { + let secs = minutes + .checked_mul(SECS_PER_MINUTE) + .expect("TimeValLike::minutes out of bounds"); + Self::seconds(secs) + } + + fn seconds(seconds: i64) -> Self; + fn milliseconds(milliseconds: i64) -> Self; + fn microseconds(microseconds: i64) -> Self; + fn nanoseconds(nanoseconds: i64) -> Self; + + #[inline] + fn num_hours(&self) -> i64 { + self.num_seconds() / 3600 + } + + #[inline] + fn num_minutes(&self) -> i64 { + self.num_seconds() / 60 + } + + fn num_seconds(&self) -> i64; + fn num_milliseconds(&self) -> i64; + fn num_microseconds(&self) -> i64; + fn num_nanoseconds(&self) -> i64; +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct TimeSpec(timespec); + +const NANOS_PER_SEC: i64 = 1_000_000_000; +const SECS_PER_MINUTE: i64 = 60; +const SECS_PER_HOUR: i64 = 3600; + +#[cfg(target_pointer_width = "64")] +const TS_MAX_SECONDS: i64 = (i64::MAX / NANOS_PER_SEC) - 1; + +#[cfg(target_pointer_width = "32")] +const TS_MAX_SECONDS: i64 = isize::MAX as i64; + +const TS_MIN_SECONDS: i64 = -TS_MAX_SECONDS; + +// x32 compatibility +// See https://sourceware.org/bugzilla/show_bug.cgi?id=16437 +#[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))] +type timespec_tv_nsec_t = i64; +#[cfg(not(all(target_arch = "x86_64", target_pointer_width = "32")))] +type timespec_tv_nsec_t = libc::c_long; + +impl From<timespec> for TimeSpec { + fn from(ts: timespec) -> Self { + Self(ts) + } +} + +impl From<Duration> for TimeSpec { + fn from(duration: Duration) -> Self { + Self::from_duration(duration) + } +} + +impl From<TimeSpec> for Duration { + fn from(timespec: TimeSpec) -> Self { + Duration::new(timespec.0.tv_sec as u64, timespec.0.tv_nsec as u32) + } +} + +impl AsRef<timespec> for TimeSpec { + fn as_ref(&self) -> ×pec { + &self.0 + } +} + +impl AsMut<timespec> for TimeSpec { + fn as_mut(&mut self) -> &mut timespec { + &mut self.0 + } +} + +impl Ord for TimeSpec { + // The implementation of cmp is simplified by assuming that the struct is + // normalized. That is, tv_nsec must always be within [0, 1_000_000_000) + fn cmp(&self, other: &TimeSpec) -> cmp::Ordering { + if self.tv_sec() == other.tv_sec() { + self.tv_nsec().cmp(&other.tv_nsec()) + } else { + self.tv_sec().cmp(&other.tv_sec()) + } + } +} + +impl PartialOrd for TimeSpec { + fn partial_cmp(&self, other: &TimeSpec) -> Option<cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl TimeValLike for TimeSpec { + #[inline] + #[cfg_attr(target_env = "musl", allow(deprecated))] + // https://github.com/rust-lang/libc/issues/1848 + fn seconds(seconds: i64) -> TimeSpec { + assert!( + (TS_MIN_SECONDS..=TS_MAX_SECONDS).contains(&seconds), + "TimeSpec out of bounds; seconds={}", + seconds + ); + let mut ts = zero_init_timespec(); + ts.tv_sec = seconds as time_t; + TimeSpec(ts) + } + + #[inline] + fn milliseconds(milliseconds: i64) -> TimeSpec { + let nanoseconds = milliseconds + .checked_mul(1_000_000) + .expect("TimeSpec::milliseconds out of bounds"); + + TimeSpec::nanoseconds(nanoseconds) + } + + /// Makes a new `TimeSpec` with given number of microseconds. + #[inline] + fn microseconds(microseconds: i64) -> TimeSpec { + let nanoseconds = microseconds + .checked_mul(1_000) + .expect("TimeSpec::milliseconds out of bounds"); + + TimeSpec::nanoseconds(nanoseconds) + } + + /// Makes a new `TimeSpec` with given number of nanoseconds. + #[inline] + #[cfg_attr(target_env = "musl", allow(deprecated))] + // https://github.com/rust-lang/libc/issues/1848 + fn nanoseconds(nanoseconds: i64) -> TimeSpec { + let (secs, nanos) = div_mod_floor_64(nanoseconds, NANOS_PER_SEC); + assert!( + (TS_MIN_SECONDS..=TS_MAX_SECONDS).contains(&secs), + "TimeSpec out of bounds" + ); + let mut ts = zero_init_timespec(); + ts.tv_sec = secs as time_t; + ts.tv_nsec = nanos as timespec_tv_nsec_t; + TimeSpec(ts) + } + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + fn num_seconds(&self) -> i64 { + if self.tv_sec() < 0 && self.tv_nsec() > 0 { + (self.tv_sec() + 1) as i64 + } else { + self.tv_sec() as i64 + } + } + + fn num_milliseconds(&self) -> i64 { + self.num_nanoseconds() / 1_000_000 + } + + fn num_microseconds(&self) -> i64 { + self.num_nanoseconds() / 1_000 + } + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + fn num_nanoseconds(&self) -> i64 { + let secs = self.num_seconds() * 1_000_000_000; + let nsec = self.nanos_mod_sec(); + secs + nsec as i64 + } +} + +impl TimeSpec { + /// Construct a new `TimeSpec` from its components + #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 + pub const fn new(seconds: time_t, nanoseconds: timespec_tv_nsec_t) -> Self { + let mut ts = zero_init_timespec(); + ts.tv_sec = seconds; + ts.tv_nsec = nanoseconds; + Self(ts) + } + + fn nanos_mod_sec(&self) -> timespec_tv_nsec_t { + if self.tv_sec() < 0 && self.tv_nsec() > 0 { + self.tv_nsec() - NANOS_PER_SEC as timespec_tv_nsec_t + } else { + self.tv_nsec() + } + } + + #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 + pub const fn tv_sec(&self) -> time_t { + self.0.tv_sec + } + + pub const fn tv_nsec(&self) -> timespec_tv_nsec_t { + self.0.tv_nsec + } + + #[cfg_attr(target_env = "musl", allow(deprecated))] + // https://github.com/rust-lang/libc/issues/1848 + pub const fn from_duration(duration: Duration) -> Self { + let mut ts = zero_init_timespec(); + ts.tv_sec = duration.as_secs() as time_t; + ts.tv_nsec = duration.subsec_nanos() as timespec_tv_nsec_t; + TimeSpec(ts) + } + + pub const fn from_timespec(timespec: timespec) -> Self { + Self(timespec) + } +} + +impl ops::Neg for TimeSpec { + type Output = TimeSpec; + + fn neg(self) -> TimeSpec { + TimeSpec::nanoseconds(-self.num_nanoseconds()) + } +} + +impl ops::Add for TimeSpec { + type Output = TimeSpec; + + fn add(self, rhs: TimeSpec) -> TimeSpec { + TimeSpec::nanoseconds(self.num_nanoseconds() + rhs.num_nanoseconds()) + } +} + +impl ops::Sub for TimeSpec { + type Output = TimeSpec; + + fn sub(self, rhs: TimeSpec) -> TimeSpec { + TimeSpec::nanoseconds(self.num_nanoseconds() - rhs.num_nanoseconds()) + } +} + +impl ops::Mul<i32> for TimeSpec { + type Output = TimeSpec; + + fn mul(self, rhs: i32) -> TimeSpec { + let usec = self + .num_nanoseconds() + .checked_mul(i64::from(rhs)) + .expect("TimeSpec multiply out of bounds"); + + TimeSpec::nanoseconds(usec) + } +} + +impl ops::Div<i32> for TimeSpec { + type Output = TimeSpec; + + fn div(self, rhs: i32) -> TimeSpec { + let usec = self.num_nanoseconds() / i64::from(rhs); + TimeSpec::nanoseconds(usec) + } +} + +impl fmt::Display for TimeSpec { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let (abs, sign) = if self.tv_sec() < 0 { + (-*self, "-") + } else { + (*self, "") + }; + + let sec = abs.tv_sec(); + + write!(f, "{}", sign)?; + + if abs.tv_nsec() == 0 { + if abs.tv_sec() == 1 { + write!(f, "{} second", sec)?; + } else { + write!(f, "{} seconds", sec)?; + } + } else if abs.tv_nsec() % 1_000_000 == 0 { + write!(f, "{}.{:03} seconds", sec, abs.tv_nsec() / 1_000_000)?; + } else if abs.tv_nsec() % 1_000 == 0 { + write!(f, "{}.{:06} seconds", sec, abs.tv_nsec() / 1_000)?; + } else { + write!(f, "{}.{:09} seconds", sec, abs.tv_nsec())?; + } + + Ok(()) + } +} + +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct TimeVal(timeval); + +const MICROS_PER_SEC: i64 = 1_000_000; + +#[cfg(target_pointer_width = "64")] +const TV_MAX_SECONDS: i64 = (i64::MAX / MICROS_PER_SEC) - 1; + +#[cfg(target_pointer_width = "32")] +const TV_MAX_SECONDS: i64 = isize::MAX as i64; + +const TV_MIN_SECONDS: i64 = -TV_MAX_SECONDS; + +impl AsRef<timeval> for TimeVal { + fn as_ref(&self) -> &timeval { + &self.0 + } +} + +impl AsMut<timeval> for TimeVal { + fn as_mut(&mut self) -> &mut timeval { + &mut self.0 + } +} + +impl Ord for TimeVal { + // The implementation of cmp is simplified by assuming that the struct is + // normalized. That is, tv_usec must always be within [0, 1_000_000) + fn cmp(&self, other: &TimeVal) -> cmp::Ordering { + if self.tv_sec() == other.tv_sec() { + self.tv_usec().cmp(&other.tv_usec()) + } else { + self.tv_sec().cmp(&other.tv_sec()) + } + } +} + +impl PartialOrd for TimeVal { + fn partial_cmp(&self, other: &TimeVal) -> Option<cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl TimeValLike for TimeVal { + #[inline] + fn seconds(seconds: i64) -> TimeVal { + assert!( + (TV_MIN_SECONDS..=TV_MAX_SECONDS).contains(&seconds), + "TimeVal out of bounds; seconds={}", + seconds + ); + #[cfg_attr(target_env = "musl", allow(deprecated))] + // https://github.com/rust-lang/libc/issues/1848 + TimeVal(timeval { + tv_sec: seconds as time_t, + tv_usec: 0, + }) + } + + #[inline] + fn milliseconds(milliseconds: i64) -> TimeVal { + let microseconds = milliseconds + .checked_mul(1_000) + .expect("TimeVal::milliseconds out of bounds"); + + TimeVal::microseconds(microseconds) + } + + /// Makes a new `TimeVal` with given number of microseconds. + #[inline] + fn microseconds(microseconds: i64) -> TimeVal { + let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); + assert!( + (TV_MIN_SECONDS..=TV_MAX_SECONDS).contains(&secs), + "TimeVal out of bounds" + ); + #[cfg_attr(target_env = "musl", allow(deprecated))] + // https://github.com/rust-lang/libc/issues/1848 + TimeVal(timeval { + tv_sec: secs as time_t, + tv_usec: micros as suseconds_t, + }) + } + + /// Makes a new `TimeVal` with given number of nanoseconds. Some precision + /// will be lost + #[inline] + fn nanoseconds(nanoseconds: i64) -> TimeVal { + let microseconds = nanoseconds / 1000; + let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); + assert!( + (TV_MIN_SECONDS..=TV_MAX_SECONDS).contains(&secs), + "TimeVal out of bounds" + ); + #[cfg_attr(target_env = "musl", allow(deprecated))] + // https://github.com/rust-lang/libc/issues/1848 + TimeVal(timeval { + tv_sec: secs as time_t, + tv_usec: micros as suseconds_t, + }) + } + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + fn num_seconds(&self) -> i64 { + if self.tv_sec() < 0 && self.tv_usec() > 0 { + (self.tv_sec() + 1) as i64 + } else { + self.tv_sec() as i64 + } + } + + fn num_milliseconds(&self) -> i64 { + self.num_microseconds() / 1_000 + } + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + fn num_microseconds(&self) -> i64 { + let secs = self.num_seconds() * 1_000_000; + let usec = self.micros_mod_sec(); + secs + usec as i64 + } + + fn num_nanoseconds(&self) -> i64 { + self.num_microseconds() * 1_000 + } +} + +impl TimeVal { + /// Construct a new `TimeVal` from its components + #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 + pub const fn new(seconds: time_t, microseconds: suseconds_t) -> Self { + Self(timeval { + tv_sec: seconds, + tv_usec: microseconds, + }) + } + + fn micros_mod_sec(&self) -> suseconds_t { + if self.tv_sec() < 0 && self.tv_usec() > 0 { + self.tv_usec() - MICROS_PER_SEC as suseconds_t + } else { + self.tv_usec() + } + } + + #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 + pub const fn tv_sec(&self) -> time_t { + self.0.tv_sec + } + + pub const fn tv_usec(&self) -> suseconds_t { + self.0.tv_usec + } +} + +impl ops::Neg for TimeVal { + type Output = TimeVal; + + fn neg(self) -> TimeVal { + TimeVal::microseconds(-self.num_microseconds()) + } +} + +impl ops::Add for TimeVal { + type Output = TimeVal; + + fn add(self, rhs: TimeVal) -> TimeVal { + TimeVal::microseconds(self.num_microseconds() + rhs.num_microseconds()) + } +} + +impl ops::Sub for TimeVal { + type Output = TimeVal; + + fn sub(self, rhs: TimeVal) -> TimeVal { + TimeVal::microseconds(self.num_microseconds() - rhs.num_microseconds()) + } +} + +impl ops::Mul<i32> for TimeVal { + type Output = TimeVal; + + fn mul(self, rhs: i32) -> TimeVal { + let usec = self + .num_microseconds() + .checked_mul(i64::from(rhs)) + .expect("TimeVal multiply out of bounds"); + + TimeVal::microseconds(usec) + } +} + +impl ops::Div<i32> for TimeVal { + type Output = TimeVal; + + fn div(self, rhs: i32) -> TimeVal { + let usec = self.num_microseconds() / i64::from(rhs); + TimeVal::microseconds(usec) + } +} + +impl fmt::Display for TimeVal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let (abs, sign) = if self.tv_sec() < 0 { + (-*self, "-") + } else { + (*self, "") + }; + + let sec = abs.tv_sec(); + + write!(f, "{}", sign)?; + + if abs.tv_usec() == 0 { + if abs.tv_sec() == 1 { + write!(f, "{} second", sec)?; + } else { + write!(f, "{} seconds", sec)?; + } + } else if abs.tv_usec() % 1000 == 0 { + write!(f, "{}.{:03} seconds", sec, abs.tv_usec() / 1000)?; + } else { + write!(f, "{}.{:06} seconds", sec, abs.tv_usec())?; + } + + Ok(()) + } +} + +impl From<timeval> for TimeVal { + fn from(tv: timeval) -> Self { + TimeVal(tv) + } +} + +#[inline] +fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { + (div_floor_64(this, other), mod_floor_64(this, other)) +} + +#[inline] +fn div_floor_64(this: i64, other: i64) -> i64 { + match div_rem_64(this, other) { + (d, r) if (r > 0 && other < 0) || (r < 0 && other > 0) => d - 1, + (d, _) => d, + } +} + +#[inline] +fn mod_floor_64(this: i64, other: i64) -> i64 { + match this % other { + r if (r > 0 && other < 0) || (r < 0 && other > 0) => r + other, + r => r, + } +} + +#[inline] +fn div_rem_64(this: i64, other: i64) -> (i64, i64) { + (this / other, this % other) +} + +#[cfg(test)] +mod test { + use super::{TimeSpec, TimeVal, TimeValLike}; + use std::time::Duration; + + #[test] + pub fn test_timespec() { + assert_ne!(TimeSpec::seconds(1), TimeSpec::zero()); + assert_eq!( + TimeSpec::seconds(1) + TimeSpec::seconds(2), + TimeSpec::seconds(3) + ); + assert_eq!( + TimeSpec::minutes(3) + TimeSpec::seconds(2), + TimeSpec::seconds(182) + ); + } + + #[test] + pub fn test_timespec_from() { + let duration = Duration::new(123, 123_456_789); + let timespec = TimeSpec::nanoseconds(123_123_456_789); + + assert_eq!(TimeSpec::from(duration), timespec); + assert_eq!(Duration::from(timespec), duration); + } + + #[test] + pub fn test_timespec_neg() { + let a = TimeSpec::seconds(1) + TimeSpec::nanoseconds(123); + let b = TimeSpec::seconds(-1) + TimeSpec::nanoseconds(-123); + + assert_eq!(a, -b); + } + + #[test] + pub fn test_timespec_ord() { + assert_eq!(TimeSpec::seconds(1), TimeSpec::nanoseconds(1_000_000_000)); + assert!(TimeSpec::seconds(1) < TimeSpec::nanoseconds(1_000_000_001)); + assert!(TimeSpec::seconds(1) > TimeSpec::nanoseconds(999_999_999)); + assert!(TimeSpec::seconds(-1) < TimeSpec::nanoseconds(-999_999_999)); + assert!(TimeSpec::seconds(-1) > TimeSpec::nanoseconds(-1_000_000_001)); + } + + #[test] + pub fn test_timespec_fmt() { + assert_eq!(TimeSpec::zero().to_string(), "0 seconds"); + assert_eq!(TimeSpec::seconds(42).to_string(), "42 seconds"); + assert_eq!(TimeSpec::milliseconds(42).to_string(), "0.042 seconds"); + assert_eq!(TimeSpec::microseconds(42).to_string(), "0.000042 seconds"); + assert_eq!( + TimeSpec::nanoseconds(42).to_string(), + "0.000000042 seconds" + ); + assert_eq!(TimeSpec::seconds(-86401).to_string(), "-86401 seconds"); + } + + #[test] + pub fn test_timeval() { + assert_ne!(TimeVal::seconds(1), TimeVal::zero()); + assert_eq!( + TimeVal::seconds(1) + TimeVal::seconds(2), + TimeVal::seconds(3) + ); + assert_eq!( + TimeVal::minutes(3) + TimeVal::seconds(2), + TimeVal::seconds(182) + ); + } + + #[test] + pub fn test_timeval_ord() { + assert_eq!(TimeVal::seconds(1), TimeVal::microseconds(1_000_000)); + assert!(TimeVal::seconds(1) < TimeVal::microseconds(1_000_001)); + assert!(TimeVal::seconds(1) > TimeVal::microseconds(999_999)); + assert!(TimeVal::seconds(-1) < TimeVal::microseconds(-999_999)); + assert!(TimeVal::seconds(-1) > TimeVal::microseconds(-1_000_001)); + } + + #[test] + pub fn test_timeval_neg() { + let a = TimeVal::seconds(1) + TimeVal::microseconds(123); + let b = TimeVal::seconds(-1) + TimeVal::microseconds(-123); + + assert_eq!(a, -b); + } + + #[test] + pub fn test_timeval_fmt() { + assert_eq!(TimeVal::zero().to_string(), "0 seconds"); + assert_eq!(TimeVal::seconds(42).to_string(), "42 seconds"); + assert_eq!(TimeVal::milliseconds(42).to_string(), "0.042 seconds"); + assert_eq!(TimeVal::microseconds(42).to_string(), "0.000042 seconds"); + assert_eq!(TimeVal::nanoseconds(1402).to_string(), "0.000001 seconds"); + assert_eq!(TimeVal::seconds(-86401).to_string(), "-86401 seconds"); + } +} diff --git a/third_party/rust/nix/src/sys/timer.rs b/third_party/rust/nix/src/sys/timer.rs new file mode 100644 index 0000000000..e1a34051e8 --- /dev/null +++ b/third_party/rust/nix/src/sys/timer.rs @@ -0,0 +1,187 @@ +//! Timer API via signals. +//! +//! Timer is a POSIX API to create timers and get expiration notifications +//! through queued Unix signals, for the current process. This is similar to +//! Linux's timerfd mechanism, except that API is specific to Linux and makes +//! use of file polling. +//! +//! For more documentation, please read [timer_create](https://pubs.opengroup.org/onlinepubs/9699919799/functions/timer_create.html). +//! +//! # Examples +//! +//! Create an interval timer that signals SIGALARM every 250 milliseconds. +//! +//! ```no_run +//! use nix::sys::signal::{self, SigEvent, SigHandler, SigevNotify, Signal}; +//! use nix::sys::timer::{Expiration, Timer, TimerSetTimeFlags}; +//! use nix::time::ClockId; +//! use std::convert::TryFrom; +//! use std::sync::atomic::{AtomicU64, Ordering}; +//! use std::thread::yield_now; +//! use std::time::Duration; +//! +//! const SIG: Signal = Signal::SIGALRM; +//! static ALARMS: AtomicU64 = AtomicU64::new(0); +//! +//! extern "C" fn handle_alarm(signal: libc::c_int) { +//! let signal = Signal::try_from(signal).unwrap(); +//! if signal == SIG { +//! ALARMS.fetch_add(1, Ordering::Relaxed); +//! } +//! } +//! +//! fn main() { +//! let clockid = ClockId::CLOCK_MONOTONIC; +//! let sigevent = SigEvent::new(SigevNotify::SigevSignal { +//! signal: SIG, +//! si_value: 0, +//! }); +//! +//! let mut timer = Timer::new(clockid, sigevent).unwrap(); +//! let expiration = Expiration::Interval(Duration::from_millis(250).into()); +//! let flags = TimerSetTimeFlags::empty(); +//! timer.set(expiration, flags).expect("could not set timer"); +//! +//! let handler = SigHandler::Handler(handle_alarm); +//! unsafe { signal::signal(SIG, handler) }.unwrap(); +//! +//! loop { +//! let alarms = ALARMS.load(Ordering::Relaxed); +//! if alarms >= 10 { +//! println!("total alarms handled: {}", alarms); +//! break; +//! } +//! yield_now() +//! } +//! } +//! ``` +use crate::sys::signal::SigEvent; +use crate::sys::time::timer::TimerSpec; +pub use crate::sys::time::timer::{Expiration, TimerSetTimeFlags}; +use crate::time::ClockId; +use crate::{errno::Errno, Result}; +use core::mem; + +/// A Unix signal per-process timer. +#[derive(Debug)] +#[repr(transparent)] +pub struct Timer(libc::timer_t); + +impl Timer { + /// Creates a new timer based on the clock defined by `clockid`. The details + /// of the signal and its handler are defined by the passed `sigevent`. + #[doc(alias("timer_create"))] + pub fn new(clockid: ClockId, mut sigevent: SigEvent) -> Result<Self> { + let mut timer_id: mem::MaybeUninit<libc::timer_t> = + mem::MaybeUninit::uninit(); + Errno::result(unsafe { + libc::timer_create( + clockid.as_raw(), + sigevent.as_mut_ptr(), + timer_id.as_mut_ptr(), + ) + }) + .map(|_| { + // SAFETY: libc::timer_create is responsible for initializing + // timer_id. + unsafe { Self(timer_id.assume_init()) } + }) + } + + /// Set a new alarm on the timer. + /// + /// # Types of alarm + /// + /// There are 3 types of alarms you can set: + /// + /// - one shot: the alarm will trigger once after the specified amount of + /// time. + /// Example: I want an alarm to go off in 60s and then disable itself. + /// + /// - interval: the alarm will trigger every specified interval of time. + /// Example: I want an alarm to go off every 60s. The alarm will first + /// go off 60s after I set it and every 60s after that. The alarm will + /// not disable itself. + /// + /// - interval delayed: the alarm will trigger after a certain amount of + /// time and then trigger at a specified interval. + /// Example: I want an alarm to go off every 60s but only start in 1h. + /// The alarm will first trigger 1h after I set it and then every 60s + /// after that. The alarm will not disable itself. + /// + /// # Relative vs absolute alarm + /// + /// If you do not set any `TimerSetTimeFlags`, then the `TimeSpec` you pass + /// to the `Expiration` you want is relative. If however you want an alarm + /// to go off at a certain point in time, you can set `TFD_TIMER_ABSTIME`. + /// Then the one shot TimeSpec and the delay TimeSpec of the delayed + /// interval are going to be interpreted as absolute. + /// + /// # Disabling alarms + /// + /// Note: Only one alarm can be set for any given timer. Setting a new alarm + /// actually removes the previous one. + /// + /// Note: Setting a one shot alarm with a 0s TimeSpec disable the alarm + /// altogether. + #[doc(alias("timer_settime"))] + pub fn set( + &mut self, + expiration: Expiration, + flags: TimerSetTimeFlags, + ) -> Result<()> { + let timerspec: TimerSpec = expiration.into(); + Errno::result(unsafe { + libc::timer_settime( + self.0, + flags.bits(), + timerspec.as_ref(), + core::ptr::null_mut(), + ) + }) + .map(drop) + } + + /// Get the parameters for the alarm currently set, if any. + #[doc(alias("timer_gettime"))] + pub fn get(&self) -> Result<Option<Expiration>> { + let mut timerspec = TimerSpec::none(); + Errno::result(unsafe { + libc::timer_gettime(self.0, timerspec.as_mut()) + }) + .map(|_| { + if timerspec.as_ref().it_interval.tv_sec == 0 + && timerspec.as_ref().it_interval.tv_nsec == 0 + && timerspec.as_ref().it_value.tv_sec == 0 + && timerspec.as_ref().it_value.tv_nsec == 0 + { + None + } else { + Some(timerspec.into()) + } + }) + } + + /// Return the number of timers that have overrun + /// + /// Each timer is able to queue one signal to the process at a time, meaning + /// if the signal is not handled before the next expiration the timer has + /// 'overrun'. This function returns how many times that has happened to + /// this timer, up to `libc::DELAYTIMER_MAX`. If more than the maximum + /// number of overruns have happened the return is capped to the maximum. + #[doc(alias("timer_getoverrun"))] + pub fn overruns(&self) -> i32 { + unsafe { libc::timer_getoverrun(self.0) } + } +} + +impl Drop for Timer { + fn drop(&mut self) { + if !std::thread::panicking() { + let result = Errno::result(unsafe { libc::timer_delete(self.0) }); + if let Err(Errno::EINVAL) = result { + panic!("close of Timer encountered EINVAL"); + } + } + } +} diff --git a/third_party/rust/nix/src/sys/timerfd.rs b/third_party/rust/nix/src/sys/timerfd.rs new file mode 100644 index 0000000000..a35fc927f4 --- /dev/null +++ b/third_party/rust/nix/src/sys/timerfd.rs @@ -0,0 +1,214 @@ +//! Timer API via file descriptors. +//! +//! Timer FD is a Linux-only API to create timers and get expiration +//! notifications through file descriptors. +//! +//! For more documentation, please read [timerfd_create(2)](https://man7.org/linux/man-pages/man2/timerfd_create.2.html). +//! +//! # Examples +//! +//! Create a new one-shot timer that expires after 1 second. +//! ``` +//! # use std::os::unix::io::AsRawFd; +//! # use nix::sys::timerfd::{TimerFd, ClockId, TimerFlags, TimerSetTimeFlags, +//! # Expiration}; +//! # use nix::sys::time::{TimeSpec, TimeValLike}; +//! # use nix::unistd::read; +//! # +//! // We create a new monotonic timer. +//! let timer = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()) +//! .unwrap(); +//! +//! // We set a new one-shot timer in 1 seconds. +//! timer.set( +//! Expiration::OneShot(TimeSpec::seconds(1)), +//! TimerSetTimeFlags::empty() +//! ).unwrap(); +//! +//! // We wait for the timer to expire. +//! timer.wait().unwrap(); +//! ``` +use crate::sys::time::timer::TimerSpec; +pub use crate::sys::time::timer::{Expiration, TimerSetTimeFlags}; +use crate::unistd::read; +use crate::{errno::Errno, Result}; +use libc::c_int; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; + +/// A timerfd instance. This is also a file descriptor, you can feed it to +/// other interfaces consuming file descriptors, epoll for example. +#[derive(Debug)] +pub struct TimerFd { + fd: RawFd, +} + +impl AsRawFd for TimerFd { + fn as_raw_fd(&self) -> RawFd { + self.fd + } +} + +impl FromRawFd for TimerFd { + unsafe fn from_raw_fd(fd: RawFd) -> Self { + TimerFd { fd } + } +} + +libc_enum! { + /// The type of the clock used to mark the progress of the timer. For more + /// details on each kind of clock, please refer to [timerfd_create(2)](https://man7.org/linux/man-pages/man2/timerfd_create.2.html). + #[repr(i32)] + #[non_exhaustive] + pub enum ClockId { + /// A settable system-wide real-time clock. + CLOCK_REALTIME, + /// A non-settable monotonically increasing clock. + /// + /// Does not change after system startup. + /// Does not measure time while the system is suspended. + CLOCK_MONOTONIC, + /// Like `CLOCK_MONOTONIC`, except that `CLOCK_BOOTTIME` includes the time + /// that the system was suspended. + CLOCK_BOOTTIME, + /// Like `CLOCK_REALTIME`, but will wake the system if it is suspended. + CLOCK_REALTIME_ALARM, + /// Like `CLOCK_BOOTTIME`, but will wake the system if it is suspended. + CLOCK_BOOTTIME_ALARM, + } +} + +libc_bitflags! { + /// Additional flags to change the behaviour of the file descriptor at the + /// time of creation. + pub struct TimerFlags: c_int { + /// Set the `O_NONBLOCK` flag on the open file description referred to by the new file descriptor. + TFD_NONBLOCK; + /// Set the `FD_CLOEXEC` flag on the file descriptor. + TFD_CLOEXEC; + } +} + +impl TimerFd { + /// Creates a new timer based on the clock defined by `clockid`. The + /// underlying fd can be assigned specific flags with `flags` (CLOEXEC, + /// NONBLOCK). The underlying fd will be closed on drop. + #[doc(alias("timerfd_create"))] + pub fn new(clockid: ClockId, flags: TimerFlags) -> Result<Self> { + Errno::result(unsafe { + libc::timerfd_create(clockid as i32, flags.bits()) + }) + .map(|fd| Self { fd }) + } + + /// Sets a new alarm on the timer. + /// + /// # Types of alarm + /// + /// There are 3 types of alarms you can set: + /// + /// - one shot: the alarm will trigger once after the specified amount of + /// time. + /// Example: I want an alarm to go off in 60s and then disable itself. + /// + /// - interval: the alarm will trigger every specified interval of time. + /// Example: I want an alarm to go off every 60s. The alarm will first + /// go off 60s after I set it and every 60s after that. The alarm will + /// not disable itself. + /// + /// - interval delayed: the alarm will trigger after a certain amount of + /// time and then trigger at a specified interval. + /// Example: I want an alarm to go off every 60s but only start in 1h. + /// The alarm will first trigger 1h after I set it and then every 60s + /// after that. The alarm will not disable itself. + /// + /// # Relative vs absolute alarm + /// + /// If you do not set any `TimerSetTimeFlags`, then the `TimeSpec` you pass + /// to the `Expiration` you want is relative. If however you want an alarm + /// to go off at a certain point in time, you can set `TFD_TIMER_ABSTIME`. + /// Then the one shot TimeSpec and the delay TimeSpec of the delayed + /// interval are going to be interpreted as absolute. + /// + /// # Disabling alarms + /// + /// Note: Only one alarm can be set for any given timer. Setting a new alarm + /// actually removes the previous one. + /// + /// Note: Setting a one shot alarm with a 0s TimeSpec disables the alarm + /// altogether. + #[doc(alias("timerfd_settime"))] + pub fn set( + &self, + expiration: Expiration, + flags: TimerSetTimeFlags, + ) -> Result<()> { + let timerspec: TimerSpec = expiration.into(); + Errno::result(unsafe { + libc::timerfd_settime( + self.fd, + flags.bits(), + timerspec.as_ref(), + std::ptr::null_mut(), + ) + }) + .map(drop) + } + + /// Get the parameters for the alarm currently set, if any. + #[doc(alias("timerfd_gettime"))] + pub fn get(&self) -> Result<Option<Expiration>> { + let mut timerspec = TimerSpec::none(); + Errno::result(unsafe { + libc::timerfd_gettime(self.fd, timerspec.as_mut()) + }) + .map(|_| { + if timerspec.as_ref().it_interval.tv_sec == 0 + && timerspec.as_ref().it_interval.tv_nsec == 0 + && timerspec.as_ref().it_value.tv_sec == 0 + && timerspec.as_ref().it_value.tv_nsec == 0 + { + None + } else { + Some(timerspec.into()) + } + }) + } + + /// Remove the alarm if any is set. + #[doc(alias("timerfd_settime"))] + pub fn unset(&self) -> Result<()> { + Errno::result(unsafe { + libc::timerfd_settime( + self.fd, + TimerSetTimeFlags::empty().bits(), + TimerSpec::none().as_ref(), + std::ptr::null_mut(), + ) + }) + .map(drop) + } + + /// Wait for the configured alarm to expire. + /// + /// Note: If the alarm is unset, then you will wait forever. + pub fn wait(&self) -> Result<()> { + while let Err(e) = read(self.fd, &mut [0u8; 8]) { + if e != Errno::EINTR { + return Err(e); + } + } + + Ok(()) + } +} + +impl Drop for TimerFd { + fn drop(&mut self) { + if !std::thread::panicking() { + let result = Errno::result(unsafe { libc::close(self.fd) }); + if let Err(Errno::EBADF) = result { + panic!("close of TimerFd encountered EBADF"); + } + } + } +} diff --git a/third_party/rust/nix/src/sys/uio.rs b/third_party/rust/nix/src/sys/uio.rs new file mode 100644 index 0000000000..b31c3068a7 --- /dev/null +++ b/third_party/rust/nix/src/sys/uio.rs @@ -0,0 +1,291 @@ +//! Vectored I/O + +use crate::errno::Errno; +use crate::Result; +use libc::{self, c_int, c_void, off_t, size_t}; +use std::io::{IoSlice, IoSliceMut}; +use std::marker::PhantomData; +use std::os::unix::io::RawFd; + +/// Low-level vectored write to a raw file descriptor +/// +/// See also [writev(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/writev.html) +pub fn writev(fd: RawFd, iov: &[IoSlice<'_>]) -> Result<usize> { + // SAFETY: to quote the documentation for `IoSlice`: + // + // [IoSlice] is semantically a wrapper around a &[u8], but is + // guaranteed to be ABI compatible with the iovec type on Unix + // platforms. + // + // Because it is ABI compatible, a pointer cast here is valid + let res = unsafe { + libc::writev(fd, iov.as_ptr() as *const libc::iovec, iov.len() as c_int) + }; + + Errno::result(res).map(|r| r as usize) +} + +/// Low-level vectored read from a raw file descriptor +/// +/// See also [readv(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/readv.html) +pub fn readv(fd: RawFd, iov: &mut [IoSliceMut<'_>]) -> Result<usize> { + // SAFETY: same as in writev(), IoSliceMut is ABI-compatible with iovec + let res = unsafe { + libc::readv(fd, iov.as_ptr() as *const libc::iovec, iov.len() as c_int) + }; + + Errno::result(res).map(|r| r as usize) +} + +/// Write to `fd` at `offset` from buffers in `iov`. +/// +/// Buffers in `iov` will be written in order until all buffers have been written +/// or an error occurs. The file offset is not changed. +/// +/// See also: [`writev`](fn.writev.html) and [`pwrite`](fn.pwrite.html) +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn pwritev(fd: RawFd, iov: &[IoSlice<'_>], offset: off_t) -> Result<usize> { + #[cfg(target_env = "uclibc")] + let offset = offset as libc::off64_t; // uclibc doesn't use off_t + + // SAFETY: same as in writev() + let res = unsafe { + libc::pwritev( + fd, + iov.as_ptr() as *const libc::iovec, + iov.len() as c_int, + offset, + ) + }; + + Errno::result(res).map(|r| r as usize) +} + +/// Read from `fd` at `offset` filling buffers in `iov`. +/// +/// Buffers in `iov` will be filled in order until all buffers have been filled, +/// no more bytes are available, or an error occurs. The file offset is not +/// changed. +/// +/// See also: [`readv`](fn.readv.html) and [`pread`](fn.pread.html) +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn preadv( + fd: RawFd, + iov: &mut [IoSliceMut<'_>], + offset: off_t, +) -> Result<usize> { + #[cfg(target_env = "uclibc")] + let offset = offset as libc::off64_t; // uclibc doesn't use off_t + + // SAFETY: same as in readv() + let res = unsafe { + libc::preadv( + fd, + iov.as_ptr() as *const libc::iovec, + iov.len() as c_int, + offset, + ) + }; + + Errno::result(res).map(|r| r as usize) +} + +/// Low-level write to a file, with specified offset. +/// +/// See also [pwrite(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pwrite.html) +// TODO: move to unistd +pub fn pwrite(fd: RawFd, buf: &[u8], offset: off_t) -> Result<usize> { + let res = unsafe { + libc::pwrite( + fd, + buf.as_ptr() as *const c_void, + buf.len() as size_t, + offset, + ) + }; + + Errno::result(res).map(|r| r as usize) +} + +/// Low-level read from a file, with specified offset. +/// +/// See also [pread(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pread.html) +// TODO: move to unistd +pub fn pread(fd: RawFd, buf: &mut [u8], offset: off_t) -> Result<usize> { + let res = unsafe { + libc::pread( + fd, + buf.as_mut_ptr() as *mut c_void, + buf.len() as size_t, + offset, + ) + }; + + Errno::result(res).map(|r| r as usize) +} + +/// A slice of memory in a remote process, starting at address `base` +/// and consisting of `len` bytes. +/// +/// This is the same underlying C structure as `IoSlice`, +/// except that it refers to memory in some other process, and is +/// therefore not represented in Rust by an actual slice as `IoSlice` is. It +/// is used with [`process_vm_readv`](fn.process_vm_readv.html) +/// and [`process_vm_writev`](fn.process_vm_writev.html). +#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +#[repr(C)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct RemoteIoVec { + /// The starting address of this slice (`iov_base`). + pub base: usize, + /// The number of bytes in this slice (`iov_len`). + pub len: usize, +} + +/// A vector of buffers. +/// +/// Vectored I/O methods like [`writev`] and [`readv`] use this structure for +/// both reading and writing. Each `IoVec` specifies the base address and +/// length of an area in memory. +#[deprecated( + since = "0.24.0", + note = "`IoVec` is no longer used in the public interface, use `IoSlice` or `IoSliceMut` instead" +)] +#[repr(transparent)] +#[allow(renamed_and_removed_lints)] +#[allow(clippy::unknown_clippy_lints)] +// Clippy false positive: https://github.com/rust-lang/rust-clippy/issues/8867 +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct IoVec<T>(pub(crate) libc::iovec, PhantomData<T>); + +#[allow(deprecated)] +impl<T> IoVec<T> { + /// View the `IoVec` as a Rust slice. + #[deprecated( + since = "0.24.0", + note = "Use the `Deref` impl of `IoSlice` or `IoSliceMut` instead" + )] + #[inline] + pub fn as_slice(&self) -> &[u8] { + use std::slice; + + unsafe { + slice::from_raw_parts(self.0.iov_base as *const u8, self.0.iov_len) + } + } +} + +#[allow(deprecated)] +impl<'a> IoVec<&'a [u8]> { + /// Create an `IoVec` from a Rust slice. + #[deprecated(since = "0.24.0", note = "Use `IoSlice::new` instead")] + pub fn from_slice(buf: &'a [u8]) -> IoVec<&'a [u8]> { + IoVec( + libc::iovec { + iov_base: buf.as_ptr() as *mut c_void, + iov_len: buf.len() as size_t, + }, + PhantomData, + ) + } +} + +#[allow(deprecated)] +impl<'a> IoVec<&'a mut [u8]> { + /// Create an `IoVec` from a mutable Rust slice. + #[deprecated(since = "0.24.0", note = "Use `IoSliceMut::new` instead")] + pub fn from_mut_slice(buf: &'a mut [u8]) -> IoVec<&'a mut [u8]> { + IoVec( + libc::iovec { + iov_base: buf.as_ptr() as *mut c_void, + iov_len: buf.len() as size_t, + }, + PhantomData, + ) + } +} + +// The only reason IoVec isn't automatically Send+Sync is because libc::iovec +// contains raw pointers. +#[allow(deprecated)] +unsafe impl<T> Send for IoVec<T> where T: Send {} +#[allow(deprecated)] +unsafe impl<T> Sync for IoVec<T> where T: Sync {} + +feature! { +#![feature = "process"] + +/// Write data directly to another process's virtual memory +/// (see [`process_vm_writev`(2)]). +/// +/// `local_iov` is a list of [`IoSlice`]s containing the data to be written, +/// and `remote_iov` is a list of [`RemoteIoVec`]s identifying where the +/// data should be written in the target process. On success, returns the +/// number of bytes written, which will always be a whole +/// number of `remote_iov` chunks. +/// +/// This requires the same permissions as debugging the process using +/// [ptrace]: you must either be a privileged process (with +/// `CAP_SYS_PTRACE`), or you must be running as the same user as the +/// target process and the OS must have unprivileged debugging enabled. +/// +/// This function is only available on Linux and Android(SDK23+). +/// +/// [`process_vm_writev`(2)]: https://man7.org/linux/man-pages/man2/process_vm_writev.2.html +/// [ptrace]: ../ptrace/index.html +/// [`IoSlice`]: https://doc.rust-lang.org/std/io/struct.IoSlice.html +/// [`RemoteIoVec`]: struct.RemoteIoVec.html +#[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] +pub fn process_vm_writev( + pid: crate::unistd::Pid, + local_iov: &[IoSlice<'_>], + remote_iov: &[RemoteIoVec]) -> Result<usize> +{ + let res = unsafe { + libc::process_vm_writev(pid.into(), + local_iov.as_ptr() as *const libc::iovec, local_iov.len() as libc::c_ulong, + remote_iov.as_ptr() as *const libc::iovec, remote_iov.len() as libc::c_ulong, 0) + }; + + Errno::result(res).map(|r| r as usize) +} + +/// Read data directly from another process's virtual memory +/// (see [`process_vm_readv`(2)]). +/// +/// `local_iov` is a list of [`IoSliceMut`]s containing the buffer to copy +/// data into, and `remote_iov` is a list of [`RemoteIoVec`]s identifying +/// where the source data is in the target process. On success, +/// returns the number of bytes written, which will always be a whole +/// number of `remote_iov` chunks. +/// +/// This requires the same permissions as debugging the process using +/// [`ptrace`]: you must either be a privileged process (with +/// `CAP_SYS_PTRACE`), or you must be running as the same user as the +/// target process and the OS must have unprivileged debugging enabled. +/// +/// This function is only available on Linux and Android(SDK23+). +/// +/// [`process_vm_readv`(2)]: https://man7.org/linux/man-pages/man2/process_vm_readv.2.html +/// [`ptrace`]: ../ptrace/index.html +/// [`IoSliceMut`]: https://doc.rust-lang.org/std/io/struct.IoSliceMut.html +/// [`RemoteIoVec`]: struct.RemoteIoVec.html +#[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] +pub fn process_vm_readv( + pid: crate::unistd::Pid, + local_iov: &mut [IoSliceMut<'_>], + remote_iov: &[RemoteIoVec]) -> Result<usize> +{ + let res = unsafe { + libc::process_vm_readv(pid.into(), + local_iov.as_ptr() as *const libc::iovec, local_iov.len() as libc::c_ulong, + remote_iov.as_ptr() as *const libc::iovec, remote_iov.len() as libc::c_ulong, 0) + }; + + Errno::result(res).map(|r| r as usize) +} +} diff --git a/third_party/rust/nix/src/sys/utsname.rs b/third_party/rust/nix/src/sys/utsname.rs new file mode 100644 index 0000000000..b48ed9f45e --- /dev/null +++ b/third_party/rust/nix/src/sys/utsname.rs @@ -0,0 +1,85 @@ +//! Get system identification +use crate::{Errno, Result}; +use libc::c_char; +use std::ffi::OsStr; +use std::mem; +use std::os::unix::ffi::OsStrExt; + +/// Describes the running system. Return type of [`uname`]. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(transparent)] +pub struct UtsName(libc::utsname); + +impl UtsName { + /// Name of the operating system implementation. + pub fn sysname(&self) -> &OsStr { + cast_and_trim(&self.0.sysname) + } + + /// Network name of this machine. + pub fn nodename(&self) -> &OsStr { + cast_and_trim(&self.0.nodename) + } + + /// Release level of the operating system. + pub fn release(&self) -> &OsStr { + cast_and_trim(&self.0.release) + } + + /// Version level of the operating system. + pub fn version(&self) -> &OsStr { + cast_and_trim(&self.0.version) + } + + /// Machine hardware platform. + pub fn machine(&self) -> &OsStr { + cast_and_trim(&self.0.machine) + } + + /// NIS or YP domain name of this machine. + #[cfg(any(target_os = "android", target_os = "linux"))] + pub fn domainname(&self) -> &OsStr { + cast_and_trim(&self.0.domainname) + } +} + +/// Get system identification +pub fn uname() -> Result<UtsName> { + unsafe { + let mut ret = mem::MaybeUninit::zeroed(); + Errno::result(libc::uname(ret.as_mut_ptr()))?; + Ok(UtsName(ret.assume_init())) + } +} + +fn cast_and_trim(slice: &[c_char]) -> &OsStr { + let length = slice + .iter() + .position(|&byte| byte == 0) + .unwrap_or(slice.len()); + let bytes = + unsafe { std::slice::from_raw_parts(slice.as_ptr().cast(), length) }; + + OsStr::from_bytes(bytes) +} + +#[cfg(test)] +mod test { + #[cfg(target_os = "linux")] + #[test] + pub fn test_uname_linux() { + assert_eq!(super::uname().unwrap().sysname(), "Linux"); + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + #[test] + pub fn test_uname_darwin() { + assert_eq!(super::uname().unwrap().sysname(), "Darwin"); + } + + #[cfg(target_os = "freebsd")] + #[test] + pub fn test_uname_freebsd() { + assert_eq!(super::uname().unwrap().sysname(), "FreeBSD"); + } +} diff --git a/third_party/rust/nix/src/sys/wait.rs b/third_party/rust/nix/src/sys/wait.rs new file mode 100644 index 0000000000..b6524e8661 --- /dev/null +++ b/third_party/rust/nix/src/sys/wait.rs @@ -0,0 +1,388 @@ +//! Wait for a process to change status +use crate::errno::Errno; +use crate::sys::signal::Signal; +use crate::unistd::Pid; +use crate::Result; +use cfg_if::cfg_if; +use libc::{self, c_int}; +use std::convert::TryFrom; +#[cfg(any( + target_os = "android", + all(target_os = "linux", not(target_env = "uclibc")), +))] +use std::os::unix::io::RawFd; + +libc_bitflags!( + /// Controls the behavior of [`waitpid`]. + pub struct WaitPidFlag: c_int { + /// Do not block when there are no processes wishing to report status. + WNOHANG; + /// Report the status of selected processes which are stopped due to a + /// [`SIGTTIN`](crate::sys::signal::Signal::SIGTTIN), + /// [`SIGTTOU`](crate::sys::signal::Signal::SIGTTOU), + /// [`SIGTSTP`](crate::sys::signal::Signal::SIGTSTP), or + /// [`SIGSTOP`](crate::sys::signal::Signal::SIGSTOP) signal. + WUNTRACED; + /// Report the status of selected processes which have terminated. + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "redox", + target_os = "macos", + target_os = "netbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + WEXITED; + /// Report the status of selected processes that have continued from a + /// job control stop by receiving a + /// [`SIGCONT`](crate::sys::signal::Signal::SIGCONT) signal. + WCONTINUED; + /// An alias for WUNTRACED. + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "redox", + target_os = "macos", + target_os = "netbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + WSTOPPED; + /// Don't reap, just poll status. + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "redox", + target_os = "macos", + target_os = "netbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + WNOWAIT; + /// Don't wait on children of other threads in this group + #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + __WNOTHREAD; + /// Wait on all children, regardless of type + #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + __WALL; + /// Wait for "clone" children only. + #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + __WCLONE; + } +); + +/// Possible return values from `wait()` or `waitpid()`. +/// +/// Each status (other than `StillAlive`) describes a state transition +/// in a child process `Pid`, such as the process exiting or stopping, +/// plus additional data about the transition if any. +/// +/// Note that there are two Linux-specific enum variants, `PtraceEvent` +/// and `PtraceSyscall`. Portable code should avoid exhaustively +/// matching on `WaitStatus`. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum WaitStatus { + /// The process exited normally (as with `exit()` or returning from + /// `main`) with the given exit code. This case matches the C macro + /// `WIFEXITED(status)`; the second field is `WEXITSTATUS(status)`. + Exited(Pid, i32), + /// The process was killed by the given signal. The third field + /// indicates whether the signal generated a core dump. This case + /// matches the C macro `WIFSIGNALED(status)`; the last two fields + /// correspond to `WTERMSIG(status)` and `WCOREDUMP(status)`. + Signaled(Pid, Signal, bool), + /// The process is alive, but was stopped by the given signal. This + /// is only reported if `WaitPidFlag::WUNTRACED` was passed. This + /// case matches the C macro `WIFSTOPPED(status)`; the second field + /// is `WSTOPSIG(status)`. + Stopped(Pid, Signal), + /// The traced process was stopped by a `PTRACE_EVENT_*` event. See + /// [`nix::sys::ptrace`] and [`ptrace`(2)] for more information. All + /// currently-defined events use `SIGTRAP` as the signal; the third + /// field is the `PTRACE_EVENT_*` value of the event. + /// + /// [`nix::sys::ptrace`]: ../ptrace/index.html + /// [`ptrace`(2)]: https://man7.org/linux/man-pages/man2/ptrace.2.html + #[cfg(any(target_os = "linux", target_os = "android"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + PtraceEvent(Pid, Signal, c_int), + /// The traced process was stopped by execution of a system call, + /// and `PTRACE_O_TRACESYSGOOD` is in effect. See [`ptrace`(2)] for + /// more information. + /// + /// [`ptrace`(2)]: https://man7.org/linux/man-pages/man2/ptrace.2.html + #[cfg(any(target_os = "linux", target_os = "android"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + PtraceSyscall(Pid), + /// The process was previously stopped but has resumed execution + /// after receiving a `SIGCONT` signal. This is only reported if + /// `WaitPidFlag::WCONTINUED` was passed. This case matches the C + /// macro `WIFCONTINUED(status)`. + Continued(Pid), + /// There are currently no state changes to report in any awaited + /// child process. This is only returned if `WaitPidFlag::WNOHANG` + /// was used (otherwise `wait()` or `waitpid()` would block until + /// there was something to report). + StillAlive, +} + +impl WaitStatus { + /// Extracts the PID from the WaitStatus unless it equals StillAlive. + pub fn pid(&self) -> Option<Pid> { + use self::WaitStatus::*; + match *self { + Exited(p, _) | Signaled(p, _, _) | Stopped(p, _) | Continued(p) => { + Some(p) + } + StillAlive => None, + #[cfg(any(target_os = "android", target_os = "linux"))] + PtraceEvent(p, _, _) | PtraceSyscall(p) => Some(p), + } + } +} + +fn exited(status: i32) -> bool { + libc::WIFEXITED(status) +} + +fn exit_status(status: i32) -> i32 { + libc::WEXITSTATUS(status) +} + +fn signaled(status: i32) -> bool { + libc::WIFSIGNALED(status) +} + +fn term_signal(status: i32) -> Result<Signal> { + Signal::try_from(libc::WTERMSIG(status)) +} + +fn dumped_core(status: i32) -> bool { + libc::WCOREDUMP(status) +} + +fn stopped(status: i32) -> bool { + libc::WIFSTOPPED(status) +} + +fn stop_signal(status: i32) -> Result<Signal> { + Signal::try_from(libc::WSTOPSIG(status)) +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +fn syscall_stop(status: i32) -> bool { + // From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect + // of delivering SIGTRAP | 0x80 as the signal number for syscall + // stops. This allows easily distinguishing syscall stops from + // genuine SIGTRAP signals. + libc::WSTOPSIG(status) == libc::SIGTRAP | 0x80 +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +fn stop_additional(status: i32) -> c_int { + (status >> 16) as c_int +} + +fn continued(status: i32) -> bool { + libc::WIFCONTINUED(status) +} + +impl WaitStatus { + /// Convert a raw `wstatus` as returned by `waitpid`/`wait` into a `WaitStatus` + /// + /// # Errors + /// + /// Returns an `Error` corresponding to `EINVAL` for invalid status values. + /// + /// # Examples + /// + /// Convert a `wstatus` obtained from `libc::waitpid` into a `WaitStatus`: + /// + /// ``` + /// use nix::sys::wait::WaitStatus; + /// use nix::sys::signal::Signal; + /// let pid = nix::unistd::Pid::from_raw(1); + /// let status = WaitStatus::from_raw(pid, 0x0002); + /// assert_eq!(status, Ok(WaitStatus::Signaled(pid, Signal::SIGINT, false))); + /// ``` + pub fn from_raw(pid: Pid, status: i32) -> Result<WaitStatus> { + Ok(if exited(status) { + WaitStatus::Exited(pid, exit_status(status)) + } else if signaled(status) { + WaitStatus::Signaled(pid, term_signal(status)?, dumped_core(status)) + } else if stopped(status) { + cfg_if! { + if #[cfg(any(target_os = "android", target_os = "linux"))] { + fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> { + let status_additional = stop_additional(status); + Ok(if syscall_stop(status) { + WaitStatus::PtraceSyscall(pid) + } else if status_additional == 0 { + WaitStatus::Stopped(pid, stop_signal(status)?) + } else { + WaitStatus::PtraceEvent(pid, stop_signal(status)?, + stop_additional(status)) + }) + } + } else { + fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> { + Ok(WaitStatus::Stopped(pid, stop_signal(status)?)) + } + } + } + return decode_stopped(pid, status); + } else { + assert!(continued(status)); + WaitStatus::Continued(pid) + }) + } + + /// Convert a `siginfo_t` as returned by `waitid` to a `WaitStatus` + /// + /// # Errors + /// + /// Returns an `Error` corresponding to `EINVAL` for invalid values. + /// + /// # Safety + /// + /// siginfo_t is actually a union, not all fields may be initialized. + /// The functions si_pid() and si_status() must be valid to call on + /// the passed siginfo_t. + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "haiku", + all(target_os = "linux", not(target_env = "uclibc")), + ))] + unsafe fn from_siginfo(siginfo: &libc::siginfo_t) -> Result<WaitStatus> { + let si_pid = siginfo.si_pid(); + if si_pid == 0 { + return Ok(WaitStatus::StillAlive); + } + + assert_eq!(siginfo.si_signo, libc::SIGCHLD); + + let pid = Pid::from_raw(si_pid); + let si_status = siginfo.si_status(); + + let status = match siginfo.si_code { + libc::CLD_EXITED => WaitStatus::Exited(pid, si_status), + libc::CLD_KILLED | libc::CLD_DUMPED => WaitStatus::Signaled( + pid, + Signal::try_from(si_status)?, + siginfo.si_code == libc::CLD_DUMPED, + ), + libc::CLD_STOPPED => { + WaitStatus::Stopped(pid, Signal::try_from(si_status)?) + } + libc::CLD_CONTINUED => WaitStatus::Continued(pid), + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::CLD_TRAPPED => { + if si_status == libc::SIGTRAP | 0x80 { + WaitStatus::PtraceSyscall(pid) + } else { + WaitStatus::PtraceEvent( + pid, + Signal::try_from(si_status & 0xff)?, + (si_status >> 8) as c_int, + ) + } + } + _ => return Err(Errno::EINVAL), + }; + + Ok(status) + } +} + +/// Wait for a process to change status +/// +/// See also [waitpid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitpid.html) +pub fn waitpid<P: Into<Option<Pid>>>( + pid: P, + options: Option<WaitPidFlag>, +) -> Result<WaitStatus> { + use self::WaitStatus::*; + + let mut status: i32 = 0; + + let option_bits = match options { + Some(bits) => bits.bits(), + None => 0, + }; + + let res = unsafe { + libc::waitpid( + pid.into().unwrap_or_else(|| Pid::from_raw(-1)).into(), + &mut status as *mut c_int, + option_bits, + ) + }; + + match Errno::result(res)? { + 0 => Ok(StillAlive), + res => WaitStatus::from_raw(Pid::from_raw(res), status), + } +} + +/// Wait for any child process to change status or a signal is received. +/// +/// See also [wait(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html) +pub fn wait() -> Result<WaitStatus> { + waitpid(None, None) +} + +/// The ID argument for `waitid` +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "haiku", + all(target_os = "linux", not(target_env = "uclibc")), +))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Id { + /// Wait for any child + All, + /// Wait for the child whose process ID matches the given PID + Pid(Pid), + /// Wait for the child whose process group ID matches the given PID + /// + /// If the PID is zero, the caller's process group is used since Linux 5.4. + PGid(Pid), + /// Wait for the child referred to by the given PID file descriptor + #[cfg(any(target_os = "android", target_os = "linux"))] + PIDFd(RawFd), +} + +/// Wait for a process to change status +/// +/// See also [waitid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitid.html) +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "haiku", + all(target_os = "linux", not(target_env = "uclibc")), +))] +pub fn waitid(id: Id, flags: WaitPidFlag) -> Result<WaitStatus> { + let (idtype, idval) = match id { + Id::All => (libc::P_ALL, 0), + Id::Pid(pid) => (libc::P_PID, pid.as_raw() as libc::id_t), + Id::PGid(pid) => (libc::P_PGID, pid.as_raw() as libc::id_t), + #[cfg(any(target_os = "android", target_os = "linux"))] + Id::PIDFd(fd) => (libc::P_PIDFD, fd as libc::id_t), + }; + + let siginfo = unsafe { + // Memory is zeroed rather than uninitialized, as not all platforms + // initialize the memory in the StillAlive case + let mut siginfo: libc::siginfo_t = std::mem::zeroed(); + Errno::result(libc::waitid(idtype, idval, &mut siginfo, flags.bits()))?; + siginfo + }; + + unsafe { WaitStatus::from_siginfo(&siginfo) } +} |