Skip to content

Commit 1e34825

Browse files
committed
Add ProgramHandle: owned, lifetime-independent handle for loaded BPF programs
Mirrors MapHandle pattern — owns fd via OwnedFd, caches name/type/tag/id. Constructors: from_prog_id, from_pinned_path, TryFrom<&Program>. Implements AsFd, pin/unpin, TryFrom<&Self> for cloning. Closes #920
1 parent a82d8be commit 1e34825

3 files changed

Lines changed: 208 additions & 0 deletions

File tree

libbpf-rs/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ pub use crate::program::Output as ProgramOutput;
147147
pub use crate::program::PerfEventOpts;
148148
pub use crate::program::Program;
149149
pub use crate::program::ProgramAttachType;
150+
pub use crate::program::ProgramHandle;
150151
pub use crate::program::ProgramImpl;
151152
pub use crate::program::ProgramMut;
152153
pub use crate::program::ProgramType;

libbpf-rs/src/program.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ use std::ffi::c_void;
66
use std::ffi::CStr;
77
use std::ffi::CString;
88
use std::ffi::OsStr;
9+
use std::ffi::OsString;
10+
use std::fmt::Debug;
11+
use std::fs::remove_file;
912
use std::io::Read;
1013
use std::marker::PhantomData;
1114
use std::mem;
@@ -1730,6 +1733,127 @@ impl<T> AsRawLibbpf for ProgramImpl<'_, T> {
17301733
}
17311734
}
17321735

1736+
/// An owned handle to a loaded BPF program.
1737+
///
1738+
/// Similar to [`MapHandle`][crate::MapHandle] for maps: owns the file descriptor
1739+
/// and caches metadata, so it can outlive the [`Object`][crate::Object] it came from.
1740+
#[derive(Debug)]
1741+
pub struct ProgramHandle {
1742+
fd: OwnedFd,
1743+
name: OsString,
1744+
ty: ProgramType,
1745+
tag: [u8; 8],
1746+
id: u32,
1747+
}
1748+
1749+
impl ProgramHandle {
1750+
fn from_fd(fd: OwnedFd) -> Result<Self> {
1751+
let mut info = libbpf_sys::bpf_prog_info::default();
1752+
let mut len = size_of::<libbpf_sys::bpf_prog_info>() as u32;
1753+
let ret = unsafe {
1754+
libbpf_sys::bpf_obj_get_info_by_fd(
1755+
fd.as_raw_fd(),
1756+
(&mut info as *mut libbpf_sys::bpf_prog_info).cast::<c_void>(),
1757+
&mut len,
1758+
)
1759+
};
1760+
util::parse_ret(ret)?;
1761+
1762+
let name_cstr = util::c_char_slice_to_cstr(&info.name)
1763+
.ok_or_else(|| Error::with_invalid_data("program name not NUL-terminated"))?;
1764+
let name = OsStr::from_bytes(name_cstr.to_bytes()).to_os_string();
1765+
1766+
Ok(Self {
1767+
fd,
1768+
name,
1769+
ty: ProgramType::from(info.type_),
1770+
tag: info.tag,
1771+
id: info.id,
1772+
})
1773+
}
1774+
1775+
/// Open a loaded program by its kernel ID.
1776+
pub fn from_prog_id(id: u32) -> Result<Self> {
1777+
Self::from_fd(Program::fd_from_id(id)?)
1778+
}
1779+
1780+
/// Open a previously pinned program from its bpffs path.
1781+
pub fn from_pinned_path<P: AsRef<Path>>(path: P) -> Result<Self> {
1782+
let fd = Program::fd_from_pinned_path(path)?;
1783+
Self::from_fd(fd)
1784+
}
1785+
1786+
/// The program's name.
1787+
pub fn name(&self) -> &OsStr {
1788+
&self.name
1789+
}
1790+
1791+
/// The `ProgramType` of this handle.
1792+
pub fn prog_type(&self) -> ProgramType {
1793+
self.ty
1794+
}
1795+
1796+
/// The 8-byte tag (instruction hash) of the program.
1797+
pub fn tag(&self) -> [u8; 8] {
1798+
self.tag
1799+
}
1800+
1801+
/// The kernel ID of this program.
1802+
pub fn id(&self) -> u32 {
1803+
self.id
1804+
}
1805+
1806+
/// [Pin](https://facebookmicrosites.github.io/bpf/blog/2018/08/31/object-lifetime.html#bpffs)
1807+
/// this program to bpffs.
1808+
pub fn pin<P: AsRef<Path>>(&self, path: P) -> Result<()> {
1809+
let path_c = util::path_to_cstring(path)?;
1810+
let ret = unsafe { libbpf_sys::bpf_obj_pin(self.fd.as_raw_fd(), path_c.as_ptr()) };
1811+
util::parse_ret(ret)
1812+
}
1813+
1814+
/// [Unpin](https://facebookmicrosites.github.io/bpf/blog/2018/08/31/object-lifetime.html#bpffs)
1815+
/// this program from bpffs.
1816+
pub fn unpin<P: AsRef<Path>>(&self, path: P) -> Result<()> {
1817+
remove_file(path).context("failed to remove pinned program")
1818+
}
1819+
}
1820+
1821+
impl AsFd for ProgramHandle {
1822+
#[inline]
1823+
fn as_fd(&self) -> BorrowedFd<'_> {
1824+
self.fd.as_fd()
1825+
}
1826+
}
1827+
1828+
impl<T: Debug> TryFrom<&ProgramImpl<'_, T>> for ProgramHandle {
1829+
type Error = Error;
1830+
1831+
fn try_from(prog: &ProgramImpl<'_, T>) -> Result<Self> {
1832+
let fd = prog
1833+
.as_fd()
1834+
.try_clone_to_owned()
1835+
.context("failed to duplicate program file descriptor")?;
1836+
Self::from_fd(fd)
1837+
}
1838+
}
1839+
1840+
impl TryFrom<&Self> for ProgramHandle {
1841+
type Error = Error;
1842+
1843+
fn try_from(other: &Self) -> Result<Self> {
1844+
Ok(Self {
1845+
fd: other
1846+
.as_fd()
1847+
.try_clone_to_owned()
1848+
.context("failed to duplicate program file descriptor")?,
1849+
name: other.name.clone(),
1850+
ty: other.ty,
1851+
tag: other.tag,
1852+
id: other.id,
1853+
})
1854+
}
1855+
}
1856+
17331857
#[cfg(test)]
17341858
mod tests {
17351859
use super::*;

libbpf-rs/tests/test.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ use libbpf_rs::Object;
5959
use libbpf_rs::ObjectBuilder;
6060
use libbpf_rs::PerfEventOpts;
6161
use libbpf_rs::Program;
62+
use libbpf_rs::ProgramHandle;
6263
use libbpf_rs::ProgramInput;
6364
use libbpf_rs::ProgramType;
6465
use libbpf_rs::RawTracepointOpts;
@@ -3032,3 +3033,85 @@ fn test_prog_info_verified_insns() {
30323033
info.verified_insns,
30333034
);
30343035
}
3036+
3037+
#[tag(root)]
3038+
#[test]
3039+
fn test_program_handle_from_prog_id() {
3040+
let mut obj = get_test_object("runqslower.bpf.o");
3041+
let prog = get_prog_mut(&mut obj, "handle__sched_switch");
3042+
let prog_id = Program::id_from_fd(prog.as_fd()).expect("failed to get program id");
3043+
3044+
let handle = ProgramHandle::from_prog_id(prog_id).expect("failed to create handle from id");
3045+
assert_eq!(handle.name(), prog.name());
3046+
assert_eq!(handle.prog_type(), prog.prog_type());
3047+
assert_eq!(handle.id(), prog_id);
3048+
}
3049+
3050+
#[tag(root)]
3051+
#[test]
3052+
fn test_program_handle_from_pinned_path() {
3053+
let path = "/sys/fs/bpf/test_prog_handle_pinned";
3054+
3055+
let mut obj = get_test_object("runqslower.bpf.o");
3056+
let mut prog = get_prog_mut(&mut obj, "handle__sched_wakeup");
3057+
let prog_id = Program::id_from_fd(prog.as_fd()).expect("failed to get program id");
3058+
prog.pin(path).expect("failed to pin program");
3059+
3060+
defer! {
3061+
let _unused = fs::remove_file(path);
3062+
}
3063+
3064+
let handle =
3065+
ProgramHandle::from_pinned_path(path).expect("failed to create handle from pinned path");
3066+
assert_eq!(handle.id(), prog_id);
3067+
assert_eq!(handle.name(), "handle__sched_wakeup");
3068+
}
3069+
3070+
#[tag(root)]
3071+
#[test]
3072+
fn test_program_handle_try_from_program() {
3073+
let mut obj = get_test_object("runqslower.bpf.o");
3074+
let prog = get_prog_mut(&mut obj, "handle__sched_switch");
3075+
3076+
let handle = ProgramHandle::try_from(&prog).expect("failed to create handle from program");
3077+
assert_eq!(handle.name(), prog.name());
3078+
assert_eq!(handle.prog_type(), prog.prog_type());
3079+
}
3080+
3081+
/// Check that cloning a `ProgramHandle` via `TryFrom<&Self>` gives a distinct fd
3082+
/// with the same metadata.
3083+
#[tag(root)]
3084+
#[test]
3085+
fn test_program_handle_clone() {
3086+
let mut obj = get_test_object("runqslower.bpf.o");
3087+
let prog = get_prog_mut(&mut obj, "handle__sched_wakeup_new");
3088+
let handle1 = ProgramHandle::try_from(&prog).expect("failed to create handle");
3089+
let handle2 = ProgramHandle::try_from(&handle1).expect("failed to clone handle");
3090+
3091+
assert_eq!(handle1.name(), handle2.name());
3092+
assert_eq!(handle1.prog_type(), handle2.prog_type());
3093+
assert_eq!(handle1.tag(), handle2.tag());
3094+
assert_eq!(handle1.id(), handle2.id());
3095+
// The cloned handle must hold its own fd.
3096+
assert_ne!(handle1.as_fd().as_raw_fd(), handle2.as_fd().as_raw_fd());
3097+
}
3098+
3099+
#[tag(root)]
3100+
#[test]
3101+
fn test_program_handle_pin_unpin() {
3102+
let path = "/sys/fs/bpf/test_prog_handle_pin_unpin";
3103+
3104+
let mut obj = get_test_object("runqslower.bpf.o");
3105+
let prog = get_prog_mut(&mut obj, "handle__sched_switch");
3106+
let handle = ProgramHandle::try_from(&prog).expect("failed to create handle");
3107+
3108+
defer! {
3109+
let _unused = fs::remove_file(path);
3110+
}
3111+
3112+
handle.pin(path).expect("failed to pin program handle");
3113+
assert!(Path::new(path).exists());
3114+
3115+
handle.unpin(path).expect("failed to unpin program handle");
3116+
assert!(!Path::new(path).exists());
3117+
}

0 commit comments

Comments
 (0)