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,
};
pub fn ambient_set_supported() -> Result<(), CapsError> {
ambient::has_cap(Capability::CAP_CHOWN)?;
Ok(())
}
pub fn procfs_all_supported(proc_mountpoint: Option<&XPath>) -> Result<Capabilities, CapsError> {
const LAST_CAP_FILEPATH: &CStr = c"sys/kernel/cap_last_cap";
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)?;
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)
}
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)
}