syd 3.55.0

rock-solid application kernel
Documentation
/*!
Detect kernel features at runtime.

This module exposes methods to perform detection of kernel
features at runtime. This allows applications to auto-detect
whether recent options are implemented by the currently
running kernel.

## Example

```ignore
let ambient = caps::runtime::ambient_set_supported().is_ok();
println!("Supported ambient set: {}", ambient);

let all = caps::runtime::procfs_all_supported(None)
    .or_else(|_| caps::runtime::thread_all_supported())?;
println!("Supported capabilities: {}", all.bits().count_ones());
```
!*/

use std::{ffi::CStr, io::Read};

use nix::{errno::Errno, fcntl::OFlag};

use super::{ambient, CapSet, Capabilities, Capability};
use crate::{
    caps::errors::CapsError,
    compat::{openat2, ResolveFlag},
    err::err2no,
    fd::is_empty_file,
    lookup::safe_open_how,
    path::XPath,
    proc::proc_open,
    retry::retry_on_eintr,
};

/// Check whether the running kernel supports the ambient set.
///
/// Ambient set was introduced in Linux kernel 4.3. On recent kernels
/// where the ambient set is supported, this will return `Ok`.
/// On a legacy kernel, an `Err` is returned instead.
pub fn ambient_set_supported() -> Result<(), CapsError> {
    ambient::has_cap(Capability::CAP_CHOWN)?;
    Ok(())
}

/// Return the set of all capabilities supported by the running kernel.
///
/// This requires a mounted proc(5) and a kernel version >= 3.2. By
/// default, it uses `/proc/` as the proc(5) mountpoint.
pub fn procfs_all_supported(proc_mountpoint: Option<&XPath>) -> Result<Capabilities, CapsError> {
    /// See `man 2 capabilities`.
    const LAST_CAP_FILEPATH: &CStr = c"sys/kernel/cap_last_cap";

    // Open file safely.
    let mut fd = proc_open(proc_mountpoint)
        .and_then(|fd| {
            let how_xdev = safe_open_how(
                OFlag::O_RDONLY | OFlag::O_NOCTTY,
                ResolveFlag::RESOLVE_NO_XDEV,
            );
            #[expect(clippy::disallowed_methods)]
            retry_on_eintr(|| openat2(&fd, LAST_CAP_FILEPATH, how_xdev))
        })
        .map_err(CapsError)?;

    // Check the file is an empty file or bail out.
    if !is_empty_file(&fd).unwrap_or(false) {
        return Err(CapsError(Errno::EBADFD));
    }

    let max_cap: u8 = {
        let mut buf = String::with_capacity(4);
        fd.read_to_string(&mut buf)
            .map_err(|err| CapsError(err2no(&err)))?;
        buf.trim_end().parse().or(Err(CapsError(Errno::EINVAL)))?
    };
    drop(fd);

    let supported = {
        let mask: u64 = if max_cap >= 63 {
            u64::MAX
        } else {
            (1u64 << (u64::from(max_cap) + 1)) - 1
        };

        super::Capabilities::from_bits_retain(mask)
    };

    Ok(supported)
}

/// Return the set of all capabilities supported on the current thread.
///
/// This does not require a mounted `procfs`, and it works with any
/// kernel version >= 2.6.25.
/// It internally uses `prctl(2)` and `PR_CAPBSET_READ`; if those are
/// unavailable, this will result in an empty set.
pub fn thread_all_supported() -> Result<Capabilities, CapsError> {
    let mut supported = Capabilities::empty();

    for idx in 0..64u8 {
        let cap = Capability::from_index(idx);
        match super::has_cap(None, CapSet::Bounding, cap) {
            Ok(_) => supported.insert(Capabilities::from_bits_retain(cap.bitmask())),
            Err(CapsError(Errno::EINVAL)) => break,
            Err(err) => return Err(err),
        }
    }

    Ok(supported)
}