1 use std::ffi::CStr;
2 use std::ffi::CString;
3 use std::fs;
4 use std::mem::transmute;
5 use std::ops::Deref;
6 use std::os::fd::AsRawFd;
7 use std::os::fd::BorrowedFd;
8 use std::os::raw::c_char;
9 use std::path::Path;
10 use std::ptr::NonNull;
11 use std::sync::OnceLock;
12
13 use crate::error::IntoError;
14 use crate::Error;
15 use crate::Result;
16
str_to_cstring(s: &str) -> Result<CString>17 pub fn str_to_cstring(s: &str) -> Result<CString> {
18 CString::new(s).map_err(|e| Error::with_invalid_data(e.to_string()))
19 }
20
path_to_cstring<P: AsRef<Path>>(path: P) -> Result<CString>21 pub fn path_to_cstring<P: AsRef<Path>>(path: P) -> Result<CString> {
22 let path_str = path.as_ref().to_str().ok_or_else(|| {
23 Error::with_invalid_data(format!("{} is not valid unicode", path.as_ref().display()))
24 })?;
25
26 str_to_cstring(path_str)
27 }
28
c_ptr_to_string(p: *const c_char) -> Result<String>29 pub fn c_ptr_to_string(p: *const c_char) -> Result<String> {
30 if p.is_null() {
31 return Err(Error::with_invalid_data("Null string"));
32 }
33
34 let c_str = unsafe { CStr::from_ptr(p) };
35 Ok(c_str
36 .to_str()
37 .map_err(|e| Error::with_invalid_data(e.to_string()))?
38 .to_owned())
39 }
40
41 /// Convert a `[c_char]` into a `CStr`.
c_char_slice_to_cstr(s: &[c_char]) -> Option<&CStr>42 pub fn c_char_slice_to_cstr(s: &[c_char]) -> Option<&CStr> {
43 // TODO: Switch to using `CStr::from_bytes_until_nul` once we require
44 // Rust 1.69.0.
45 let nul_idx = s
46 .iter()
47 .enumerate()
48 .find_map(|(idx, b)| (*b == 0).then_some(idx))?;
49 let cstr =
50 // SAFETY: `c_char` and `u8` are both just one byte plain old data
51 // types.
52 CStr::from_bytes_with_nul(unsafe { transmute::<&[c_char], &[u8]>(&s[0..=nul_idx]) })
53 .unwrap();
54 Some(cstr)
55 }
56
57 /// Round up a number to the next multiple of `r`
roundup(num: usize, r: usize) -> usize58 pub fn roundup(num: usize, r: usize) -> usize {
59 ((num + (r - 1)) / r) * r
60 }
61
62 /// Get the number of CPUs in the system, e.g., to interact with per-cpu maps.
num_possible_cpus() -> Result<usize>63 pub fn num_possible_cpus() -> Result<usize> {
64 let ret = unsafe { libbpf_sys::libbpf_num_possible_cpus() };
65 parse_ret(ret).map(|()| ret as usize)
66 }
67
parse_ret(ret: i32) -> Result<()>68 pub fn parse_ret(ret: i32) -> Result<()> {
69 if ret < 0 {
70 // Error code is returned negative, flip to positive to match errno
71 Err(Error::from_raw_os_error(-ret))
72 } else {
73 Ok(())
74 }
75 }
76
parse_ret_i32(ret: i32) -> Result<i32>77 pub fn parse_ret_i32(ret: i32) -> Result<i32> {
78 parse_ret(ret).map(|()| ret)
79 }
80
81
82 /// Check the returned pointer of a `libbpf` call, extracting any
83 /// reported errors and converting them.
validate_bpf_ret<T>(ptr: *mut T) -> Result<NonNull<T>>84 pub fn validate_bpf_ret<T>(ptr: *mut T) -> Result<NonNull<T>> {
85 // SAFETY: `libbpf_get_error` is always safe to call.
86 match unsafe { libbpf_sys::libbpf_get_error(ptr as *const _) } {
87 0 => {
88 debug_assert!(!ptr.is_null());
89 // SAFETY: libbpf guarantees that if NULL is returned an
90 // error it set, so we will always end up with a
91 // valid pointer when `libbpf_get_error` returned 0.
92 let ptr = unsafe { NonNull::new_unchecked(ptr) };
93 Ok(ptr)
94 }
95 err => Err(Error::from_raw_os_error(-err as i32)),
96 }
97 }
98
99 /// An enum describing type of eBPF object.
100 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
101 pub enum BpfObjectType {
102 /// The object is a map.
103 Map,
104 /// The object is a program.
105 Program,
106 /// The object is a BPF link.
107 Link,
108 }
109
110 /// Get type of BPF object by fd.
111 ///
112 /// This information is not exported directly by bpf_*_get_info_by_fd() functions,
113 /// as kernel relies on the userspace code to know what kind of object it
114 /// queries. The type of object can be recovered by fd only from the proc
115 /// filesystem. The same approach is used in bpftool.
object_type_from_fd(fd: BorrowedFd<'_>) -> Result<BpfObjectType>116 pub fn object_type_from_fd(fd: BorrowedFd<'_>) -> Result<BpfObjectType> {
117 let fd_link = format!("/proc/self/fd/{}", fd.as_raw_fd());
118 let link_type = fs::read_link(fd_link)
119 .map_err(|e| Error::with_invalid_data(format!("can't read fd link: {}", e)))?;
120 let link_type = link_type
121 .to_str()
122 .ok_or_invalid_data(|| "can't convert PathBuf to str")?;
123
124 match link_type {
125 "anon_inode:bpf-link" => Ok(BpfObjectType::Link),
126 "anon_inode:bpf-map" => Ok(BpfObjectType::Map),
127 "anon_inode:bpf-prog" => Ok(BpfObjectType::Program),
128 other => Err(Error::with_invalid_data(format!(
129 "unknown type of BPF fd: {other}"
130 ))),
131 }
132 }
133
134 // Fix me, If std::sync::LazyLock is stable(https://github.com/rust-lang/rust/issues/109736).
135 pub(crate) struct LazyLock<T> {
136 cell: OnceLock<T>,
137 init: fn() -> T,
138 }
139
140 impl<T> LazyLock<T> {
new(f: fn() -> T) -> Self141 pub const fn new(f: fn() -> T) -> Self {
142 Self {
143 cell: OnceLock::new(),
144 init: f,
145 }
146 }
147 }
148
149 impl<T> Deref for LazyLock<T> {
150 type Target = T;
151 #[inline]
deref(&self) -> &T152 fn deref(&self) -> &T {
153 self.cell.get_or_init(self.init)
154 }
155 }
156
157 #[cfg(test)]
158 mod tests {
159 use super::*;
160
161 use std::io;
162 use std::os::fd::AsFd;
163
164 use tempfile::NamedTempFile;
165
166
167 #[test]
test_roundup()168 fn test_roundup() {
169 for i in 1..=256 {
170 let up = roundup(i, 8);
171 assert!(up % 8 == 0);
172 assert!(i <= up);
173 assert!(up - i < 8);
174 }
175 }
176
177 #[test]
test_roundup_multiples()178 fn test_roundup_multiples() {
179 for i in (8..=256).step_by(8) {
180 assert_eq!(roundup(i, 8), i);
181 }
182 }
183
184 #[test]
test_num_possible_cpus()185 fn test_num_possible_cpus() {
186 let num = num_possible_cpus().unwrap();
187 assert!(num > 0);
188 }
189
190 /// Check that we can convert a `[c_char]` into a `CStr`.
191 #[test]
c_char_slice_conversion()192 fn c_char_slice_conversion() {
193 let slice = [];
194 assert_eq!(c_char_slice_to_cstr(&slice), None);
195
196 let slice = [0];
197 assert_eq!(
198 c_char_slice_to_cstr(&slice).unwrap(),
199 CStr::from_bytes_with_nul(b"\0").unwrap()
200 );
201
202 let slice = ['a' as _, 'b' as _, 'c' as _, 0 as _];
203 assert_eq!(
204 c_char_slice_to_cstr(&slice).unwrap(),
205 CStr::from_bytes_with_nul(b"abc\0").unwrap()
206 );
207
208 // Missing terminating NUL byte.
209 let slice = ['a' as _, 'b' as _, 'c' as _];
210 assert_eq!(c_char_slice_to_cstr(&slice), None);
211 }
212
213 /// Check that object_type_from_fd() doesn't allow descriptors of usual
214 /// files to be used. Testing with BPF objects requires BPF to be
215 /// loaded.
216 #[test]
test_object_type_from_fd_with_unexpected_fds()217 fn test_object_type_from_fd_with_unexpected_fds() {
218 let not_object = NamedTempFile::new().unwrap();
219
220 let _ = object_type_from_fd(not_object.as_fd())
221 .expect_err("a common file was treated as a BPF object");
222 let _ = object_type_from_fd(io::stdout().as_fd())
223 .expect_err("the stdout fd was treated as a BPF object");
224 }
225 }
226