//! Rust friendly bindings to the various *nix system functions.
//!
//! Modules are structured according to the C header file that they would be
//! defined in.
//!
//! # Features
//!
//! Nix uses the following Cargo features to enable optional functionality.
//! They may be enabled in any combination.
//! * `acct` - Process accounting
//! * `aio` - POSIX AIO
//! * `dir` - Stuff relating to directory iteration
//! * `env` - Manipulate environment variables
//! * `event` - Event-driven APIs, like `kqueue` and `epoll`
//! * `fanotify` - Linux's `fanotify` filesystem events monitoring API
//! * `feature` - Query characteristics of the OS at runtime
//! * `fs` - File system functionality
//! * `hostname` - Get and set the system's hostname
//! * `inotify` - Linux's `inotify` file system notification API
//! * `ioctl` - The `ioctl` syscall, and wrappers for many specific instances
//! * `kmod` - Load and unload kernel modules
//! * `mman` - Stuff relating to memory management
//! * `mount` - Mount and unmount file systems
//! * `mqueue` - POSIX message queues
//! * `net` - Networking-related functionality
//! * `personality` - Set the process execution domain
//! * `poll` - APIs like `poll` and `select`
//! * `process` - Stuff relating to running processes
//! * `pthread` - POSIX threads
//! * `ptrace` - Process tracing and debugging
//! * `quota` - File system quotas
//! * `reboot` - Reboot the system
//! * `resource` - Process resource limits
//! * `sched` - Manipulate process's scheduling
//! * `socket` - Sockets, whether for networking or local use
//! * `signal` - Send and receive signals to processes
//! * `syslog` - System logging
//! * `term` - Terminal control APIs
//! * `time` - Query the operating system's clocks
//! * `ucontext` - User thread context
//! * `uio` - Vectored I/O
//! * `user` - Stuff relating to users and groups
//! * `zerocopy` - APIs like `sendfile` and `copy_file_range`
#![crate_name = "nix"]
#![cfg(unix)]
#![allow(non_camel_case_types)]
// A clear document is a good document no matter if it has a summary in its
// first paragraph or not.
#![allow(clippy::too_long_first_doc_paragraph)]
#![recursion_limit = "500"]
#![deny(unused)]
#![deny(unexpected_cfgs)]
#![allow(unused_macros)]
#![cfg_attr(
    not(all(
        feature = "acct",
        feature = "aio",
        feature = "dir",
        feature = "env",
        feature = "event",
        feature = "fanotify",
        feature = "feature",
        feature = "fs",
        feature = "hostname",
        feature = "inotify",
        feature = "ioctl",
        feature = "kmod",
        feature = "mman",
        feature = "mount",
        feature = "mqueue",
        feature = "net",
        feature = "personality",
        feature = "poll",
        feature = "process",
        feature = "pthread",
        feature = "ptrace",
        feature = "quota",
        feature = "reboot",
        feature = "resource",
        feature = "sched",
        feature = "socket",
        feature = "signal",
        feature = "syslog",
        feature = "term",
        feature = "time",
        feature = "ucontext",
        feature = "uio",
        feature = "user",
        feature = "zerocopy",
    )),
    allow(unused_imports)
)]
#![deny(unstable_features)]
#![deny(missing_copy_implementations)]
#![deny(missing_debug_implementations)]
#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(clippy::cast_ptr_alignment)]
#![deny(unsafe_op_in_unsafe_fn)]
// I found the change suggested by this rules could hurt code readability. I cannot
// remeber every type's default value, in such cases, it forces me to open
// the std doc to insepct the Default value, which is unnecessary with
// `.unwrap_or(value)`.
#![allow(clippy::unwrap_or_default)]

// Re-exported external crates
pub use libc;

// Private internal modules
#[macro_use]
mod macros;

// Public crates
#[cfg(not(target_os = "redox"))]
feature! {
    #![feature = "dir"]
    pub mod dir;
}
feature! {
    #![feature = "env"]
    pub mod env;
}
#[allow(missing_docs)]
pub mod errno;
feature! {
    #![feature = "feature"]

    #[deny(missing_docs)]
    pub mod features;
}
pub mod fcntl;
feature! {
    #![feature = "net"]

    #[cfg(any(linux_android,
              bsd,
              solarish,
              target_os = "hurd"))]
    #[deny(missing_docs)]
    pub mod ifaddrs;
    #[cfg(not(target_os = "redox"))]
    #[deny(missing_docs)]
    pub mod net;
}
#[cfg(linux_android)]
feature! {
    #![feature = "kmod"]
    pub mod kmod;
}
feature! {
    #![feature = "mount"]
    pub mod mount;
}
#[cfg(any(
    freebsdlike,
    all(target_os = "linux", not(target_env = "ohos")),
    target_os = "netbsd"
))]
feature! {
    #![feature = "mqueue"]
    pub mod mqueue;
}
feature! {
    #![feature = "poll"]
    pub mod poll;
}
#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))]
feature! {
    #![feature = "term"]
    #[deny(missing_docs)]
    pub mod pty;
}
feature! {
    #![feature = "sched"]
    pub mod sched;
}
pub mod sys;
feature! {
    #![feature = "time"]
    pub mod time;
}
// This can be implemented for other platforms as soon as libc
// provides bindings for them.
#[cfg(all(
    target_os = "linux",
    any(
        target_arch = "aarch64",
        target_arch = "s390x",
        target_arch = "x86",
        target_arch = "x86_64"
    )
))]
feature! {
    #![feature = "ucontext"]
    #[allow(missing_docs)]
    pub mod ucontext;
}
pub mod unistd;

#[cfg(any(feature = "poll", feature = "event"))]
mod poll_timeout;

#[cfg(any(
    target_os = "freebsd",
    target_os = "haiku",
    target_os = "linux",
    target_os = "netbsd",
    apple_targets
))]
feature! {
    #![feature = "process"]
    pub mod spawn;
}

feature! {
    #![feature = "syslog"]
    pub mod syslog;
}

use std::ffi::{CStr, CString, OsStr};
use std::mem::MaybeUninit;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::{ptr, result, slice};

use errno::Errno;

/// Nix Result Type
pub type Result<T> = result::Result<T, Errno>;

/// Nix's main error type.
///
/// It's a wrapper around Errno.  As such, it's very interoperable with
/// [`std::io::Error`], but it has the advantages of:
/// * `Clone`
/// * `Copy`
/// * `Eq`
/// * Small size
/// * Represents all of the system's errnos, instead of just the most common
///   ones.
pub type Error = Errno;

/// Common trait used to represent file system paths by many Nix functions.
pub trait NixPath {
    /// Is the path empty?
    fn is_empty(&self) -> bool;

    /// Length of the path in bytes
    fn len(&self) -> usize;

    /// Execute a function with this path as a `CStr`.
    ///
    /// Mostly used internally by Nix.
    fn with_nix_path<T, F>(&self, f: F) -> Result<T>
    where
        F: FnOnce(&CStr) -> T;
}

impl NixPath for str {
    fn is_empty(&self) -> bool {
        NixPath::is_empty(OsStr::new(self))
    }

    fn len(&self) -> usize {
        NixPath::len(OsStr::new(self))
    }

    fn with_nix_path<T, F>(&self, f: F) -> Result<T>
    where
        F: FnOnce(&CStr) -> T,
    {
        OsStr::new(self).with_nix_path(f)
    }
}

impl NixPath for OsStr {
    fn is_empty(&self) -> bool {
        self.as_bytes().is_empty()
    }

    fn len(&self) -> usize {
        self.as_bytes().len()
    }

    fn with_nix_path<T, F>(&self, f: F) -> Result<T>
    where
        F: FnOnce(&CStr) -> T,
    {
        self.as_bytes().with_nix_path(f)
    }
}

impl NixPath for CStr {
    fn is_empty(&self) -> bool {
        self.to_bytes().is_empty()
    }

    fn len(&self) -> usize {
        self.to_bytes().len()
    }

    fn with_nix_path<T, F>(&self, f: F) -> Result<T>
    where
        F: FnOnce(&CStr) -> T,
    {
        Ok(f(self))
    }
}

impl NixPath for [u8] {
    fn is_empty(&self) -> bool {
        self.is_empty()
    }

    fn len(&self) -> usize {
        self.len()
    }

    fn with_nix_path<T, F>(&self, f: F) -> Result<T>
    where
        F: FnOnce(&CStr) -> T,
    {
        // The real PATH_MAX is typically 4096, but it's statistically unlikely to have a path
        // longer than ~300 bytes. See the PR description to get stats for your own machine.
        // https://github.com/nix-rust/nix/pull/1656
        //
        // By being smaller than a memory page, we also avoid the compiler inserting a probe frame:
        // https://docs.rs/compiler_builtins/latest/compiler_builtins/probestack/index.html
        const MAX_STACK_ALLOCATION: usize = 1024;

        if self.len() >= MAX_STACK_ALLOCATION {
            return with_nix_path_allocating(self, f);
        }

        let mut buf = MaybeUninit::<[u8; MAX_STACK_ALLOCATION]>::uninit();
        let buf_ptr = buf.as_mut_ptr().cast();

        unsafe {
            ptr::copy_nonoverlapping(self.as_ptr(), buf_ptr, self.len());
            buf_ptr.add(self.len()).write(0);
        }

        match CStr::from_bytes_with_nul(unsafe {
            slice::from_raw_parts(buf_ptr, self.len() + 1)
        }) {
            Ok(s) => Ok(f(s)),
            Err(_) => Err(Errno::EINVAL),
        }
    }
}

#[cold]
#[inline(never)]
fn with_nix_path_allocating<T, F>(from: &[u8], f: F) -> Result<T>
where
    F: FnOnce(&CStr) -> T,
{
    match CString::new(from) {
        Ok(s) => Ok(f(&s)),
        Err(_) => Err(Errno::EINVAL),
    }
}

impl NixPath for Path {
    fn is_empty(&self) -> bool {
        NixPath::is_empty(self.as_os_str())
    }

    fn len(&self) -> usize {
        NixPath::len(self.as_os_str())
    }

    fn with_nix_path<T, F>(&self, f: F) -> Result<T>
    where
        F: FnOnce(&CStr) -> T,
    {
        self.as_os_str().with_nix_path(f)
    }
}

impl NixPath for PathBuf {
    fn is_empty(&self) -> bool {
        NixPath::is_empty(self.as_os_str())
    }

    fn len(&self) -> usize {
        NixPath::len(self.as_os_str())
    }

    fn with_nix_path<T, F>(&self, f: F) -> Result<T>
    where
        F: FnOnce(&CStr) -> T,
    {
        self.as_os_str().with_nix_path(f)
    }
}

/// Like `NixPath::with_nix_path()`, but allow the `path` argument to be optional.
///
/// A NULL pointer will be provided if `path.is_none()`.
#[cfg(any(
    all(apple_targets, feature = "mount"),
    all(linux_android, any(feature = "mount", feature = "fanotify"))
))]
pub(crate) fn with_opt_nix_path<P, T, F>(path: Option<&P>, f: F) -> Result<T>
where
    P: ?Sized + NixPath,
    F: FnOnce(*const libc::c_char) -> T,
{
    match path {
        Some(path) => path.with_nix_path(|p_str| f(p_str.as_ptr())),
        None => Ok(f(ptr::null())),
    }
}
