| use std::ffi::CStr; |
| use std::ffi::CString; |
| use std::fs; |
| use std::mem::transmute; |
| use std::ops::Deref; |
| use std::os::fd::AsRawFd; |
| use std::os::fd::BorrowedFd; |
| use std::os::raw::c_char; |
| use std::path::Path; |
| use std::ptr::NonNull; |
| use std::sync::OnceLock; |
| |
| use crate::error::IntoError; |
| use crate::Error; |
| use crate::Result; |
| |
| pub fn str_to_cstring(s: &str) -> Result<CString> { |
| CString::new(s).map_err(|e| Error::with_invalid_data(e.to_string())) |
| } |
| |
| pub fn path_to_cstring<P: AsRef<Path>>(path: P) -> Result<CString> { |
| let path_str = path.as_ref().to_str().ok_or_else(|| { |
| Error::with_invalid_data(format!("{} is not valid unicode", path.as_ref().display())) |
| })?; |
| |
| str_to_cstring(path_str) |
| } |
| |
| pub fn c_ptr_to_string(p: *const c_char) -> Result<String> { |
| if p.is_null() { |
| return Err(Error::with_invalid_data("Null string")); |
| } |
| |
| let c_str = unsafe { CStr::from_ptr(p) }; |
| Ok(c_str |
| .to_str() |
| .map_err(|e| Error::with_invalid_data(e.to_string()))? |
| .to_owned()) |
| } |
| |
| /// Convert a `[c_char]` into a `CStr`. |
| pub fn c_char_slice_to_cstr(s: &[c_char]) -> Option<&CStr> { |
| // TODO: Switch to using `CStr::from_bytes_until_nul` once we require |
| // Rust 1.69.0. |
| let nul_idx = s |
| .iter() |
| .enumerate() |
| .find_map(|(idx, b)| (*b == 0).then_some(idx))?; |
| let cstr = |
| // SAFETY: `c_char` and `u8` are both just one byte plain old data |
| // types. |
| CStr::from_bytes_with_nul(unsafe { transmute::<&[c_char], &[u8]>(&s[0..=nul_idx]) }) |
| .unwrap(); |
| Some(cstr) |
| } |
| |
| /// Round up a number to the next multiple of `r` |
| pub fn roundup(num: usize, r: usize) -> usize { |
| ((num + (r - 1)) / r) * r |
| } |
| |
| /// Get the number of CPUs in the system, e.g., to interact with per-cpu maps. |
| pub fn num_possible_cpus() -> Result<usize> { |
| let ret = unsafe { libbpf_sys::libbpf_num_possible_cpus() }; |
| parse_ret(ret).map(|()| ret as usize) |
| } |
| |
| pub fn parse_ret(ret: i32) -> Result<()> { |
| if ret < 0 { |
| // Error code is returned negative, flip to positive to match errno |
| Err(Error::from_raw_os_error(-ret)) |
| } else { |
| Ok(()) |
| } |
| } |
| |
| pub fn parse_ret_i32(ret: i32) -> Result<i32> { |
| parse_ret(ret).map(|()| ret) |
| } |
| |
| |
| /// Check the returned pointer of a `libbpf` call, extracting any |
| /// reported errors and converting them. |
| pub fn validate_bpf_ret<T>(ptr: *mut T) -> Result<NonNull<T>> { |
| // SAFETY: `libbpf_get_error` is always safe to call. |
| match unsafe { libbpf_sys::libbpf_get_error(ptr as *const _) } { |
| 0 => { |
| debug_assert!(!ptr.is_null()); |
| // SAFETY: libbpf guarantees that if NULL is returned an |
| // error it set, so we will always end up with a |
| // valid pointer when `libbpf_get_error` returned 0. |
| let ptr = unsafe { NonNull::new_unchecked(ptr) }; |
| Ok(ptr) |
| } |
| err => Err(Error::from_raw_os_error(-err as i32)), |
| } |
| } |
| |
| /// An enum describing type of eBPF object. |
| #[derive(Copy, Clone, PartialEq, Eq, Debug)] |
| pub enum BpfObjectType { |
| /// The object is a map. |
| Map, |
| /// The object is a program. |
| Program, |
| /// The object is a BPF link. |
| Link, |
| } |
| |
| /// Get type of BPF object by fd. |
| /// |
| /// This information is not exported directly by bpf_*_get_info_by_fd() functions, |
| /// as kernel relies on the userspace code to know what kind of object it |
| /// queries. The type of object can be recovered by fd only from the proc |
| /// filesystem. The same approach is used in bpftool. |
| pub fn object_type_from_fd(fd: BorrowedFd<'_>) -> Result<BpfObjectType> { |
| let fd_link = format!("/proc/self/fd/{}", fd.as_raw_fd()); |
| let link_type = fs::read_link(fd_link) |
| .map_err(|e| Error::with_invalid_data(format!("can't read fd link: {}", e)))?; |
| let link_type = link_type |
| .to_str() |
| .ok_or_invalid_data(|| "can't convert PathBuf to str")?; |
| |
| match link_type { |
| "anon_inode:bpf-link" => Ok(BpfObjectType::Link), |
| "anon_inode:bpf-map" => Ok(BpfObjectType::Map), |
| "anon_inode:bpf-prog" => Ok(BpfObjectType::Program), |
| other => Err(Error::with_invalid_data(format!( |
| "unknown type of BPF fd: {other}" |
| ))), |
| } |
| } |
| |
| // Fix me, If std::sync::LazyLock is stable(https://github.com/rust-lang/rust/issues/109736). |
| pub(crate) struct LazyLock<T> { |
| cell: OnceLock<T>, |
| init: fn() -> T, |
| } |
| |
| impl<T> LazyLock<T> { |
| pub const fn new(f: fn() -> T) -> Self { |
| Self { |
| cell: OnceLock::new(), |
| init: f, |
| } |
| } |
| } |
| |
| impl<T> Deref for LazyLock<T> { |
| type Target = T; |
| #[inline] |
| fn deref(&self) -> &T { |
| self.cell.get_or_init(self.init) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| use std::io; |
| use std::os::fd::AsFd; |
| |
| use tempfile::NamedTempFile; |
| |
| |
| #[test] |
| fn test_roundup() { |
| for i in 1..=256 { |
| let up = roundup(i, 8); |
| assert!(up % 8 == 0); |
| assert!(i <= up); |
| assert!(up - i < 8); |
| } |
| } |
| |
| #[test] |
| fn test_roundup_multiples() { |
| for i in (8..=256).step_by(8) { |
| assert_eq!(roundup(i, 8), i); |
| } |
| } |
| |
| #[test] |
| fn test_num_possible_cpus() { |
| let num = num_possible_cpus().unwrap(); |
| assert!(num > 0); |
| } |
| |
| /// Check that we can convert a `[c_char]` into a `CStr`. |
| #[test] |
| fn c_char_slice_conversion() { |
| let slice = []; |
| assert_eq!(c_char_slice_to_cstr(&slice), None); |
| |
| let slice = [0]; |
| assert_eq!( |
| c_char_slice_to_cstr(&slice).unwrap(), |
| CStr::from_bytes_with_nul(b"\0").unwrap() |
| ); |
| |
| let slice = ['a' as _, 'b' as _, 'c' as _, 0 as _]; |
| assert_eq!( |
| c_char_slice_to_cstr(&slice).unwrap(), |
| CStr::from_bytes_with_nul(b"abc\0").unwrap() |
| ); |
| |
| // Missing terminating NUL byte. |
| let slice = ['a' as _, 'b' as _, 'c' as _]; |
| assert_eq!(c_char_slice_to_cstr(&slice), None); |
| } |
| |
| /// Check that object_type_from_fd() doesn't allow descriptors of usual |
| /// files to be used. Testing with BPF objects requires BPF to be |
| /// loaded. |
| #[test] |
| fn test_object_type_from_fd_with_unexpected_fds() { |
| let not_object = NamedTempFile::new().unwrap(); |
| |
| let _ = object_type_from_fd(not_object.as_fd()) |
| .expect_err("a common file was treated as a BPF object"); |
| let _ = object_type_from_fd(io::stdout().as_fd()) |
| .expect_err("the stdout fd was treated as a BPF object"); |
| } |
| } |