#![cfg(any(target_os = "linux", target_os = "android"))]
#![doc = include_str!("../README.md")]
#![doc(html_root_url = "https://docs.rs/selinux/0.6.3")]
//
// https://rust-lang.github.io/api-guidelines/checklist.html
//
#![cfg_attr(test, allow(clippy::unwrap_used))]

#[cfg(test)]
mod tests;

/// Access Vector Cache.
pub mod avc;
/// SELinux call backs.
pub mod call_back;
/// Restore file(s) default SELinux security contexts.
pub mod context_restore;
/// Errors.
pub mod errors;
/// Labeling files.
pub mod label;
/// SELinux paths.
pub mod path;
/// SELinux policies.
pub mod policy;
/// Utilities.
pub mod utils;

extern crate alloc;

use alloc::borrow::Cow;
use alloc::ffi::CString;
use core::ffi::CStr;
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::{cmp, fmt, mem, ptr, slice, str};
use std::collections::{HashMap, hash_map};
use std::io;
use std::os::raw::{c_char, c_int, c_uint, c_void};
use std::os::unix::io::AsRawFd;
use std::path::Path;

use selinux_sys::pid_t;

#[macro_use]
extern crate bitflags;

use errors::{Error, Result};
use utils::{
    CAllocatedBlock, OptionalNativeFunctions, c_str_to_non_null_ptr, os_str_to_c_string,
    ret_val_to_result, ret_val_to_result_with_path, str_to_c_string,
};

/// Red, green and blue components of a color.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct RGB {
    /// Red component.
    pub red: u8,
    /// Green component.
    pub green: u8,
    /// Blue component.
    pub blue: u8,
}

impl RGB {
    /// Create a new instance.
    #[must_use]
    pub fn new(red: u8, green: u8, blue: u8) -> Self {
        Self { red, green, blue }
    }
}

/// Background and foreground colors.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct LayerColors {
    /// Background color.
    pub background: RGB,
    /// Foreground color.
    pub foreground: RGB,
}

impl LayerColors {
    /// Create a new instance.
    #[must_use]
    pub fn new(background: RGB, foreground: RGB) -> Self {
        Self {
            background,
            foreground,
        }
    }
}

/// Colors of a security context.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct SecurityContextColors {
    /// Background and foreground colors of SELinux user.
    pub user: LayerColors,
    /// Background and foreground colors of SELinux role.
    pub role: LayerColors,
    /// Background and foreground colors of SELinux type.
    pub the_type: LayerColors,
    /// Background and foreground colors of SELinux range.
    pub range: LayerColors,
}

impl SecurityContextColors {
    /// Create a new instance.
    #[must_use]
    pub fn new(
        user: LayerColors,
        role: LayerColors,
        the_type: LayerColors,
        range: LayerColors,
    ) -> Self {
        Self {
            user,
            role,
            the_type,
            range,
        }
    }
}

/// SELinux security context.
#[derive(Debug, Eq)]
pub struct SecurityContext<'context> {
    context: ptr::NonNull<c_char>,
    size: Option<usize>,
    is_raw: bool,
    context_owned: bool,
    _phantom_data: PhantomData<&'context c_char>,
}

impl<'context> SecurityContext<'context> {
    /// Return `false` if security context translation must be performed.
    #[must_use]
    pub fn is_raw_format(&self) -> bool {
        self.is_raw
    }

    /// Return the managed raw pointer to [`c_char`].
    #[must_use]
    pub fn as_ptr(&self) -> *const c_char {
        self.context.as_ptr()
    }

    /// Return the managed raw pointer to [`c_char`].
    #[must_use]
    pub fn as_mut_ptr(&mut self) -> *mut c_char {
        self.context.as_ptr()
    }

    /// Return the security context's byte slice.
    #[must_use]
    pub fn as_bytes(&self) -> &[u8] {
        self.size.map_or_else(
            || unsafe { CStr::from_ptr(self.context.as_ptr()).to_bytes() },
            |size| unsafe { slice::from_raw_parts(self.context.as_ptr().cast(), size) },
        )
    }

    /// Return the string value of this security context.
    ///
    /// If the context is empty, then this returns `Ok(None)`.
    pub fn to_c_string(&self) -> Result<Option<Cow<'_, CStr>>> {
        if let Some(size) = self.size {
            let bytes = unsafe { slice::from_raw_parts(self.context.as_ptr().cast(), size) };
            if bytes.is_empty() {
                Ok(None)
            } else if bytes.last().copied() == Some(0) {
                if let Ok(result) = CStr::from_bytes_with_nul(bytes) {
                    Ok(Some(Cow::Borrowed(result)))
                } else {
                    let op = "CStr::from_bytes_with_nul()";
                    Err(Error::from_io(op, io::ErrorKind::InvalidData.into()))
                }
            } else if let Ok(result) = CString::new(bytes) {
                Ok(Some(Cow::Owned(result)))
            } else {
                let op = "CString::new()";
                Err(Error::from_io(op, io::ErrorKind::InvalidData.into()))
            }
        } else {
            let result = unsafe { CStr::from_ptr(self.context.as_ptr()) };
            Ok(Some(Cow::Borrowed(result)))
        }
    }

    /// Return the security context identified by `context`.
    ///
    /// ⚠️ The returned instance does **NOT** own the provided context.
    /// When the returned instance get dropped, it will **NOT** deallocate
    /// the provided context.
    #[must_use]
    pub fn from_c_str(c_context: &'context CStr, raw_format: bool) -> SecurityContext<'context> {
        Self {
            context: c_str_to_non_null_ptr(c_context),
            size: Some(c_context.to_bytes().len()),
            is_raw: raw_format,
            context_owned: false,
            _phantom_data: PhantomData,
        }
    }

    /// Return the security context of the current process.
    ///
    /// See: `getcon()`.
    #[doc(alias = "getcon")]
    pub fn current(raw_format: bool) -> Result<Self> {
        let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if raw_format {
            (selinux_sys::getcon_raw, "getcon_raw()")
        } else {
            (selinux_sys::getcon, "getcon()")
        };

        let mut context: *mut c_char = ptr::null_mut();
        let r = unsafe { proc(&raw mut context) };
        Self::from_result(proc_name, r, context, raw_format)
    }

    /// Return the security context of the current process before the last exec.
    ///
    /// See: `getprevcon()`.
    #[doc(alias = "getprevcon")]
    pub fn previous(raw_format: bool) -> Result<Self> {
        Self::previous_of_process(None, raw_format)
    }

    /// Return the security context, of the current or specified process, before the last exec.
    ///
    /// If `process_id` is `None`, then the current process is queried.
    ///
    /// Specifying a particular process id (`process_id.is_some()`) requires `libselinux` version
    /// `3.5` or later.
    ///
    /// See: `getprevcon()`, `getpidprevcon()`.
    #[doc(alias = "getprevcon")]
    #[doc(alias = "getpidprevcon")]
    pub fn previous_of_process(process_id: Option<pid_t>, raw_format: bool) -> Result<Self> {
        let mut context: *mut c_char = ptr::null_mut();

        if let Some(process_id) = process_id {
            let onf = OptionalNativeFunctions::get();
            let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) = if raw_format {
                (onf.getpidprevcon_raw, "getpidprevcon_raw()")
            } else {
                (onf.getpidprevcon, "getpidprevcon()")
            };

            let r = unsafe { proc(process_id, &raw mut context) };
            Self::from_result_with_pid(proc_name, r, context, process_id, raw_format)
        } else {
            let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if raw_format {
                (selinux_sys::getprevcon_raw, "getprevcon_raw()")
            } else {
                (selinux_sys::getprevcon, "getprevcon()")
            };

            let r = unsafe { proc(&raw mut context) };
            Self::from_result(proc_name, r, context, raw_format)
        }
    }

    /// Set the current security context of the process to this context.
    ///
    /// See: `setcon()`.
    #[doc(alias = "setcon")]
    pub fn set_as_current(&self) -> Result<()> {
        let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if self.is_raw {
            (selinux_sys::setcon_raw, "setcon_raw()")
        } else {
            (selinux_sys::setcon, "setcon()")
        };

        ret_val_to_result(proc_name, unsafe { proc(self.context.as_ptr()) })
    }

    /// Get the context of a kernel initial security identifier specified by name.
    ///
    /// See: `security_get_initial_context()`.
    #[doc(alias = "security_get_initial_context")]
    pub fn of_initial_kernel_context(name: &str, raw_format: bool) -> Result<Self> {
        let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) = if raw_format {
            let proc_name = "security_get_initial_context_raw()";
            (selinux_sys::security_get_initial_context_raw, proc_name)
        } else {
            let proc_name = "security_get_initial_context()";
            (selinux_sys::security_get_initial_context, proc_name)
        };

        let c_name = str_to_c_string(name)?;
        let mut context: *mut c_char = ptr::null_mut();
        let r = unsafe { proc(c_name.as_ptr(), &raw mut context) };
        Self::from_result_with_name(proc_name, r, context, name, raw_format)
    }

    /// Get the default SELinux security context for the specified media type
    /// from the policy.
    ///
    /// See: `matchmediacon()`.
    #[doc(alias = "matchmediacon")]
    pub fn of_media_type(name: &str) -> Result<Self> {
        let c_name = str_to_c_string(name)?;
        let mut context: *mut c_char = ptr::null_mut();
        let r = unsafe { selinux_sys::matchmediacon(c_name.as_ptr(), &raw mut context) };
        Self::from_result_with_name("matchmediacon()", r, context, name, false)
    }

    /// Return the process context for the specified process identifier.
    ///
    /// See: `getpidcon()`.
    #[doc(alias = "getpidcon")]
    pub fn of_process(process_id: pid_t, raw_format: bool) -> Result<Self> {
        let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) = if raw_format {
            (selinux_sys::getpidcon_raw, "getpidcon_raw()")
        } else {
            (selinux_sys::getpidcon, "getpidcon()")
        };

        let mut context: *mut c_char = ptr::null_mut();
        let r = unsafe { proc(process_id, &raw mut context) };
        Self::from_result_with_pid(proc_name, r, context, process_id, raw_format)
    }

    /// Perform context translation from the human-readable format (translated)
    /// to the internal system format (raw).
    ///
    /// See: `selinux_trans_to_raw_context()`.
    #[doc(alias = "selinux_trans_to_raw_context")]
    pub fn to_raw_format(&self) -> Result<Self> {
        if self.is_raw {
            return Err(Error::UnexpectedSecurityContextFormat);
        }

        let mut context: *mut c_char = ptr::null_mut();
        let r = unsafe {
            selinux_sys::selinux_trans_to_raw_context(self.context.as_ptr(), &raw mut context)
        };
        Self::from_result("selinux_trans_to_raw_context()", r, context, true)
    }

    /// Perform context translation from the internal system format (raw) to
    /// the human-readable format (translated).
    ///
    /// See: `selinux_raw_to_trans_context()`.
    #[doc(alias = "selinux_raw_to_trans_context")]
    pub fn to_translated_format(&self) -> Result<Self> {
        if !self.is_raw {
            return Err(Error::UnexpectedSecurityContextFormat);
        }

        let mut context: *mut c_char = ptr::null_mut();
        let r = unsafe {
            selinux_sys::selinux_raw_to_trans_context(self.context.as_ptr(), &raw mut context)
        };
        Self::from_result("selinux_raw_to_trans_context()", r, context, false)
    }

    /// Ask the user to manually enter a context as a fallback if a list of
    /// authorized contexts could not be obtained.
    ///
    /// See: `manual_user_enter_context()`.
    #[doc(alias = "manual_user_enter_context")]
    pub fn of_se_user_with_selected_context(se_user: &str, raw_format: bool) -> Result<Self> {
        let mut context: *mut c_char = ptr::null_mut();
        let c_se_user = str_to_c_string(se_user)?;
        let r =
            unsafe { selinux_sys::manual_user_enter_context(c_se_user.as_ptr(), &raw mut context) };
        Self::from_result("manual_user_enter_context()", r, context, raw_format)
    }

    /// Obtain a context, for the specified SELinux user identity, that is
    /// reachable from the specified `reachable_from_context`.
    ///
    /// See: `get_default_context()`, `get_default_context_with_level()`,
    /// `get_default_context_with_role()`, `get_default_context_with_rolelevel()`.
    #[doc(alias = "get_default_context")]
    #[doc(alias = "get_default_context_with_level")]
    #[doc(alias = "get_default_context_with_role")]
    #[doc(alias = "get_default_context_with_rolelevel")]
    pub fn default_for_se_user(
        se_user: &str,
        role: Option<&str>,
        level: Option<&str>,
        reachable_from_context: Option<&Self>,
        raw_format: bool,
    ) -> Result<Self> {
        let c_se_user = str_to_c_string(se_user)?;

        let c_role = if let Some(role) = role {
            str_to_c_string(role).map(Some)?
        } else {
            None
        };

        let c_level = if let Some(level) = level {
            str_to_c_string(level).map(Some)?
        } else {
            None
        };

        let reachable_from_context =
            reachable_from_context.map_or(ptr::null_mut(), |c| c.context.as_ptr());

        let mut context: *mut c_char = ptr::null_mut();

        let (r, proc_name) = unsafe {
            match (c_role, c_level) {
                (None, None) => (
                    selinux_sys::get_default_context(
                        c_se_user.as_ptr(),
                        reachable_from_context,
                        &raw mut context,
                    ),
                    "get_default_context()",
                ),

                (None, Some(c_level)) => (
                    selinux_sys::get_default_context_with_level(
                        c_se_user.as_ptr(),
                        c_level.as_ptr(),
                        reachable_from_context,
                        &raw mut context,
                    ),
                    "get_default_context_with_level()",
                ),

                (Some(c_role), None) => (
                    selinux_sys::get_default_context_with_role(
                        c_se_user.as_ptr(),
                        c_role.as_ptr(),
                        reachable_from_context,
                        &raw mut context,
                    ),
                    "get_default_context_with_role()",
                ),

                (Some(c_role), Some(c_level)) => (
                    selinux_sys::get_default_context_with_rolelevel(
                        c_se_user.as_ptr(),
                        c_role.as_ptr(),
                        c_level.as_ptr(),
                        reachable_from_context,
                        &raw mut context,
                    ),
                    "get_default_context_with_rolelevel()",
                ),
            }
        };

        Self::from_result(proc_name, r, context, raw_format)
    }

    /// Get the context used for executing a new process.
    ///
    /// See: `getexeccon()`.
    #[doc(alias = "getexeccon")]
    pub fn of_next_exec(raw_format: bool) -> Result<Option<Self>> {
        let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if raw_format {
            (selinux_sys::getexeccon_raw, "getexeccon_raw()")
        } else {
            (selinux_sys::getexeccon, "getexeccon()")
        };

        Self::of_new_operations(proc, proc_name, raw_format)
    }

    /// Reset the context, used for the next `execve()` call, to the default
    /// policy behavior.
    ///
    /// See: `setexeccon()`.
    #[doc(alias = "setexeccon")]
    pub fn set_default_context_for_next_exec() -> Result<()> {
        let r = unsafe { selinux_sys::setexeccon(ptr::null()) };
        ret_val_to_result("setexeccon()", r)
    }

    /// Set the context used for the next `execve()` call.
    ///
    /// See: `setexeccon()`.
    #[doc(alias = "setexeccon")]
    pub fn set_for_next_exec(&self) -> Result<()> {
        let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if self.is_raw {
            (selinux_sys::setexeccon_raw, "setexeccon_raw()")
        } else {
            (selinux_sys::setexeccon, "setexeccon()")
        };

        ret_val_to_result(proc_name, unsafe { proc(self.context.as_ptr()) })
    }

    /// Get the context used for creating a new file system object.
    ///
    /// See: `getfscreatecon()`.
    #[doc(alias = "getfscreatecon")]
    pub fn of_new_file_system_objects(raw_format: bool) -> Result<Option<Self>> {
        let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if raw_format {
            (selinux_sys::getfscreatecon_raw, "getfscreatecon_raw()")
        } else {
            (selinux_sys::getfscreatecon, "getfscreatecon()")
        };

        Self::of_new_operations(proc, proc_name, raw_format)
    }

    /// Reset the context, used for creating a new file system object, to the
    /// default policy behavior.
    ///
    /// See: `setfscreatecon()`.
    #[doc(alias = "setfscreatecon")]
    pub fn set_default_context_for_new_file_system_objects() -> Result<()> {
        let r = unsafe { selinux_sys::setfscreatecon(ptr::null()) };
        ret_val_to_result("setfscreatecon()", r)
    }

    /// Set the context used for creating a new file system object.
    ///
    /// See: `setfscreatecon()`.
    #[doc(alias = "setfscreatecon")]
    pub fn set_for_new_file_system_objects(&self, raw_format: bool) -> Result<()> {
        let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if raw_format {
            (selinux_sys::setfscreatecon_raw, "setfscreatecon_raw()")
        } else {
            (selinux_sys::setfscreatecon, "setfscreatecon()")
        };

        ret_val_to_result(proc_name, unsafe { proc(self.context.as_ptr()) })
    }

    /// Get the context used for creating a new kernel key ring.
    ///
    /// See: `getkeycreatecon()`.
    #[doc(alias = "getkeycreatecon")]
    pub fn of_new_kernel_key_rings(raw_format: bool) -> Result<Option<Self>> {
        let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if raw_format {
            (selinux_sys::getkeycreatecon_raw, "getkeycreatecon_raw()")
        } else {
            (selinux_sys::getkeycreatecon, "getkeycreatecon()")
        };

        Self::of_new_operations(proc, proc_name, raw_format)
    }

    /// Set the context, used for creating a new kernel key ring, to the
    /// default policy behavior.
    ///
    /// See: `setkeycreatecon()`.
    #[doc(alias = "setkeycreatecon")]
    pub fn set_default_context_for_new_kernel_key_rings() -> Result<()> {
        let r = unsafe { selinux_sys::setkeycreatecon(ptr::null()) };
        ret_val_to_result("setkeycreatecon()", r)
    }

    /// Set the context used for creating a new kernel key ring.
    ///
    /// See: `setkeycreatecon()`.
    #[doc(alias = "setkeycreatecon")]
    pub fn set_for_new_kernel_key_rings(&self, raw_format: bool) -> Result<()> {
        let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if raw_format {
            (selinux_sys::setkeycreatecon_raw, "setkeycreatecon_raw()")
        } else {
            (selinux_sys::setkeycreatecon, "setkeycreatecon()")
        };

        ret_val_to_result(proc_name, unsafe { proc(self.context.as_ptr()) })
    }

    /// Get the context used for creating a new labeled network socket.
    ///
    /// See: `getsockcreatecon()`.
    #[doc(alias = "getsockcreatecon")]
    pub fn of_new_labeled_sockets(raw_format: bool) -> Result<Option<Self>> {
        let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if raw_format {
            (selinux_sys::getsockcreatecon_raw, "getsockcreatecon_raw()")
        } else {
            (selinux_sys::getsockcreatecon, "getsockcreatecon()")
        };

        Self::of_new_operations(proc, proc_name, raw_format)
    }

    /// Set the context, used for creating a new labeled network sockets, to the
    /// default policy behavior.
    ///
    /// See: `setsockcreatecon()`.
    #[doc(alias = "setsockcreatecon")]
    pub fn set_default_context_for_new_labeled_sockets() -> Result<()> {
        let r = unsafe { selinux_sys::setsockcreatecon(ptr::null()) };
        ret_val_to_result("setsockcreatecon()", r)
    }

    /// Set the context used for creating a new labeled network sockets.
    ///
    /// See: `setsockcreatecon()`.
    #[doc(alias = "setsockcreatecon")]
    pub fn set_for_new_labeled_sockets(&self, raw_format: bool) -> Result<()> {
        let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if raw_format {
            (selinux_sys::setsockcreatecon_raw, "setsockcreatecon_raw()")
        } else {
            (selinux_sys::setsockcreatecon, "setsockcreatecon()")
        };

        ret_val_to_result(proc_name, unsafe { proc(self.context.as_ptr()) })
    }

    /// Get the context associated with the given path in the file system.
    ///
    /// See: `lgetfilecon()`, `getfilecon()`.
    #[doc(alias = "lgetfilecon")]
    #[doc(alias = "getfilecon")]
    pub fn of_path(
        path: impl AsRef<Path>,
        follow_symbolic_links: bool,
        raw_format: bool,
    ) -> Result<Option<Self>> {
        let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) =
            match (follow_symbolic_links, raw_format) {
                (false, false) => (selinux_sys::lgetfilecon, "lgetfilecon()"),
                (false, true) => (selinux_sys::lgetfilecon_raw, "lgetfilecon_raw()"),
                (true, false) => (selinux_sys::getfilecon, "getfilecon()"),
                (true, true) => (selinux_sys::getfilecon_raw, "getfilecon_raw()"),
            };

        let c_path = os_str_to_c_string(path.as_ref().as_os_str())?;
        let mut context: *mut c_char = ptr::null_mut();
        let r: c_int = unsafe { proc(c_path.as_ptr(), &raw mut context) };
        if r == -1_i32 {
            let err = io::Error::last_os_error();
            if let Some(libc::ENODATA) = err.raw_os_error() {
                Ok(None)
            } else {
                Err(Error::from_io_path(proc_name, path.as_ref(), err))
            }
        } else {
            Ok(ptr::NonNull::new(context).map(|context| {
                #[allow(clippy::cast_sign_loss)]
                let size = (r >= 0_i32).then_some(r as c_uint);
                Self::from_ptr(context, size, raw_format)
            }))
        }
    }

    /// Set the file context to the system defaults.
    ///
    /// See: `selinux_lsetfilecon_default()`.
    #[doc(alias = "selinux_lsetfilecon_default")]
    pub fn set_default_for_path(path: impl AsRef<Path>) -> Result<()> {
        let c_path = os_str_to_c_string(path.as_ref().as_os_str())?;
        let r = unsafe { selinux_sys::selinux_lsetfilecon_default(c_path.as_ptr()) };
        ret_val_to_result_with_path("selinux_lsetfilecon_default()", r, path.as_ref())
    }

    /// Set the SELinux security context of a file system object.
    ///
    /// See: `lsetfilecon()`, `setfilecon()`.
    #[doc(alias = "lsetfilecon")]
    #[doc(alias = "setfilecon")]
    pub fn set_for_path(
        &self,
        path: impl AsRef<Path>,
        follow_symbolic_links: bool,
        raw_format: bool,
    ) -> Result<()> {
        let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) =
            match (follow_symbolic_links, raw_format) {
                (false, false) => (selinux_sys::lsetfilecon, "lsetfilecon()"),
                (false, true) => (selinux_sys::lsetfilecon_raw, "lsetfilecon_raw()"),
                (true, false) => (selinux_sys::setfilecon, "setfilecon()"),
                (true, true) => (selinux_sys::setfilecon_raw, "setfilecon_raw()"),
            };

        let c_path = os_str_to_c_string(path.as_ref().as_os_str())?;
        let r = unsafe { proc(c_path.as_ptr(), self.context.as_ptr()) };
        ret_val_to_result_with_path(proc_name, r, path.as_ref())
    }

    /// Get the SELinux security context of a file system object.
    ///
    /// See: `fgetfilecon()`.
    #[doc(alias = "fgetfilecon")]
    pub fn of_file<T>(fd: &T, raw_format: bool) -> Result<Option<Self>>
    where
        T: AsRawFd,
    {
        let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) = if raw_format {
            (selinux_sys::fgetfilecon_raw, "fgetfilecon_raw()")
        } else {
            (selinux_sys::fgetfilecon, "fgetfilecon()")
        };

        let mut context: *mut c_char = ptr::null_mut();
        let r: c_int = unsafe { proc(fd.as_raw_fd(), &raw mut context) };
        if r == -1_i32 {
            let err = io::Error::last_os_error();
            if let Some(libc::ENODATA) = err.raw_os_error() {
                Ok(None)
            } else {
                Err(Error::from_io(proc_name, err))
            }
        } else {
            Ok(ptr::NonNull::new(context).map(|context| {
                #[allow(clippy::cast_sign_loss, clippy::as_conversions)]
                let size = (r >= 0_i32).then_some(r as c_uint);
                Self::from_ptr(context, size, raw_format)
            }))
        }
    }

    /// Set the SELinux security context of the file system object identified
    /// by an open file descriptor.
    ///
    /// See: `fsetfilecon()`.
    #[doc(alias = "fsetfilecon")]
    pub fn set_for_file<T>(&self, fd: &T) -> Result<()>
    where
        T: AsRawFd,
    {
        let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) = if self.is_raw {
            (selinux_sys::fsetfilecon_raw, "fsetfilecon_raw()")
        } else {
            (selinux_sys::fsetfilecon, "fsetfilecon()")
        };

        let r = unsafe { proc(fd.as_raw_fd(), self.context.as_ptr()) };
        ret_val_to_result(proc_name, r)
    }

    /// Set the SELinux security context of the peer socket identified by an
    /// open file descriptor.
    ///
    /// See: `getpeercon()`.
    #[doc(alias = "getpeercon")]
    pub fn of_peer_socket<T>(socket: &T, raw_format: bool) -> Result<Self>
    where
        T: AsRawFd,
    {
        let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) = if raw_format {
            (selinux_sys::getpeercon_raw, "getpeercon_raw()")
        } else {
            (selinux_sys::getpeercon, "getpeercon()")
        };

        let mut context: *mut c_char = ptr::null_mut();
        let r = unsafe { proc(socket.as_raw_fd(), &raw mut context) };
        Self::from_result(proc_name, r, context, raw_format)
    }

    /// Return whether the policy permits this source context to access
    /// `target_context` via `target_class` with the requested access vector.
    ///
    /// See: `security_compute_av_flags()`.
    #[doc(alias = "security_compute_av_flags")]
    pub fn query_access_decision(
        &self,
        target_context: &Self,
        target_class: SecurityClass,
        requested_access: selinux_sys::access_vector_t,
    ) -> Result<selinux_sys::av_decision> {
        if self.is_raw != target_context.is_raw {
            return Err(Error::SecurityContextFormatMismatch);
        }

        let (proc, proc_name): (unsafe extern "C" fn(_, _, _, _, _) -> _, _) = if self.is_raw {
            let proc_name = "security_compute_av_flags_raw()";
            (selinux_sys::security_compute_av_flags_raw, proc_name)
        } else {
            let proc_name = "security_compute_av_flags()";
            (selinux_sys::security_compute_av_flags, proc_name)
        };

        let mut result = MaybeUninit::<selinux_sys::av_decision>::uninit();
        let r: c_int = unsafe {
            proc(
                self.context.as_ptr(),
                target_context.context.as_ptr(),
                target_class.0,
                requested_access,
                result.as_mut_ptr(),
            )
        };

        if r == -1_i32 {
            Err(Error::last_io_error(proc_name))
        } else {
            Ok(unsafe { result.assume_init() })
        }
    }

    /// Compute a context to use for labeling a new named object in a particular
    /// class based on a SID pair.
    ///
    /// See: `security_compute_create_name()`.
    #[doc(alias = "security_compute_create_name")]
    pub fn of_labeling_decision(
        &self,
        target_context: &Self,
        target_class: SecurityClass,
        object_name: &str,
    ) -> Result<Self> {
        if self.is_raw != target_context.is_raw {
            return Err(Error::SecurityContextFormatMismatch);
        }

        let (proc, proc_name): (unsafe extern "C" fn(_, _, _, _, _) -> _, _) = if self.is_raw {
            let proc_name = "security_compute_create_name_raw()";
            (selinux_sys::security_compute_create_name_raw, proc_name)
        } else {
            let proc_name = "security_compute_create_name()";
            (selinux_sys::security_compute_create_name, proc_name)
        };

        let c_object_name = str_to_c_string(object_name)?;
        let mut context: *mut c_char = ptr::null_mut();
        let r = unsafe {
            proc(
                self.context.as_ptr(),
                target_context.context.as_ptr(),
                target_class.0,
                c_object_name.as_ptr(),
                &raw mut context,
            )
        };

        Self::from_result_with_name(proc_name, r, context, object_name, self.is_raw)
    }

    /// Compute the new context to use when relabeling an object.
    ///
    /// See: `security_compute_relabel()`.
    #[doc(alias = "security_compute_relabel")]
    pub fn of_relabeling_decision(
        &self,
        target_context: &Self,
        target_class: SecurityClass,
    ) -> Result<Self> {
        if self.is_raw != target_context.is_raw {
            return Err(Error::SecurityContextFormatMismatch);
        }

        let (proc, proc_name): (unsafe extern "C" fn(_, _, _, _) -> _, _) = if self.is_raw {
            let proc_name = "security_compute_relabel_raw()";
            (selinux_sys::security_compute_relabel_raw, proc_name)
        } else {
            let proc_name = "security_compute_relabel()";
            (selinux_sys::security_compute_relabel, proc_name)
        };

        let mut context: *mut c_char = ptr::null_mut();
        let r = unsafe {
            proc(
                self.context.as_ptr(),
                target_context.context.as_ptr(),
                target_class.0,
                &raw mut context,
            )
        };

        Self::from_result(proc_name, r, context, self.is_raw)
    }

    /// Compute the context to use when labeling a polyinstantiated
    /// object instance.
    ///
    /// See: `security_compute_member()`.
    #[doc(alias = "security_compute_member")]
    pub fn of_polyinstantiation_member_decision(
        &self,
        target_context: &Self,
        target_class: SecurityClass,
    ) -> Result<Self> {
        if self.is_raw != target_context.is_raw {
            return Err(Error::SecurityContextFormatMismatch);
        }

        let (proc, proc_name): (unsafe extern "C" fn(_, _, _, _) -> _, _) = if self.is_raw {
            let proc_name = "security_compute_member_raw()";
            (selinux_sys::security_compute_member_raw, proc_name)
        } else {
            let proc_name = "security_compute_member()";
            (selinux_sys::security_compute_member, proc_name)
        };

        let mut context: *mut c_char = ptr::null_mut();
        let r = unsafe {
            proc(
                self.context.as_ptr(),
                target_context.context.as_ptr(),
                target_class.0,
                &raw mut context,
            )
        };

        Self::from_result(proc_name, r, context, self.is_raw)
    }

    /// Determine if a transition from this context to `new_context` using
    /// `target_context` as the object is valid for object class `target_class`.
    ///
    /// This checks against the `mlsvalidatetrans` and `validatetrans`
    /// constraints in the loaded policy.
    ///
    /// This function requires `libselinux` version `3.0` or later.
    ///
    /// See: `security_validatetrans()`.
    #[doc(alias = "security_validatetrans")]
    pub fn validate_transition(
        &self,
        target_context: &Self,
        target_class: SecurityClass,
        new_context: &Self,
    ) -> Result<()> {
        if self.is_raw != target_context.is_raw {
            return Err(Error::SecurityContextFormatMismatch);
        }

        let onf = OptionalNativeFunctions::get();
        let (proc, proc_name) = if self.is_raw {
            let proc_name = "security_validatetrans_raw()";
            (onf.security_validatetrans_raw, proc_name)
        } else {
            let proc_name = "security_validatetrans()";
            (onf.security_validatetrans, proc_name)
        };

        let r = unsafe {
            proc(
                self.context.as_ptr(),
                target_context.context.as_ptr(),
                target_class.0,
                new_context.context.as_ptr(),
            )
        };

        ret_val_to_result(proc_name, r)
    }

    /// Check the validity of an SELinux context.
    ///
    /// See: `security_check_context()`, `is_selinux_enabled()`.
    #[doc(alias = "security_check_context")]
    #[doc(alias = "is_selinux_enabled")]
    #[must_use]
    pub fn check(&self) -> Option<bool> {
        let proc: unsafe extern "C" fn(_) -> _ = if self.is_raw {
            selinux_sys::security_check_context_raw
        } else {
            selinux_sys::security_check_context
        };

        if unsafe { proc(self.context.as_ptr()) } == -1_i32 {
            if unsafe { selinux_sys::is_selinux_enabled() } == 0_i32 {
                None
            } else {
                Some(false)
            }
        } else {
            Some(true)
        }
    }

    /// Canonicalize this security context.
    ///
    /// See: `security_canonicalize_context()`.
    #[doc(alias = "security_canonicalize_context")]
    pub fn canonicalize(&self) -> Result<Self> {
        let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) = if self.is_raw {
            let proc_name = "security_canonicalize_context_raw()";
            (selinux_sys::security_canonicalize_context_raw, proc_name)
        } else {
            let proc_name = "security_canonicalize_context()";
            (selinux_sys::security_canonicalize_context, proc_name)
        };

        let mut context: *mut c_char = ptr::null_mut();
        let r = unsafe { proc(self.context.as_ptr(), &raw mut context) };
        Self::from_result(proc_name, r, context, self.is_raw)
    }

    /// Check if this context has the access permission for the specified class
    /// on the target context.
    ///
    /// See: `selinux_check_access()`.
    #[doc(alias = "selinux_check_access")]
    #[allow(clippy::not_unsafe_ptr_arg_deref)]
    pub fn check_access(
        &self,
        target_context: &Self,
        target_class: &str,
        requested_permission: &str,
        audit_data: *mut c_void,
    ) -> Result<bool> {
        let c_target_class = str_to_c_string(target_class)?;
        let c_requested_permission = str_to_c_string(requested_permission)?;

        let r: c_int = unsafe {
            selinux_sys::selinux_check_access(
                self.context.as_ptr(),
                target_context.context.as_ptr(),
                c_target_class.as_ptr(),
                c_requested_permission.as_ptr(),
                audit_data,
            )
        };

        Ok(r == 0_i32)
    }

    /// Check whether a SELinux tty security context is defined as
    /// a securetty context.
    ///
    /// See: `selinux_check_securetty_context()`.
    #[doc(alias = "selinux_check_securetty_context")]
    #[must_use]
    pub fn check_securetty_context(&self) -> bool {
        unsafe { selinux_sys::selinux_check_securetty_context(self.context.as_ptr()) == 0_i32 }
    }

    /// Check whether SELinux context type is customizable by the administrator.
    ///
    /// See: `is_context_customizable()`.
    #[doc(alias = "is_context_customizable")]
    pub fn is_customizable(&self) -> Result<bool> {
        let r: c_int = unsafe { selinux_sys::is_context_customizable(self.context.as_ptr()) };
        if r == -1_i32 {
            Err(Error::last_io_error("is_context_customizable()"))
        } else {
            Ok(r != 0_i32)
        }
    }

    /// Return the color string for this SELinux security context.
    ///
    /// See: `selinux_raw_context_to_color()`.
    #[doc(alias = "selinux_raw_context_to_color")]
    pub fn to_color(&self) -> Result<SecurityContextColors> {
        if !self.is_raw {
            let raw_context = self.to_raw_format()?;
            return raw_context.to_color();
        }

        let mut color_ptr: *mut c_char = ptr::null_mut();
        let r: c_int = unsafe {
            selinux_sys::selinux_raw_context_to_color(self.context.as_ptr(), &raw mut color_ptr)
        };

        if r == -1_i32 {
            Err(Error::last_io_error("selinux_raw_context_to_color()"))
        } else {
            CAllocatedBlock::new(color_ptr).map_or_else(
                || {
                    let err = io::ErrorKind::InvalidData.into();
                    Err(Error::from_io("selinux_raw_context_to_color()", err))
                },
                |c_color| Self::parse_context_color(c_color.as_c_str().to_bytes()),
            )
        }
    }

    /// Compare this SELinux security context with another one, excluding
    /// the `user` component.
    ///
    /// See: `selinux_file_context_cmp()`.
    #[doc(alias = "selinux_file_context_cmp")]
    #[must_use]
    pub fn compare_user_insensitive(&self, other: &Self) -> cmp::Ordering {
        let r: c_int = unsafe {
            selinux_sys::selinux_file_context_cmp(self.context.as_ptr(), other.context.as_ptr())
        };

        r.cmp(&0_i32)
    }

    /// Compare the SELinux security context on disk to the default security
    /// context required by the policy file contexts file.
    ///
    /// See: `selinux_file_context_verify()`.
    #[doc(alias = "selinux_file_context_verify")]
    pub fn verify_file_context(
        path: impl AsRef<Path>,
        mode: Option<FileAccessMode>,
    ) -> Result<bool> {
        let c_path = os_str_to_c_string(path.as_ref().as_os_str())?;
        let mode = mode.map_or(0, FileAccessMode::mode);

        Error::clear_errno();

        match unsafe { selinux_sys::selinux_file_context_verify(c_path.as_ptr(), mode) } {
            -1_i32 => Err(Error::from_io_path(
                "selinux_file_context_verify()",
                path.as_ref(),
                io::Error::last_os_error(),
            )),

            0_i32 => {
                let err = io::Error::last_os_error();
                match err.raw_os_error() {
                    None | Some(0_i32) => Ok(false),

                    _ => Err(Error::from_io_path(
                        "selinux_file_context_verify()",
                        path.as_ref(),
                        err,
                    )),
                }
            }

            _ => Ok(true),
        }
    }

    fn of_new_operations(
        proc: unsafe extern "C" fn(*mut *mut c_char) -> c_int,
        proc_name: &'static str,
        raw_format: bool,
    ) -> Result<Option<Self>> {
        let mut context: *mut c_char = ptr::null_mut();
        if unsafe { proc(&raw mut context) } == -1_i32 {
            Err(Error::last_io_error(proc_name))
        } else {
            Ok(ptr::NonNull::new(context).map(|c| Self::from_ptr(c, None, raw_format)))
        }
    }

    fn from_ptr(context: ptr::NonNull<c_char>, size: Option<c_uint>, raw_format: bool) -> Self {
        #[allow(clippy::as_conversions)]
        Self {
            context,
            size: size.map(|size| size as usize),
            is_raw: raw_format,
            context_owned: true,
            _phantom_data: PhantomData,
        }
    }

    fn from_result(
        proc_name: &'static str,
        result: c_int,
        context: *mut c_char,
        raw_format: bool,
    ) -> Result<Self> {
        if result == -1_i32 {
            Err(Error::last_io_error(proc_name))
        } else {
            ptr::NonNull::new(context).map_or_else(
                || Err(Error::from_io(proc_name, io::ErrorKind::InvalidData.into())),
                |context| Ok(Self::from_ptr(context, None, raw_format)),
            )
        }
    }

    fn from_result_with_name(
        proc_name: &'static str,
        result: c_int,
        context: *mut c_char,
        name: &str,
        raw_format: bool,
    ) -> Result<Self> {
        if result == -1_i32 {
            Err(Error::last_io_error(proc_name))
        } else {
            ptr::NonNull::new(context).map_or_else(
                || {
                    Err(Error::IO1Name {
                        operation: proc_name,
                        name: name.into(),
                        source: io::ErrorKind::InvalidData.into(),
                    })
                },
                |context| Ok(Self::from_ptr(context, None, raw_format)),
            )
        }
    }

    fn from_result_with_pid(
        proc_name: &'static str,
        result: c_int,
        context: *mut c_char,
        process_id: pid_t,
        raw_format: bool,
    ) -> Result<Self> {
        if result == -1_i32 {
            let err = io::Error::last_os_error();
            Err(Error::from_io_pid(proc_name, process_id, err))
        } else {
            ptr::NonNull::new(context).map_or_else(
                || {
                    let err = io::ErrorKind::InvalidData.into();
                    Err(Error::from_io_pid(proc_name, process_id, err))
                },
                |context| Ok(Self::from_ptr(context, None, raw_format)),
            )
        }
    }

    fn parse_context_color(bytes: &[u8]) -> Result<SecurityContextColors> {
        #[allow(clippy::as_conversions)]
        let colors: Vec<RGB> = bytes
            .split(u8::is_ascii_whitespace)
            .filter(|&bytes| !bytes.is_empty())
            .take(8)
            .filter_map(|bytes| bytes.strip_prefix(b"#"))
            .filter(|&bytes| !bytes.is_empty())
            .filter_map(|bytes| str::from_utf8(bytes).ok())
            .filter_map(|s| u32::from_str_radix(s, 16).ok())
            .filter(|&n| n <= 0x00ff_ffff_u32)
            .map(|n| RGB {
                red: (n & 0xff_u32) as u8,
                green: ((n >> 8) & 0xff_u32) as u8,
                blue: ((n >> 16) & 0xff_u32) as u8,
            })
            .collect();

        if let Ok(colors) = <[RGB; 8]>::try_from(colors) {
            Ok(SecurityContextColors {
                user: LayerColors {
                    background: colors[1],
                    foreground: colors[0],
                },
                role: LayerColors {
                    background: colors[3],
                    foreground: colors[2],
                },
                the_type: LayerColors {
                    background: colors[5],
                    foreground: colors[4],
                },
                range: LayerColors {
                    background: colors[7],
                    foreground: colors[6],
                },
            })
        } else {
            Err(Error::from_io_name(
                "selinux_raw_context_to_color()",
                String::from_utf8_lossy(bytes),
                io::ErrorKind::InvalidData.into(),
            ))
        }
    }
}

impl Drop for SecurityContext<'_> {
    /// See: `freecon()`.
    #[doc(alias = "freecon")]
    fn drop(&mut self) {
        let context = self.context.as_ptr();
        self.context = ptr::NonNull::dangling();
        if self.context_owned {
            unsafe { selinux_sys::freecon(context) }
        }
    }
}

impl PartialEq for SecurityContext<'_> {
    /// Test whether this security context is equal to another security context,
    /// potentially after **canonicalizing** both of them.
    ///
    /// If a security context fails canonicalization, then it is used as is for the purpose
    /// of this equality test.
    fn eq(&self, that: &Self) -> bool {
        if self.is_raw != that.is_raw {
            return false;
        }

        match (self.canonicalize(), that.canonicalize()) {
            (Ok(this_canon), Ok(that_canon)) => this_canon.as_bytes() == that_canon.as_bytes(),
            (Ok(this_canon), Err(_)) => this_canon.as_bytes() == that.as_bytes(),
            (Err(_), Ok(that_canon)) => self.as_bytes() == that_canon.as_bytes(),
            (Err(_), Err(_)) => self.as_bytes() == that.as_bytes(),
        }
    }
}

/// List of security contexts.
#[derive(Debug, Eq)]
pub struct SecurityContextList {
    context_list: ptr::NonNull<*mut c_char>,
    count: usize,
    _phantom_data: PhantomData<c_char>,
}

impl SecurityContextList {
    /// Return the managed raw pointer to [`c_char`].
    #[must_use]
    pub fn as_ptr(&self) -> *const *const c_char {
        self.context_list.as_ptr().cast()
    }

    /// Return the managed raw pointer to [`c_char`].
    #[must_use]
    pub fn as_mut_ptr(&mut self) -> *mut *mut c_char {
        self.context_list.as_ptr()
    }

    /// Number of security contexts present in this list.
    #[must_use]
    pub fn len(&self) -> usize {
        self.count
    }

    /// Return `true` if this list is empty.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.count == 0
    }

    /// Obtain a list of contexts, for the specified SELinux user identity,
    /// that are reachable from the specified `reachable_from_context`.
    ///
    /// See: `get_ordered_context_list()`, `get_ordered_context_list_with_level()`.
    #[doc(alias = "get_ordered_context_list")]
    #[doc(alias = "get_ordered_context_list_with_level")]
    pub fn of_se_user(
        se_user: &str,
        level: Option<&str>,
        reachable_from_context: Option<&SecurityContext>,
    ) -> Result<Self> {
        let c_se_user = str_to_c_string(se_user)?;

        let c_level = if let Some(level) = level {
            str_to_c_string(level).map(Some)?
        } else {
            None
        };

        let reachable_from_context =
            reachable_from_context.map_or(ptr::null_mut(), |c| c.context.as_ptr());

        let mut context_list: *mut *mut c_char = ptr::null_mut();

        let (r, proc_name) = unsafe {
            if let Some(c_level) = c_level {
                let r = selinux_sys::get_ordered_context_list_with_level(
                    c_se_user.as_ptr(),
                    c_level.as_ptr(),
                    reachable_from_context,
                    &raw mut context_list,
                );
                (r, "get_ordered_context_list_with_level()")
            } else {
                let r = selinux_sys::get_ordered_context_list(
                    c_se_user.as_ptr(),
                    reachable_from_context,
                    &raw mut context_list,
                );
                (r, "get_ordered_context_list()")
            }
        };

        if r == -1_i32 {
            Err(Error::last_io_error(proc_name))
        } else {
            ptr::NonNull::new(context_list).map_or_else(
                || Err(Error::from_io(proc_name, io::ErrorKind::InvalidData.into())),
                |context_list| {
                    Ok(Self {
                        context_list,
                        #[allow(clippy::cast_sign_loss, clippy::as_conversions)]
                        count: r as c_uint as usize,
                        _phantom_data: PhantomData,
                    })
                },
            )
        }
    }

    /// Return the security context at the given index, if the index is valid.
    ///
    /// ⚠️ The returned instance does **NOT** own the context.
    /// When the returned instance get dropped, it will **NOT** deallocate the
    /// provided context.
    /// Deallocation of the context will only happen when the whole list gets
    /// dropped.
    #[must_use]
    pub fn get(&'_ self, index: usize, raw_format: bool) -> Option<SecurityContext<'_>> {
        if index < self.count {
            let context = unsafe { *self.context_list.as_ptr().wrapping_add(index) };
            ptr::NonNull::new(context).map(|context| SecurityContext {
                context,
                size: None,
                is_raw: raw_format,
                context_owned: false,
                _phantom_data: PhantomData,
            })
        } else {
            None
        }
    }

    /// Ask the user via `stdin`/`stdout` as to which context they want from
    /// this list of contexts, and return a new context as selected by the user.
    ///
    /// See: `query_user_context()`.
    #[doc(alias = "query_user_context")]
    pub fn user_selected_context(&self, raw_format: bool) -> Result<SecurityContext<'_>> {
        let mut context: *mut c_char = ptr::null_mut();
        let r = unsafe {
            selinux_sys::query_user_context(self.context_list.as_ptr(), &raw mut context)
        };
        SecurityContext::from_result("query_user_context()", r, context, raw_format)
    }
}

impl Drop for SecurityContextList {
    /// See: `freeconary()`.
    #[doc(alias = "freeconary")]
    fn drop(&mut self) {
        let context_list = self.context_list.as_ptr();
        self.context_list = ptr::NonNull::dangling();
        unsafe { selinux_sys::freeconary(context_list) }
    }
}

impl PartialEq for SecurityContextList {
    fn eq(&self, that: &Self) -> bool {
        if self.count != that.count {
            return false;
        }

        let mut this_ptr = self.context_list.as_ptr();
        let this_end = this_ptr.wrapping_add(self.count);
        let mut that_ptr = that.context_list.as_ptr();

        while this_ptr < this_end {
            if unsafe { CStr::from_ptr(*this_ptr) != CStr::from_ptr(*that_ptr) } {
                return false;
            }

            this_ptr = this_ptr.wrapping_add(1);
            that_ptr = that_ptr.wrapping_add(1);
        }

        true
    }
}

/// File access mode.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct FileAccessMode(selinux_sys::mode_t);

impl FileAccessMode {
    /// Create a new file access mode, if given a non-zero `mode`.
    #[must_use]
    pub fn new(mode: selinux_sys::mode_t) -> Option<Self> {
        (mode != 0).then_some(Self(mode))
    }

    /// Return the mode value.
    #[must_use]
    pub fn mode(self) -> selinux_sys::mode_t {
        self.0
    }
}

/// SELinux security class.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct SecurityClass(selinux_sys::security_class_t);

impl fmt::Display for SecurityClass {
    /// See: `security_class_to_string()`.
    #[doc(alias = "security_class_to_string")]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let name_ptr = unsafe { selinux_sys::security_class_to_string(self.0) };
        if name_ptr.is_null() {
            write!(f, "<invalid-class>")
        } else {
            let c_name = unsafe { CStr::from_ptr(name_ptr) };
            write!(f, "{}", c_name.to_string_lossy())
        }
    }
}

impl SecurityClass {
    /// Return the security class value.
    #[must_use]
    pub fn value(&self) -> selinux_sys::security_class_t {
        self.0
    }

    /// Create a new security class, if given a non-zero `class`.
    pub fn new(class: selinux_sys::security_class_t) -> Result<Self> {
        if class == 0 {
            let err = io::ErrorKind::NotFound.into();
            Err(Error::from_io("SecurityClass::new()", err))
        } else {
            Ok(Self(class))
        }
    }

    /// Return the security class corresponding to the string name,
    /// if such class exists.
    ///
    /// See: `string_to_security_class()`.
    #[doc(alias = "string_to_security_class")]
    pub fn from_name(name: &str) -> Result<Self> {
        let c_name = str_to_c_string(name)?;
        let r = unsafe { selinux_sys::string_to_security_class(c_name.as_ptr()) };
        if r == 0 {
            let err = io::ErrorKind::NotFound.into();
            Err(Error::from_io("string_to_security_class()", err))
        } else {
            Ok(Self(r))
        }
    }

    /// Return the name of the `access_vector` of this security class.
    ///
    /// See: `security_av_perm_to_string()`.
    ///
    /// # Safety
    ///
    /// The returned string must not be **modified** or **freed**.
    #[doc(alias = "security_av_perm_to_string")]
    pub unsafe fn access_vector_bit_name(
        &self,
        access_vector: selinux_sys::access_vector_t,
    ) -> Result<&'static CStr> {
        let name_ptr = unsafe { selinux_sys::security_av_perm_to_string(self.0, access_vector) };
        if name_ptr.is_null() {
            let err = io::ErrorKind::NotFound.into();
            Err(Error::from_io("security_av_perm_to_string()", err))
        } else {
            unsafe { Ok(CStr::from_ptr(name_ptr)) }
        }
    }

    /// Return the access vector bit corresponding to the given name and this
    /// security class.
    ///
    /// See: `string_to_av_perm()`.
    #[doc(alias = "string_to_av_perm")]
    pub fn access_vector_bit(&self, name: &str) -> Result<selinux_sys::access_vector_t> {
        let c_name = str_to_c_string(name)?;
        let r = unsafe { selinux_sys::string_to_av_perm(self.0, c_name.as_ptr()) };
        if r == 0 {
            let err = io::ErrorKind::NotFound.into();
            Err(Error::from_io("string_to_av_perm()", err))
        } else {
            Ok(r)
        }
    }

    /// Compute a full access vector string representation using this security
    /// class and `access_vector`, which may have multiple bits set.
    ///
    /// See: `security_av_string()`.
    #[doc(alias = "security_av_string")]
    pub fn full_access_vector_name(
        &self,
        access_vector: selinux_sys::access_vector_t,
    ) -> Result<CAllocatedBlock<c_char>> {
        let mut name_ptr: *mut c_char = ptr::null_mut();
        let r: c_int =
            unsafe { selinux_sys::security_av_string(self.0, access_vector, &raw mut name_ptr) };
        if r == -1_i32 {
            Err(Error::last_io_error("security_av_string()"))
        } else {
            CAllocatedBlock::new(name_ptr).ok_or_else(|| {
                Error::from_io("security_av_string()", io::ErrorKind::NotFound.into())
            })
        }
    }
}

impl TryFrom<FileAccessMode> for SecurityClass {
    type Error = Error;

    /// See: `mode_to_security_class()`.
    #[doc(alias = "mode_to_security_class")]
    fn try_from(mode: FileAccessMode) -> Result<Self> {
        let r = unsafe { selinux_sys::mode_to_security_class(mode.mode()) };
        if r == 0 {
            let err = io::ErrorKind::NotFound.into();
            Err(Error::from_io("mode_to_security_class()", err))
        } else {
            Ok(Self(r))
        }
    }
}

/// Opaque security context.
#[derive(Debug, Eq)]
pub struct OpaqueSecurityContext {
    context: ptr::NonNull<selinux_sys::context_s_t>,
    _phantom_data: PhantomData<selinux_sys::context_s_t>,
}

/// **ASSUMPTION:** The tuple **(type, range, role, user)** completely identifies an opaque security
/// context, irrespective of its format.
impl PartialEq for OpaqueSecurityContext {
    fn eq(&self, that: &Self) -> bool {
        if let Ok(this_type) = self.the_type()
            && let Ok(that_type) = that.the_type()
            && this_type == that_type
            && let Ok(this_range) = self.range()
            && let Ok(that_range) = that.range()
            && this_range == that_range
            && let Ok(this_role) = self.role()
            && let Ok(that_role) = that.role()
            && this_role == that_role
            && let Ok(this_user) = self.user()
            && let Ok(that_user) = that.user()
            && this_user == that_user
        {
            true
        } else {
            false
        }
    }
}

impl OpaqueSecurityContext {
    /// Return the managed raw pointer to [`selinux_sys::context_s_t`].
    #[must_use]
    pub fn as_ptr(&self) -> *const selinux_sys::context_s_t {
        self.context.as_ptr()
    }

    /// Return the managed raw pointer to [`selinux_sys::context_s_t`].
    #[must_use]
    pub fn as_mut_ptr(&mut self) -> *mut selinux_sys::context_s_t {
        self.context.as_ptr()
    }

    /// Return a new context initialized to a context string.
    ///
    /// See: `context_new()`.
    #[doc(alias = "context_new")]
    pub fn new(context: &str) -> Result<Self> {
        let c_context = str_to_c_string(context)?;
        Self::from_c_str(&c_context)
    }

    /// Return a new context initialized to a context string.
    ///
    /// See: `context_new()`.
    #[doc(alias = "context_new")]
    pub fn from_c_str(context: &CStr) -> Result<Self> {
        let context = unsafe { selinux_sys::context_new(context.as_ptr()) };
        ptr::NonNull::new(context).map_or_else(
            || Err(Error::last_io_error("context_new()")),
            |context| {
                Ok(Self {
                    context,
                    _phantom_data: PhantomData,
                })
            },
        )
    }

    /// Return the string value of this security context.
    ///
    /// See: `context_str()`.
    #[doc(alias = "context_str")]
    pub fn to_c_string(&self) -> Result<CString> {
        let r = unsafe { selinux_sys::context_str(self.context.as_ptr()) };
        if r.is_null() {
            Err(Error::last_io_error("context_str()"))
        } else {
            Ok(unsafe { CStr::from_ptr(r) }.into())
        }
    }

    /// Return the string value of this security context's type.
    ///
    /// See: `context_type_get()`.
    #[doc(alias = "context_type_get")]
    pub fn the_type(&self) -> Result<CString> {
        self.get(selinux_sys::context_type_get, "context_type_get()")
    }

    /// Set the type of this security context.
    ///
    /// See: `context_type_set()`.
    #[doc(alias = "context_type_set")]
    pub fn set_type_str(&self, new_value: &str) -> Result<()> {
        let c_new_value = str_to_c_string(new_value)?;
        self.set(
            selinux_sys::context_type_set,
            "context_type_set()",
            c_new_value.as_ref(),
        )
    }

    /// Set the type of this security context.
    ///
    /// See: `context_type_set()`.
    #[doc(alias = "context_type_set")]
    pub fn set_type(&self, new_value: &CStr) -> Result<()> {
        let proc_name = "context_type_set()";
        self.set(selinux_sys::context_type_set, proc_name, new_value)
    }

    /// Return the string value of this security context's range.
    ///
    /// See: `context_range_get()`.
    #[doc(alias = "context_range_get")]
    pub fn range(&self) -> Result<CString> {
        self.get(selinux_sys::context_range_get, "context_range_get()")
    }

    /// Set the range of this security context.
    ///
    /// See: `context_range_set()`.
    #[doc(alias = "context_range_set")]
    pub fn set_range_str(&self, new_value: &str) -> Result<()> {
        let c_new_value = str_to_c_string(new_value)?;
        self.set(
            selinux_sys::context_range_set,
            "context_range_set()",
            c_new_value.as_ref(),
        )
    }

    /// Set the range of this security context.
    ///
    /// See: `context_range_set()`.
    #[doc(alias = "context_range_set")]
    pub fn set_range(&self, new_value: &CStr) -> Result<()> {
        let proc_name = "context_range_set()";
        self.set(selinux_sys::context_range_set, proc_name, new_value)
    }

    /// Return the string value of this security context's role.
    ///
    /// See: `context_role_get()`.
    #[doc(alias = "context_role_get")]
    pub fn role(&self) -> Result<CString> {
        self.get(selinux_sys::context_role_get, "context_role_get()")
    }

    /// Set the role of this security context.
    ///
    /// See: `context_role_set()`.
    #[doc(alias = "context_role_set")]
    pub fn set_role_str(&self, new_value: &str) -> Result<()> {
        let c_new_value = str_to_c_string(new_value)?;
        self.set(
            selinux_sys::context_role_set,
            "context_role_set()",
            c_new_value.as_ref(),
        )
    }

    /// Set the role of this security context.
    ///
    /// See: `context_role_set()`.
    #[doc(alias = "context_role_set")]
    pub fn set_role(&self, new_value: &CStr) -> Result<()> {
        let proc_name = "context_role_set()";
        self.set(selinux_sys::context_role_set, proc_name, new_value)
    }

    /// Return the string value of this security context's user.
    ///
    /// See: `context_user_get()`.
    #[doc(alias = "context_user_get")]
    pub fn user(&self) -> Result<CString> {
        self.get(selinux_sys::context_user_get, "context_user_get()")
    }

    /// Set the user of this security context.
    ///
    /// See: `context_user_set()`.
    #[doc(alias = "context_user_set")]
    pub fn set_user_str(&self, new_value: &str) -> Result<()> {
        let c_new_value = str_to_c_string(new_value)?;
        self.set(
            selinux_sys::context_user_set,
            "context_user_set()",
            c_new_value.as_ref(),
        )
    }

    /// Set the user of this security context.
    ///
    /// See: `context_user_set()`.
    #[doc(alias = "context_user_set")]
    pub fn set_user(&self, new_value: &CStr) -> Result<()> {
        let proc_name = "context_user_set()";
        self.set(selinux_sys::context_user_set, proc_name, new_value)
    }

    fn get(
        &self,
        proc: unsafe extern "C" fn(selinux_sys::context_t) -> *const c_char,
        proc_name: &'static str,
    ) -> Result<CString> {
        let r = unsafe { proc(self.context.as_ptr()) };
        if r.is_null() {
            Err(Error::last_io_error(proc_name))
        } else {
            Ok(unsafe { CStr::from_ptr(r) }.into())
        }
    }

    fn set(
        &self,
        proc: unsafe extern "C" fn(selinux_sys::context_t, *const c_char) -> c_int,
        proc_name: &'static str,
        new_value: &CStr,
    ) -> Result<()> {
        if unsafe { proc(self.context.as_ptr(), new_value.as_ptr()) } == 0_i32 {
            Ok(())
        } else {
            Err(Error::last_io_error(proc_name))
        }
    }
}

impl Drop for OpaqueSecurityContext {
    /// See: `context_free()`.
    #[doc(alias = "context_free")]
    fn drop(&mut self) {
        let context = self.context.as_ptr();
        self.context = ptr::NonNull::dangling();
        unsafe { selinux_sys::context_free(context) };
    }
}

impl fmt::Display for OpaqueSecurityContext {
    /// See: `context_str()`.
    #[doc(alias = "context_str")]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let ptr = unsafe { selinux_sys::context_str(self.context.as_ptr()) };
        let context = if ptr.is_null() {
            Cow::Borrowed("<null>")
        } else {
            unsafe { CStr::from_ptr(ptr) }.to_string_lossy()
        };
        write!(f, "{context}")
    }
}

/// Support of SELinux in the running kernel.
#[non_exhaustive]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum KernelSupport {
    /// SELinux is unsupported.
    Unsupported,
    /// SELinux is supported.
    SELinux,
    /// SELinux is supported, with Multi Level Security.
    SELinuxMLS,
}

/// SELinux enforcing mode.
#[non_exhaustive]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum SELinuxMode {
    /// SELinux is not enforcing.
    NotRunning,
    /// SELinux is permissive.
    Permissive,
    /// SELinux is enforcing.
    Enforcing,
}

/// SELinux handling of undefined object classes and permissions.
#[non_exhaustive]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum UndefinedHandling {
    /// Undefined object classes and permissions are allowed.
    Allowed,
    /// Undefined object classes and permissions are deined at run time.
    DeniedAtRunTime,
    /// Undefined object classes and permissions are rejected at policy load time.
    RejectedAtLoadTime,
}

/// Protection checked by SELinux on `mmap()` and `mprotect()` calls.
#[non_exhaustive]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum ProtectionCheckingMode {
    /// Actual protection that will be applied by the kernel
    /// (including the effects of `READ_IMPLIES_EXEC`).
    CheckingActualProtection,
    /// Protection requested by the application.
    CheckingRequestedProtection,
}

/// Determine the support of SELinux in the running kernel.
///
/// See: `is_selinux_enabled()`, `is_selinux_mls_enabled()`.
#[doc(alias = "is_selinux_enabled")]
#[doc(alias = "is_selinux_mls_enabled")]
#[must_use]
pub fn kernel_support() -> KernelSupport {
    if unsafe { selinux_sys::is_selinux_mls_enabled() } != 0_i32 {
        KernelSupport::SELinuxMLS
    } else if unsafe { selinux_sys::is_selinux_enabled() } != 0_i32 {
        KernelSupport::SELinux
    } else {
        KernelSupport::Unsupported
    }
}

/// Determine how the system was set up to run SELinux.
///
/// See: `selinux_getenforcemode()`.
#[doc(alias = "selinux_getenforcemode")]
pub fn boot_mode() -> Result<SELinuxMode> {
    let mut enforce: c_int = -1;
    if unsafe { selinux_sys::selinux_getenforcemode(&raw mut enforce) } == -1_i32 {
        Err(Error::last_io_error("selinux_getenforcemode()"))
    } else {
        match enforce {
            -1_i32 => Ok(SELinuxMode::NotRunning),
            0_i32 => Ok(SELinuxMode::Permissive),
            _ => Ok(SELinuxMode::Enforcing),
        }
    }
}

/// Determine the current SELinux enforcing mode.
///
/// See: `security_getenforce()`.
#[doc(alias = "security_getenforce")]
#[must_use]
pub fn current_mode() -> SELinuxMode {
    match unsafe { selinux_sys::security_getenforce() } {
        -1_i32 => SELinuxMode::NotRunning,
        0_i32 => SELinuxMode::Permissive,
        _ => SELinuxMode::Enforcing,
    }
}

/// Set the current SELinux enforcing mode.
///
/// See: `security_disable()`, `security_setenforce()`.
#[doc(alias = "security_disable")]
#[doc(alias = "security_setenforce")]
pub fn set_current_mode(new_mode: SELinuxMode) -> Result<()> {
    let (r, proc_name) = match new_mode {
        SELinuxMode::NotRunning => {
            let r = unsafe { selinux_sys::security_disable() };
            (r, "security_disable()")
        }

        SELinuxMode::Permissive => {
            let r = unsafe { selinux_sys::security_setenforce(0) };
            (r, "security_setenforce()")
        }

        SELinuxMode::Enforcing => {
            let r = unsafe { selinux_sys::security_setenforce(1) };
            (r, "security_setenforce()")
        }
    };

    ret_val_to_result(proc_name, r)
}

/// Return the current SELinux handling of undefined object classes
/// and permissions.
///
/// See: `security_reject_unknown()`, `security_deny_unknown()`.
#[doc(alias = "security_reject_unknown")]
#[doc(alias = "security_deny_unknown")]
pub fn undefined_handling() -> Result<UndefinedHandling> {
    let mut reject_unknown: c_int =
        unsafe { (OptionalNativeFunctions::get().security_reject_unknown)() };
    if reject_unknown == -1_i32 {
        let err = io::Error::last_os_error();
        if err.raw_os_error() == Some(libc::ENOSYS) {
            reject_unknown = 0_i32;
        } else {
            return Err(Error::from_io("security_reject_unknown()", err));
        }
    }

    if reject_unknown == 0_i32 {
        match unsafe { selinux_sys::security_deny_unknown() } {
            -1_i32 => Err(Error::last_io_error("security_deny_unknown()")),

            0_i32 => Ok(UndefinedHandling::Allowed),

            _ => Ok(UndefinedHandling::DeniedAtRunTime),
        }
    } else {
        Ok(UndefinedHandling::RejectedAtLoadTime)
    }
}

/// Determine the protection currently checked by SELinux on `mmap()` and
/// `mprotect()` calls.
///
/// See: `security_get_checkreqprot()`.
#[doc(alias = "security_get_checkreqprot")]
pub fn protection_checking_mode() -> Result<ProtectionCheckingMode> {
    match unsafe { selinux_sys::security_get_checkreqprot() } {
        -1_i32 => Err(Error::last_io_error("security_get_checkreqprot()")),

        0_i32 => Ok(ProtectionCheckingMode::CheckingActualProtection),

        _ => Ok(ProtectionCheckingMode::CheckingRequestedProtection),
    }
}

fn dynamic_mapping_into_native_form<'mapping, 'key, 'value, K, V, O>(
    mapping: &'mapping [(K, V)],
    c_string_storage: &mut HashMap<&'key str, CString>,
) -> Result<Vec<selinux_sys::security_class_mapping>>
where
    'mapping: 'key,
    'value: 'key,
    K: AsRef<str> + 'key,
    V: AsRef<[O]>,
    O: AsRef<str> + 'value,
{
    let mut c_map = Vec::with_capacity(mapping.len() + 1);

    for (name, permissions) in mapping {
        let mut element: selinux_sys::security_class_mapping = unsafe { mem::zeroed() };

        if permissions.as_ref().len() >= element.perms.len() {
            let err = io::ErrorKind::InvalidInput.into();
            return Err(Error::from_io("SELinux::set_dynamic_mapping()", err));
        }

        element.name = match c_string_storage.entry(name.as_ref()) {
            hash_map::Entry::Vacant(entry) => {
                let c_name = str_to_c_string(name.as_ref())?;
                entry.insert(c_name).as_ptr()
            }

            hash_map::Entry::Occupied(entry) => entry.get().as_ptr(),
        };

        for (index, permission) in permissions.as_ref().iter().enumerate() {
            element.perms[index] = match c_string_storage.entry(permission.as_ref()) {
                hash_map::Entry::Vacant(entry) => {
                    let c_permission = str_to_c_string(permission.as_ref())?;
                    entry.insert(c_permission).as_ptr()
                }

                hash_map::Entry::Occupied(entry) => entry.get().as_ptr(),
            };
        }

        c_map.push(element);
    }
    c_map.push(unsafe { mem::zeroed() }); // End of the array.
    Ok(c_map)
}

/// Establishes a mapping from a user-provided ordering of object classes
/// and permissions to the numbers actually used by the loaded system policy.
///
/// See: `selinux_set_mapping()`.
#[doc(alias = "selinux_set_mapping")]
pub fn set_dynamic_mapping<K, V, O>(mapping: &[(K, V)]) -> Result<()>
where
    K: AsRef<str>,
    V: AsRef<[O]>,
    O: AsRef<str>,
{
    // The `selinux_set_mapping()` parameter holds pointers to null-terminated strings.
    //
    // We transform the input `mapping` into an array of `security_class_mapping`
    // by transforming `&str` instances into CString instances and storing
    // them into `c_string_storage`. Pointers to these `CString`s are then stored
    // into the `security_class_mapping` structures.
    let mut c_string_storage = HashMap::<&str, CString>::with_capacity(mapping.len() * 3);
    let mut c_map = dynamic_mapping_into_native_form(mapping, &mut c_string_storage)?;

    let r = unsafe { selinux_sys::selinux_set_mapping(c_map.as_mut_ptr()) };
    ret_val_to_result("selinux_set_mapping()", r)
}

/// Flush the SELinux class cache, *e.g.*, upon a policy reload.
///
/// This function requires `libselinux` version `3.1` or later.
///
/// See: `selinux_flush_class_cache()`.
#[doc(alias = "selinux_flush_class_cache")]
pub fn flush_class_cache() -> Result<()> {
    Error::clear_errno();
    unsafe { (OptionalNativeFunctions::get().selinux_flush_class_cache)() }

    let err = io::Error::last_os_error();
    match err.raw_os_error() {
        None | Some(0_i32) => Ok(()),
        _ => Err(Error::from_io("selinux_flush_class_cache()", err)),
    }
}

/// Get the SELinux user name and level for a given Linux user name.
///
/// See: `getseuser()`, `getseuserbyname()`.
#[doc(alias = "getseuser")]
#[doc(alias = "getseuserbyname")]
pub fn se_user_and_level(
    user_name: &str,
    service: Option<&str>,
) -> Result<(CAllocatedBlock<c_char>, CAllocatedBlock<c_char>)> {
    let c_user_name = str_to_c_string(user_name)?;
    let c_service = if let Some(service) = service {
        Some(str_to_c_string(service)?)
    } else {
        None
    };

    let mut se_user_ptr: *mut c_char = ptr::null_mut();
    let mut level_ptr: *mut c_char = ptr::null_mut();

    let (r, proc_name) = if let Some(c_service) = c_service {
        let r: c_int = unsafe {
            selinux_sys::getseuser(
                c_user_name.as_ptr(),
                c_service.as_ptr(),
                &raw mut se_user_ptr,
                &raw mut level_ptr,
            )
        };

        (r, "getseuser()")
    } else {
        let r = unsafe {
            selinux_sys::getseuserbyname(
                c_user_name.as_ptr(),
                &raw mut se_user_ptr,
                &raw mut level_ptr,
            )
        };

        (r, "getseuserbyname()")
    };

    if r == -1_i32 {
        Err(Error::last_io_error(proc_name))
    } else if se_user_ptr.is_null() || level_ptr.is_null() {
        Err(Error::from_io(proc_name, io::ErrorKind::InvalidData.into()))
    } else {
        let se_user = CAllocatedBlock::new(se_user_ptr)
            .ok_or_else(|| Error::from_io(proc_name, io::ErrorKind::InvalidInput.into()))?;

        let level = CAllocatedBlock::new(level_ptr)
            .ok_or_else(|| Error::from_io(proc_name, io::ErrorKind::InvalidInput.into()))?;

        Ok((se_user, level))
    }
}

/// Force a reset of the loaded configuration.
///
/// See: `selinux_reset_config()`.
#[doc(alias = "selinux_reset_config")]
pub fn reset_config() {
    unsafe { selinux_sys::selinux_reset_config() }
}

/// Get the default type (domain) for role, and set type to refer to it.
///
/// See: `get_default_type()`.
#[doc(alias = "get_default_type")]
pub fn default_type_for_role(role: &str) -> Result<CAllocatedBlock<c_char>> {
    let mut c_type: *mut c_char = ptr::null_mut();
    let c_role = str_to_c_string(role)?;
    if unsafe { selinux_sys::get_default_type(c_role.as_ptr(), &raw mut c_type) } == -1_i32 {
        Err(Error::last_io_error("get_default_type()"))
    } else {
        CAllocatedBlock::new(c_type)
            .ok_or_else(|| Error::from_io("get_default_type()", io::ErrorKind::InvalidData.into()))
    }
}
