/*!
A pure-Rust library to work with Linux capabilities.

It provides support for manipulating capabilities available on modern Linux
kernels. It supports traditional POSIX sets (Effective, Inheritable, Permitted)
as well as Linux-specific Ambient and Bounding capabilities sets.

```rust
type ExResult<T> = Result<T, Box<dyn std::error::Error + 'static>>;

fn manipulate_caps() -> ExResult<()> {
    use syd::caps::{Capability, Capabilities, CapSet};

    if syd::caps::has_cap(None, CapSet::Permitted, Capability::CAP_SYS_NICE)? {
        syd::caps::drop(None, CapSet::Effective, Capability::CAP_SYS_NICE)?;
        let effective = syd::caps::read(None, CapSet::Effective)?;
        assert_eq!(effective.contains(Capabilities::CAP_SYS_NICE), false);

        syd::caps::clear(None, CapSet::Effective)?;
        let cleared = syd::caps::read(None, CapSet::Effective)?;
        assert_eq!(cleared.is_empty(), true);
    };

    Ok(())
}
```
!*/

pub mod errors;
pub mod runtime;
pub mod securebits;

// Implementation of Bounding set.
mod ambient;
// Implementation of POSIX sets.
mod base;
// Implementation of Bounding set.
mod bounding;
// All kernel-related constants.
mod nr;

use bitflags::bitflags;
use nix::errno::Errno;

use crate::caps::errors::CapsError;

/// Linux capabilities sets.
///
/// All capabilities sets supported by Linux, including standard
/// POSIX and custom ones. See `capabilities(7)`.
#[derive(Debug, Clone, Copy)]
pub enum CapSet {
    /// Ambient capabilities set (from Linux 4.3).
    Ambient,
    /// Bounding capabilities set (from Linux 2.6.25)
    Bounding,
    /// Effective capabilities set (from POSIX)
    Effective,
    /// Inheritable capabilities set (from POSIX)
    Inheritable,
    /// Permitted capabilities set (from POSIX)
    Permitted,
}

#[cfg(feature = "oci")]
use oci_spec::runtime::Capability as SpecCapability;

/// Linux capabilities.
///
/// All capabilities supported by Linux, including standard
/// POSIX and custom ones. See `capabilities(7)`.
#[expect(clippy::manual_non_exhaustive)]
#[expect(non_camel_case_types)]
#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
#[repr(u8)]
pub enum Capability {
    /// `CAP_CHOWN` (from POSIX)
    CAP_CHOWN = nr::CAP_CHOWN,
    /// `CAP_DAC_OVERRIDE` (from POSIX)
    CAP_DAC_OVERRIDE = nr::CAP_DAC_OVERRIDE,
    /// `CAP_DAC_READ_SEARCH` (from POSIX)
    CAP_DAC_READ_SEARCH = nr::CAP_DAC_READ_SEARCH,
    /// `CAP_FOWNER` (from POSIX)
    CAP_FOWNER = nr::CAP_FOWNER,
    /// `CAP_FSETID` (from POSIX)
    CAP_FSETID = nr::CAP_FSETID,
    /// `CAP_KILL` (from POSIX)
    CAP_KILL = nr::CAP_KILL,
    /// `CAP_SETGID` (from POSIX)
    CAP_SETGID = nr::CAP_SETGID,
    /// `CAP_SETUID` (from POSIX)
    CAP_SETUID = nr::CAP_SETUID,
    /// `CAP_SETPCAP` (from Linux)
    CAP_SETPCAP = nr::CAP_SETPCAP,
    CAP_LINUX_IMMUTABLE = nr::CAP_LINUX_IMMUTABLE,
    CAP_NET_BIND_SERVICE = nr::CAP_NET_BIND_SERVICE,
    CAP_NET_BROADCAST = nr::CAP_NET_BROADCAST,
    CAP_NET_ADMIN = nr::CAP_NET_ADMIN,
    CAP_NET_RAW = nr::CAP_NET_RAW,
    CAP_IPC_LOCK = nr::CAP_IPC_LOCK,
    CAP_IPC_OWNER = nr::CAP_IPC_OWNER,
    /// `CAP_SYS_MODULE` (from Linux)
    CAP_SYS_MODULE = nr::CAP_SYS_MODULE,
    /// `CAP_SYS_RAWIO` (from Linux)
    CAP_SYS_RAWIO = nr::CAP_SYS_RAWIO,
    /// `CAP_SYS_CHROOT` (from Linux)
    CAP_SYS_CHROOT = nr::CAP_SYS_CHROOT,
    /// `CAP_SYS_PTRACE` (from Linux)
    CAP_SYS_PTRACE = nr::CAP_SYS_PTRACE,
    /// `CAP_SYS_PACCT` (from Linux)
    CAP_SYS_PACCT = nr::CAP_SYS_PACCT,
    /// `CAP_SYS_ADMIN` (from Linux)
    CAP_SYS_ADMIN = nr::CAP_SYS_ADMIN,
    /// `CAP_SYS_BOOT` (from Linux)
    CAP_SYS_BOOT = nr::CAP_SYS_BOOT,
    /// `CAP_SYS_NICE` (from Linux)
    CAP_SYS_NICE = nr::CAP_SYS_NICE,
    /// `CAP_SYS_RESOURCE` (from Linux)
    CAP_SYS_RESOURCE = nr::CAP_SYS_RESOURCE,
    /// `CAP_SYS_TIME` (from Linux)
    CAP_SYS_TIME = nr::CAP_SYS_TIME,
    /// `CAP_SYS_TTY_CONFIG` (from Linux)
    CAP_SYS_TTY_CONFIG = nr::CAP_SYS_TTY_CONFIG,
    /// `CAP_SYS_MKNOD` (from Linux, >= 2.4)
    CAP_MKNOD = nr::CAP_MKNOD,
    /// `CAP_LEASE` (from Linux, >= 2.4)
    CAP_LEASE = nr::CAP_LEASE,
    CAP_AUDIT_WRITE = nr::CAP_AUDIT_WRITE,
    /// `CAP_AUDIT_CONTROL` (from Linux, >= 2.6.11)
    CAP_AUDIT_CONTROL = nr::CAP_AUDIT_CONTROL,
    CAP_SETFCAP = nr::CAP_SETFCAP,
    CAP_MAC_OVERRIDE = nr::CAP_MAC_OVERRIDE,
    CAP_MAC_ADMIN = nr::CAP_MAC_ADMIN,
    /// `CAP_SYSLOG` (from Linux, >= 2.6.37)
    CAP_SYSLOG = nr::CAP_SYSLOG,
    /// `CAP_WAKE_ALARM` (from Linux, >= 3.0)
    CAP_WAKE_ALARM = nr::CAP_WAKE_ALARM,
    CAP_BLOCK_SUSPEND = nr::CAP_BLOCK_SUSPEND,
    /// `CAP_AUDIT_READ` (from Linux, >= 3.16).
    CAP_AUDIT_READ = nr::CAP_AUDIT_READ,
    /// `CAP_PERFMON` (from Linux, >= 5.8).
    CAP_PERFMON = nr::CAP_PERFMON,
    /// `CAP_BPF` (from Linux, >= 5.8).
    CAP_BPF = nr::CAP_BPF,
    /// `CAP_CHECKPOINT_RESTORE` (from Linux, >= 5.9).
    CAP_CHECKPOINT_RESTORE = nr::CAP_CHECKPOINT_RESTORE,
    #[doc(hidden)]
    __Nonexhaustive,
}

impl std::fmt::Display for Capability {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let name = match *self {
            Capability::CAP_CHOWN => "cap_chown",
            Capability::CAP_DAC_OVERRIDE => "cap_dac_override",
            Capability::CAP_DAC_READ_SEARCH => "cap_dac_read_search",
            Capability::CAP_FOWNER => "cap_fowner",
            Capability::CAP_FSETID => "cap_fsetid",
            Capability::CAP_KILL => "cap_kill",
            Capability::CAP_SETGID => "cap_setgid",
            Capability::CAP_SETUID => "cap_setuid",
            Capability::CAP_SETPCAP => "cap_setpcap",
            Capability::CAP_LINUX_IMMUTABLE => "cap_linux_immutable",
            Capability::CAP_NET_BIND_SERVICE => "cap_net_bind_service",
            Capability::CAP_NET_BROADCAST => "cap_net_broadcast",
            Capability::CAP_NET_ADMIN => "cap_net_admin",
            Capability::CAP_NET_RAW => "cap_net_raw",
            Capability::CAP_IPC_LOCK => "cap_ipc_lock",
            Capability::CAP_IPC_OWNER => "cap_ipc_owner",
            Capability::CAP_SYS_MODULE => "cap_sys_module",
            Capability::CAP_SYS_RAWIO => "cap_sys_rawio",
            Capability::CAP_SYS_CHROOT => "cap_sys_chroot",
            Capability::CAP_SYS_PTRACE => "cap_sys_ptrace",
            Capability::CAP_SYS_PACCT => "cap_sys_pacct",
            Capability::CAP_SYS_ADMIN => "cap_sys_admin",
            Capability::CAP_SYS_BOOT => "cap_sys_boot",
            Capability::CAP_SYS_NICE => "cap_sys_nice",
            Capability::CAP_SYS_RESOURCE => "cap_sys_resource",
            Capability::CAP_SYS_TIME => "cap_sys_time",
            Capability::CAP_SYS_TTY_CONFIG => "cap_sys_tty_config",
            Capability::CAP_MKNOD => "cap_mknod",
            Capability::CAP_LEASE => "cap_lease",
            Capability::CAP_AUDIT_WRITE => "cap_audit_write",
            Capability::CAP_AUDIT_CONTROL => "cap_audit_control",
            Capability::CAP_SETFCAP => "cap_setfcap",
            Capability::CAP_MAC_OVERRIDE => "cap_mac_override",
            Capability::CAP_MAC_ADMIN => "cap_mac_admin",
            Capability::CAP_SYSLOG => "cap_syslog",
            Capability::CAP_WAKE_ALARM => "cap_wake_alarm",
            Capability::CAP_BLOCK_SUSPEND => "cap_block_suspend",
            Capability::CAP_AUDIT_READ => "cap_audit_read",
            Capability::CAP_PERFMON => "cap_perfmon",
            Capability::CAP_BPF => "cap_bpf",
            Capability::CAP_CHECKPOINT_RESTORE => "cap_checkpoint_restore",
            Capability::__Nonexhaustive => unreachable!("invalid capability"),
        };
        write!(f, "{name}")
    }
}

impl std::str::FromStr for Capability {
    type Err = CapsError;

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        match s.to_ascii_lowercase().as_str() {
            "cap_chown" => Ok(Capability::CAP_CHOWN),
            "cap_dac_override" => Ok(Capability::CAP_DAC_OVERRIDE),
            "cap_dac_read_search" => Ok(Capability::CAP_DAC_READ_SEARCH),
            "cap_fowner" => Ok(Capability::CAP_FOWNER),
            "cap_fsetid" => Ok(Capability::CAP_FSETID),
            "cap_kill" => Ok(Capability::CAP_KILL),
            "cap_setgid" => Ok(Capability::CAP_SETGID),
            "cap_setuid" => Ok(Capability::CAP_SETUID),
            "cap_setpcap" => Ok(Capability::CAP_SETPCAP),
            "cap_linux_immutable" => Ok(Capability::CAP_LINUX_IMMUTABLE),
            "cap_net_bind_service" => Ok(Capability::CAP_NET_BIND_SERVICE),
            "cap_net_broadcast" => Ok(Capability::CAP_NET_BROADCAST),
            "cap_net_admin" => Ok(Capability::CAP_NET_ADMIN),
            "cap_net_raw" => Ok(Capability::CAP_NET_RAW),
            "cap_ipc_lock" => Ok(Capability::CAP_IPC_LOCK),
            "cap_ipc_owner" => Ok(Capability::CAP_IPC_OWNER),
            "cap_sys_module" => Ok(Capability::CAP_SYS_MODULE),
            "cap_sys_rawio" => Ok(Capability::CAP_SYS_RAWIO),
            "cap_sys_chroot" => Ok(Capability::CAP_SYS_CHROOT),
            "cap_sys_ptrace" => Ok(Capability::CAP_SYS_PTRACE),
            "cap_sys_pacct" => Ok(Capability::CAP_SYS_PACCT),
            "cap_sys_admin" => Ok(Capability::CAP_SYS_ADMIN),
            "cap_sys_boot" => Ok(Capability::CAP_SYS_BOOT),
            "cap_sys_nice" => Ok(Capability::CAP_SYS_NICE),
            "cap_sys_resource" => Ok(Capability::CAP_SYS_RESOURCE),
            "cap_sys_time" => Ok(Capability::CAP_SYS_TIME),
            "cap_sys_tty_config" => Ok(Capability::CAP_SYS_TTY_CONFIG),
            "cap_mknod" => Ok(Capability::CAP_MKNOD),
            "cap_lease" => Ok(Capability::CAP_LEASE),
            "cap_audit_write" => Ok(Capability::CAP_AUDIT_WRITE),
            "cap_audit_control" => Ok(Capability::CAP_AUDIT_CONTROL),
            "cap_setfcap" => Ok(Capability::CAP_SETFCAP),
            "cap_mac_override" => Ok(Capability::CAP_MAC_OVERRIDE),
            "cap_mac_admin" => Ok(Capability::CAP_MAC_ADMIN),
            "cap_syslog" => Ok(Capability::CAP_SYSLOG),
            "cap_wake_alarm" => Ok(Capability::CAP_WAKE_ALARM),
            "cap_block_suspend" => Ok(Capability::CAP_BLOCK_SUSPEND),
            "cap_audit_read" => Ok(Capability::CAP_AUDIT_READ),
            "cap_perfmon" => Ok(Capability::CAP_PERFMON),
            "cap_bpf" => Ok(Capability::CAP_BPF),
            "cap_checkpoint_restore" => Ok(Capability::CAP_CHECKPOINT_RESTORE),
            _ => Err(CapsError(Errno::EINVAL)),
        }
    }
}

#[cfg(feature = "oci")]
impl Capability {
    pub fn spec(&self) -> SpecCapability {
        match *self {
            Capability::CAP_CHOWN => SpecCapability::Chown,
            Capability::CAP_DAC_OVERRIDE => SpecCapability::DacOverride,
            Capability::CAP_DAC_READ_SEARCH => SpecCapability::DacReadSearch,
            Capability::CAP_FOWNER => SpecCapability::Fowner,
            Capability::CAP_FSETID => SpecCapability::Fsetid,
            Capability::CAP_KILL => SpecCapability::Kill,
            Capability::CAP_SETGID => SpecCapability::Setgid,
            Capability::CAP_SETUID => SpecCapability::Setuid,
            Capability::CAP_SETPCAP => SpecCapability::Setpcap,
            Capability::CAP_LINUX_IMMUTABLE => SpecCapability::LinuxImmutable,
            Capability::CAP_NET_BIND_SERVICE => SpecCapability::NetBindService,
            Capability::CAP_NET_BROADCAST => SpecCapability::NetBroadcast,
            Capability::CAP_NET_ADMIN => SpecCapability::NetAdmin,
            Capability::CAP_NET_RAW => SpecCapability::NetRaw,
            Capability::CAP_IPC_LOCK => SpecCapability::IpcLock,
            Capability::CAP_IPC_OWNER => SpecCapability::IpcOwner,
            Capability::CAP_SYS_MODULE => SpecCapability::SysModule,
            Capability::CAP_SYS_RAWIO => SpecCapability::SysRawio,
            Capability::CAP_SYS_CHROOT => SpecCapability::SysChroot,
            Capability::CAP_SYS_PTRACE => SpecCapability::SysPtrace,
            Capability::CAP_SYS_PACCT => SpecCapability::SysPacct,
            Capability::CAP_SYS_ADMIN => SpecCapability::SysAdmin,
            Capability::CAP_SYS_BOOT => SpecCapability::SysBoot,
            Capability::CAP_SYS_NICE => SpecCapability::SysNice,
            Capability::CAP_SYS_RESOURCE => SpecCapability::SysResource,
            Capability::CAP_SYS_TIME => SpecCapability::SysTime,
            Capability::CAP_SYS_TTY_CONFIG => SpecCapability::SysTtyConfig,
            Capability::CAP_MKNOD => SpecCapability::Mknod,
            Capability::CAP_LEASE => SpecCapability::Lease,
            Capability::CAP_AUDIT_WRITE => SpecCapability::AuditWrite,
            Capability::CAP_AUDIT_CONTROL => SpecCapability::AuditControl,
            Capability::CAP_SETFCAP => SpecCapability::Setfcap,
            Capability::CAP_MAC_OVERRIDE => SpecCapability::MacOverride,
            Capability::CAP_MAC_ADMIN => SpecCapability::MacAdmin,
            Capability::CAP_SYSLOG => SpecCapability::Syslog,
            Capability::CAP_WAKE_ALARM => SpecCapability::WakeAlarm,
            Capability::CAP_BLOCK_SUSPEND => SpecCapability::BlockSuspend,
            Capability::CAP_AUDIT_READ => SpecCapability::AuditRead,
            Capability::CAP_PERFMON => SpecCapability::Perfmon,
            Capability::CAP_BPF => SpecCapability::Bpf,
            Capability::CAP_CHECKPOINT_RESTORE => SpecCapability::CheckpointRestore,
            Capability::__Nonexhaustive => unreachable!("invalid capability"),
        }
    }
}

impl TryFrom<Capabilities> for Capability {
    type Error = CapsError;

    fn try_from(caps: Capabilities) -> Result<Self, Self::Error> {
        let bits = caps.bits();

        // Capabilities must have exactly one bit set.
        if bits == 0 || (bits & (bits - 1)) != 0 {
            return Err(CapsError(Errno::EINVAL));
        }

        let idx = u8::try_from(bits.trailing_zeros()).or(Err(CapsError(Errno::EINVAL)))?;

        // SAFETY: `caps` came from a bitflags mask defined in `Capabilities::all()`.
        // That means its bit index corresponds exactly to a valid `Capability`
        // discriminant (`repr(u8)` matches kernel index).
        Ok(unsafe { std::mem::transmute::<u8, Capability>(idx) })
    }
}

impl From<Capability> for Capabilities {
    #[inline]
    fn from(c: Capability) -> Self {
        Capabilities::from_bits_truncate(c.bitmask())
    }
}

impl Capability {
    /// Returns the bitmask corresponding to this capability value.
    pub const fn bitmask(self) -> u64 {
        1u64 << (self as u8)
    }

    /// Returns the index of this capability, i.e. its kernel-defined value.
    pub const fn index(self) -> u8 {
        self as u8
    }
}

bitflags! {
    /// A compact bitmask of Linux capabilities.
    ///
    /// Each bit corresponds to the kernel-defined capability number (see `nr`).
    /// We use `u64`, which covers all currently defined capabilities (< 64).
    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
    pub struct Capabilities: u64 {
        const CAP_CHOWN               = Capability::bitmask(Capability::CAP_CHOWN);
        const CAP_DAC_OVERRIDE        = Capability::bitmask(Capability::CAP_DAC_OVERRIDE);
        const CAP_DAC_READ_SEARCH     = Capability::bitmask(Capability::CAP_DAC_READ_SEARCH);
        const CAP_FOWNER              = Capability::bitmask(Capability::CAP_FOWNER);
        const CAP_FSETID              = Capability::bitmask(Capability::CAP_FSETID);
        const CAP_KILL                = Capability::bitmask(Capability::CAP_KILL);
        const CAP_SETGID              = Capability::bitmask(Capability::CAP_SETGID);
        const CAP_SETUID              = Capability::bitmask(Capability::CAP_SETUID);
        const CAP_SETPCAP             = Capability::bitmask(Capability::CAP_SETPCAP);
        const CAP_LINUX_IMMUTABLE     = Capability::bitmask(Capability::CAP_LINUX_IMMUTABLE);
        const CAP_NET_BIND_SERVICE    = Capability::bitmask(Capability::CAP_NET_BIND_SERVICE);
        const CAP_NET_BROADCAST       = Capability::bitmask(Capability::CAP_NET_BROADCAST);
        const CAP_NET_ADMIN           = Capability::bitmask(Capability::CAP_NET_ADMIN);
        const CAP_NET_RAW             = Capability::bitmask(Capability::CAP_NET_RAW);
        const CAP_IPC_LOCK            = Capability::bitmask(Capability::CAP_IPC_LOCK);
        const CAP_IPC_OWNER           = Capability::bitmask(Capability::CAP_IPC_OWNER);
        const CAP_SYS_MODULE          = Capability::bitmask(Capability::CAP_SYS_MODULE);
        const CAP_SYS_RAWIO           = Capability::bitmask(Capability::CAP_SYS_RAWIO);
        const CAP_SYS_CHROOT          = Capability::bitmask(Capability::CAP_SYS_CHROOT);
        const CAP_SYS_PTRACE          = Capability::bitmask(Capability::CAP_SYS_PTRACE);
        const CAP_SYS_PACCT           = Capability::bitmask(Capability::CAP_SYS_PACCT);
        const CAP_SYS_ADMIN           = Capability::bitmask(Capability::CAP_SYS_ADMIN);
        const CAP_SYS_BOOT            = Capability::bitmask(Capability::CAP_SYS_BOOT);
        const CAP_SYS_NICE            = Capability::bitmask(Capability::CAP_SYS_NICE);
        const CAP_SYS_RESOURCE        = Capability::bitmask(Capability::CAP_SYS_RESOURCE);
        const CAP_SYS_TIME            = Capability::bitmask(Capability::CAP_SYS_TIME);
        const CAP_SYS_TTY_CONFIG      = Capability::bitmask(Capability::CAP_SYS_TTY_CONFIG);
        const CAP_MKNOD               = Capability::bitmask(Capability::CAP_MKNOD);
        const CAP_LEASE               = Capability::bitmask(Capability::CAP_LEASE);
        const CAP_AUDIT_WRITE         = Capability::bitmask(Capability::CAP_AUDIT_WRITE);
        const CAP_AUDIT_CONTROL       = Capability::bitmask(Capability::CAP_AUDIT_CONTROL);
        const CAP_SETFCAP             = Capability::bitmask(Capability::CAP_SETFCAP);
        const CAP_MAC_OVERRIDE        = Capability::bitmask(Capability::CAP_MAC_OVERRIDE);
        const CAP_MAC_ADMIN           = Capability::bitmask(Capability::CAP_MAC_ADMIN);
        const CAP_SYSLOG              = Capability::bitmask(Capability::CAP_SYSLOG);
        const CAP_WAKE_ALARM          = Capability::bitmask(Capability::CAP_WAKE_ALARM);
        const CAP_BLOCK_SUSPEND       = Capability::bitmask(Capability::CAP_BLOCK_SUSPEND);
        const CAP_AUDIT_READ          = Capability::bitmask(Capability::CAP_AUDIT_READ);
        const CAP_PERFMON             = Capability::bitmask(Capability::CAP_PERFMON);
        const CAP_BPF                 = Capability::bitmask(Capability::CAP_BPF);
        const CAP_CHECKPOINT_RESTORE  = Capability::bitmask(Capability::CAP_CHECKPOINT_RESTORE);
    }
}

/// Check if a thread contains a capability in a set.
///
/// Check if set `cset` for thread `tid` contains capability `cap`.
/// If `tid` is `None`, this operates on current thread (tid=0).
/// It cannot check Ambient or Bounding capabilities of other processes.
pub fn has_cap(tid: Option<i32>, cset: CapSet, cap: Capability) -> Result<bool, CapsError> {
    let t = tid.unwrap_or(0);
    match cset {
        CapSet::Ambient if t == 0 => ambient::has_cap(cap),
        CapSet::Bounding if t == 0 => bounding::has_cap(cap),
        CapSet::Effective | CapSet::Inheritable | CapSet::Permitted => base::has_cap(t, cset, cap),
        _ => Err(CapsError(Errno::EOPNOTSUPP)),
    }
}

/// Return all capabilities in a set for a thread.
///
/// Return current content of set `cset` for thread `tid`.
/// If `tid` is `None`, this operates on current thread (tid=0).
/// It cannot read Ambient or Bounding capabilities of other processes.
pub fn read(tid: Option<i32>, cset: CapSet) -> Result<Capabilities, CapsError> {
    let t = tid.unwrap_or(0);
    match cset {
        CapSet::Ambient if t == 0 => ambient::read(),
        CapSet::Bounding if t == 0 => bounding::read(),
        CapSet::Effective | CapSet::Inheritable | CapSet::Permitted => base::read(t, cset),
        _ => Err(CapsError(Errno::EOPNOTSUPP)),
    }
}

/// Set a capability set for a thread to a new value.
///
/// All and only capabilities in `value` will be set for set `cset` for thread `tid`.
/// If `tid` is `None`, this operates on current thread (tid=0).
/// It cannot manipulate Ambient set of other processes.
/// Capabilities cannot be set in Bounding set.
pub fn set(tid: Option<i32>, cset: CapSet, value: Capabilities) -> Result<(), CapsError> {
    let t = tid.unwrap_or(0);
    match cset {
        CapSet::Ambient if t == 0 => ambient::set(value),
        CapSet::Effective | CapSet::Inheritable | CapSet::Permitted => base::set(t, cset, value),
        _ => Err(CapsError(Errno::EOPNOTSUPP)),
    }
}

/// Set effective, permitted and inheritable capabilities at once.
pub fn set_all(
    tid: Option<i32>,
    eff: Capabilities,
    perm: Capabilities,
    inh: Capabilities,
) -> Result<(), CapsError> {
    base::set_epi(tid.unwrap_or(0), eff, perm, inh)
}

/// Clear all capabilities in a set for a thread.
///
/// All capabilities will be cleared from set `cset` for thread `tid`.
/// If `tid` is `None`, this operates on current thread (tid=0).
/// It cannot manipulate Ambient or Bounding set of other processes.
pub fn clear(tid: Option<i32>, cset: CapSet) -> Result<(), CapsError> {
    let t = tid.unwrap_or(0);
    match cset {
        CapSet::Ambient if t == 0 => ambient::clear(),
        CapSet::Bounding if t == 0 => bounding::clear(),
        CapSet::Effective | CapSet::Permitted | CapSet::Inheritable => base::clear(t, cset),
        _ => Err(CapsError(Errno::EOPNOTSUPP)),
    }
}

/// Raise a single capability in a set for a thread.
///
/// Capabilities `cap` will be raised from set `cset` of thread `tid`.
/// If `tid` is `None`, this operates on current thread (tid=0).
/// It cannot manipulate Ambient set of other processes.
/// Capabilities cannot be raised in Bounding set.
pub fn raise(tid: Option<i32>, cset: CapSet, cap: Capability) -> Result<(), CapsError> {
    let t = tid.unwrap_or(0);
    match cset {
        CapSet::Ambient if t == 0 => ambient::raise(cap),
        CapSet::Effective | CapSet::Permitted | CapSet::Inheritable => base::raise(t, cset, cap),
        _ => Err(CapsError(Errno::EOPNOTSUPP)),
    }
}

/// Drop a single capability from a set for a thread.
///
/// Capabilities `cap` will be dropped from set `cset` of thread `tid`.
/// If `tid` is `None`, this operates on current thread (tid=0).
/// It cannot manipulate Ambient and Bounding sets of other processes.
pub fn drop(tid: Option<i32>, cset: CapSet, cap: Capability) -> Result<(), CapsError> {
    let t = tid.unwrap_or(0);
    match cset {
        CapSet::Ambient if t == 0 => ambient::drop(cap),
        CapSet::Bounding if t == 0 => bounding::drop(cap),
        CapSet::Effective | CapSet::Permitted | CapSet::Inheritable => base::drop(t, cset, cap),
        _ => Err(CapsError(Errno::EOPNOTSUPP)),
    }
}

/// Convert an informal capability name into a canonical form.
///
/// This converts the input string to uppercase and ensures that it starts with
/// `CAP_`, prepending it if necessary. It performs no validity checks so the
/// output may not represent an actual capability. To check if it is, pass it
/// to [`from_str`].
///
/// [`from_str`]: enum.Capability.html#method.from_str
pub fn to_canonical(name: &str) -> String {
    let uppername = name.to_ascii_uppercase();
    if uppername.starts_with("CAP_") {
        uppername
    } else {
        ["CAP_", &uppername].concat()
    }
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;

    use super::*;

    #[test]
    fn test_all_roundtrip() {
        let all = Capabilities::all();
        assert!(all.iter().count() > 0);
        for c in all {
            let c: Capability = c.try_into().unwrap();
            let name = c.to_string();
            let parsed: Capability = name.parse().unwrap();
            assert_eq!(c, parsed);
        }
    }

    #[test]
    fn test_parse_invalid() {
        let p1 = Capability::from_str("CAP_FOO");
        let p1_err = p1.unwrap_err();
        assert_eq!(p1_err.0, Errno::EINVAL);
        let p2: Result<Capability, CapsError> = "CAP_BAR".parse();
        assert!(p2.is_err());
    }

    #[test]
    fn test_to_canonical() {
        let p1 = "foo";
        assert!(Capability::from_str(&to_canonical(p1)).is_err());
        let p2 = "sys_admin";
        assert!(Capability::from_str(&to_canonical(p2)).is_ok());
        let p3 = "CAP_SYS_CHROOT";
        assert!(Capability::from_str(&to_canonical(p3)).is_ok());
    }
}
