Merge pull request #641 from aya-rs/logger-messages-plz

Miscellaneous fixes to allow running integration tests in bpf-linker
pull/646/head
Tamir Duberstein 1 year ago committed by GitHub
commit 4c0983bca9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -295,6 +295,7 @@ pub fn write_record_header(
mod test { mod test {
use super::*; use super::*;
#[test]
fn log_value_length_sufficient() { fn log_value_length_sufficient() {
assert!( assert!(
LOG_BUF_CAPACITY >= LogValueLength::MAX.into(), LOG_BUF_CAPACITY >= LogValueLength::MAX.into(),

@ -135,7 +135,7 @@ pub enum BtfError {
#[source] #[source]
io_error: std::io::Error, io_error: std::io::Error,
/// The error log produced by the kernel verifier. /// The error log produced by the kernel verifier.
verifier_log: String, verifier_log: Cow<'static, str>,
}, },
/// offset not found for symbol /// offset not found for symbol

@ -109,7 +109,7 @@ pub struct Object {
/// Program license /// Program license
pub license: CString, pub license: CString,
/// Kernel version /// Kernel version
pub kernel_version: KernelVersion, pub kernel_version: Option<u32>,
/// Program BTF /// Program BTF
pub btf: Option<Btf>, pub btf: Option<Btf>,
/// Program BTF.ext /// Program BTF.ext
@ -135,7 +135,7 @@ pub struct Program {
/// The license /// The license
pub license: CString, pub license: CString,
/// The kernel version /// The kernel version
pub kernel_version: KernelVersion, pub kernel_version: Option<u32>,
/// The section containing the program /// The section containing the program
pub section: ProgramSection, pub section: ProgramSection,
/// The section index of the program /// The section index of the program
@ -579,7 +579,7 @@ impl Object {
let kernel_version = if let Some(section) = obj.section_by_name("version") { let kernel_version = if let Some(section) = obj.section_by_name("version") {
parse_version(Section::try_from(&section)?.data, endianness)? parse_version(Section::try_from(&section)?.data, endianness)?
} else { } else {
KernelVersion::Any None
}; };
let mut bpf_obj = Object::new(endianness, license, kernel_version); let mut bpf_obj = Object::new(endianness, license, kernel_version);
@ -631,7 +631,7 @@ impl Object {
Ok(bpf_obj) Ok(bpf_obj)
} }
fn new(endianness: Endianness, license: CString, kernel_version: KernelVersion) -> Object { fn new(endianness: Endianness, license: CString, kernel_version: Option<u32>) -> Object {
Object { Object {
endianness, endianness,
license, license,
@ -1256,7 +1256,7 @@ fn parse_license(data: &[u8]) -> Result<CString, ParseError> {
.to_owned()) .to_owned())
} }
fn parse_version(data: &[u8], endianness: object::Endianness) -> Result<KernelVersion, ParseError> { fn parse_version(data: &[u8], endianness: object::Endianness) -> Result<Option<u32>, ParseError> {
let data = match data.len() { let data = match data.len() {
4 => data.try_into().unwrap(), 4 => data.try_into().unwrap(),
_ => { _ => {
@ -1271,9 +1271,10 @@ fn parse_version(data: &[u8], endianness: object::Endianness) -> Result<KernelVe
object::Endianness::Little => u32::from_le_bytes(data), object::Endianness::Little => u32::from_le_bytes(data),
}; };
Ok(match v { Ok(if v == KERNEL_VERSION_ANY {
KERNEL_VERSION_ANY => KernelVersion::Any, None
v => KernelVersion::Version(v), } else {
Some(v)
}) })
} }
@ -1301,24 +1302,6 @@ fn get_map_field(btf: &Btf, type_id: u32) -> Result<u32, BtfError> {
Ok(arr.len) Ok(arr.len)
} }
/// The parsed kernel version
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum KernelVersion {
/// Specified version
Version(u32),
/// Any version
Any,
}
impl From<KernelVersion> for u32 {
fn from(version: KernelVersion) -> u32 {
match version {
KernelVersion::Any => KERNEL_VERSION_ANY,
KernelVersion::Version(v) => v,
}
}
}
// Parsed '.bss' '.data' and '.rodata' sections. These sections are arrays of // Parsed '.bss' '.data' and '.rodata' sections. These sections are arrays of
// bytes and are relocated based on their section index. // bytes and are relocated based on their section index.
fn parse_data_map_section(section: &Section) -> Result<Map, ParseError> { fn parse_data_map_section(section: &Section) -> Result<Map, ParseError> {
@ -1592,23 +1575,20 @@ mod tests {
Err(ParseError::InvalidKernelVersion { .. }) Err(ParseError::InvalidKernelVersion { .. })
)); ));
assert_eq!( assert!(matches!(
parse_version(&0xFFFF_FFFEu32.to_le_bytes(), Endianness::Little) parse_version(&0xFFFF_FFFEu32.to_le_bytes(), Endianness::Little),
.expect("failed to parse magic version"), Ok(None)
KernelVersion::Any ));
);
assert_eq!( assert!(matches!(
parse_version(&0xFFFF_FFFEu32.to_be_bytes(), Endianness::Big) parse_version(&0xFFFF_FFFEu32.to_be_bytes(), Endianness::Big),
.expect("failed to parse magic version"), Ok(None)
KernelVersion::Any ));
);
assert_eq!( assert!(matches!(
parse_version(&1234u32.to_le_bytes(), Endianness::Little) parse_version(&1234u32.to_le_bytes(), Endianness::Little),
.expect("failed to parse magic version"), Ok(Some(1234))
KernelVersion::Version(1234) ));
);
} }
#[test] #[test]
@ -1699,11 +1679,7 @@ mod tests {
} }
fn fake_obj() -> Object { fn fake_obj() -> Object {
Object::new( Object::new(Endianness::Little, CString::new("GPL").unwrap(), None)
Endianness::Little,
CString::new("GPL").unwrap(),
KernelVersion::Any,
)
} }
#[test] #[test]
@ -1753,7 +1729,7 @@ mod tests {
obj.parse_program(&fake_section(BpfSectionKind::Program,"kprobe/foo", bytes_of(&fake_ins()))), obj.parse_program(&fake_section(BpfSectionKind::Program,"kprobe/foo", bytes_of(&fake_ins()))),
Ok((Program { Ok((Program {
license, license,
kernel_version: KernelVersion::Any, kernel_version: None,
section: ProgramSection::KProbe { .. }, section: ProgramSection::KProbe { .. },
.. }, Function { .. }, Function {
name, name,

@ -11,21 +11,31 @@ documentation = "https://docs.rs/aya"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
libc = { version = "0.2.105" } async-io = { version = "1.3", optional = true }
aya-obj = { path = "../aya-obj", version = "0.1.0", features = ["std"] } aya-obj = { path = "../aya-obj", version = "0.1.0", features = ["std"] }
thiserror = "1"
object = { version = "0.31", default-features = false, features = ["std", "read_core", "elf"] }
bitflags = "2.2.1" bitflags = "2.2.1"
bytes = "1" bytes = "1"
lazy_static = "1" lazy_static = "1"
parking_lot = { version = "0.12.0", features = ["send_guard"] } libc = { version = "0.2.105" }
tokio = { version = "1.24.0", features = ["macros", "rt", "rt-multi-thread", "net"], optional = true }
async-io = { version = "1.3", optional = true }
log = "0.4" log = "0.4"
object = { version = "0.31", default-features = false, features = [
"std",
"read_core",
"elf",
] }
parking_lot = { version = "0.12.0", features = ["send_guard"] }
thiserror = "1"
tokio = { version = "1.24.0", features = [
"macros",
"rt",
"rt-multi-thread",
"net",
], optional = true }
procfs = { version = "0.15.1", default-features = false }
[dev-dependencies] [dev-dependencies]
matches = "0.1.8"
futures = { version = "0.3.12", default-features = false, features = ["std"] } futures = { version = "0.3.12", default-features = false, features = ["std"] }
matches = "0.1.8"
[features] [features]
default = [] default = []
@ -35,4 +45,4 @@ async_std = ["async-io", "async"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
rustdoc-args = ["--cfg", "docsrs","-D", "warnings"] rustdoc-args = ["--cfg", "docsrs", "-D", "warnings"]

@ -39,7 +39,7 @@ use crate::{
is_btf_supported, is_btf_type_tag_supported, is_perf_link_supported, is_btf_supported, is_btf_type_tag_supported, is_perf_link_supported,
is_probe_read_kernel_supported, is_prog_name_supported, retry_with_verifier_logs, is_probe_read_kernel_supported, is_prog_name_supported, retry_with_verifier_logs,
}, },
util::{bytes_of, bytes_of_slice, possible_cpus, VerifierLog, POSSIBLE_CPUS}, util::{bytes_of, bytes_of_slice, possible_cpus, POSSIBLE_CPUS},
}; };
pub(crate) const BPF_OBJ_NAME_LEN: usize = 16; pub(crate) const BPF_OBJ_NAME_LEN: usize = 16;
@ -133,7 +133,7 @@ pub struct BpfLoader<'a> {
bitflags! { bitflags! {
/// Used to set the verifier log level flags in [BpfLoader](BpfLoader::verifier_log_level()). /// Used to set the verifier log level flags in [BpfLoader](BpfLoader::verifier_log_level()).
#[derive(Debug)] #[derive(Clone, Copy, Debug)]
pub struct VerifierLogLevel: u32 { pub struct VerifierLogLevel: u32 {
/// Sets no verifier logging. /// Sets no verifier logging.
const DISABLE = 0; const DISABLE = 0;
@ -349,14 +349,22 @@ impl<'a> BpfLoader<'a> {
/// # Ok::<(), aya::BpfError>(()) /// # Ok::<(), aya::BpfError>(())
/// ``` /// ```
pub fn load(&mut self, data: &[u8]) -> Result<Bpf, BpfError> { pub fn load(&mut self, data: &[u8]) -> Result<Bpf, BpfError> {
let verifier_log_level = self.verifier_log_level.bits(); let Self {
btf,
map_pin_path,
globals,
max_entries,
extensions,
verifier_log_level,
} = self;
let mut obj = Object::parse(data)?; let mut obj = Object::parse(data)?;
obj.patch_map_data(self.globals.clone())?; obj.patch_map_data(globals.clone())?;
let btf_fd = if let Some(features) = &FEATURES.btf() { let btf_fd = if let Some(features) = &FEATURES.btf() {
if let Some(btf) = obj.fixup_and_sanitize_btf(features)? { if let Some(btf) = obj.fixup_and_sanitize_btf(features)? {
// load btf to the kernel // load btf to the kernel
Some(load_btf(btf.to_bytes())?) let btf = load_btf(btf.to_bytes(), *verifier_log_level)?;
Some(btf)
} else { } else {
None None
} }
@ -364,7 +372,7 @@ impl<'a> BpfLoader<'a> {
None None
}; };
if let Some(btf) = &self.btf { if let Some(btf) = &btf {
obj.relocate_btf(btf)?; obj.relocate_btf(btf)?;
} }
let mut maps = HashMap::new(); let mut maps = HashMap::new();
@ -375,7 +383,7 @@ impl<'a> BpfLoader<'a> {
continue; continue;
} }
match self.max_entries.get(name.as_str()) { match max_entries.get(name.as_str()) {
Some(size) => obj.set_max_entries(*size), Some(size) => obj.set_max_entries(*size),
None => { None => {
if obj.map_type() == BPF_MAP_TYPE_PERF_EVENT_ARRAY as u32 if obj.map_type() == BPF_MAP_TYPE_PERF_EVENT_ARRAY as u32
@ -400,7 +408,7 @@ impl<'a> BpfLoader<'a> {
}; };
let fd = match map.obj.pinning() { let fd = match map.obj.pinning() {
PinningType::ByName => { PinningType::ByName => {
let path = match &self.map_pin_path { let path = match &map_pin_path {
Some(p) => p, Some(p) => p,
None => return Err(BpfError::NoPinPath), None => return Err(BpfError::NoPinPath),
}; };
@ -466,72 +474,72 @@ impl<'a> BpfLoader<'a> {
let section = prog_obj.section.clone(); let section = prog_obj.section.clone();
let obj = (prog_obj, function_obj); let obj = (prog_obj, function_obj);
let program = if self.extensions.contains(name.as_str()) { let program = if extensions.contains(name.as_str()) {
Program::Extension(Extension { Program::Extension(Extension {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
}) })
} else { } else {
match &section { match &section {
ProgramSection::KProbe { .. } => Program::KProbe(KProbe { ProgramSection::KProbe { .. } => Program::KProbe(KProbe {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
kind: ProbeKind::KProbe, kind: ProbeKind::KProbe,
}), }),
ProgramSection::KRetProbe { .. } => Program::KProbe(KProbe { ProgramSection::KRetProbe { .. } => Program::KProbe(KProbe {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
kind: ProbeKind::KRetProbe, kind: ProbeKind::KRetProbe,
}), }),
ProgramSection::UProbe { .. } => Program::UProbe(UProbe { ProgramSection::UProbe { .. } => Program::UProbe(UProbe {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
kind: ProbeKind::UProbe, kind: ProbeKind::UProbe,
}), }),
ProgramSection::URetProbe { .. } => Program::UProbe(UProbe { ProgramSection::URetProbe { .. } => Program::UProbe(UProbe {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
kind: ProbeKind::URetProbe, kind: ProbeKind::URetProbe,
}), }),
ProgramSection::TracePoint { .. } => Program::TracePoint(TracePoint { ProgramSection::TracePoint { .. } => Program::TracePoint(TracePoint {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
}), }),
ProgramSection::SocketFilter { .. } => { ProgramSection::SocketFilter { .. } => {
Program::SocketFilter(SocketFilter { Program::SocketFilter(SocketFilter {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
}) })
} }
ProgramSection::Xdp { frags, .. } => { ProgramSection::Xdp { frags, .. } => {
let mut data = let mut data =
ProgramData::new(prog_name, obj, btf_fd, verifier_log_level); ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level);
if *frags { if *frags {
data.flags = BPF_F_XDP_HAS_FRAGS; data.flags = BPF_F_XDP_HAS_FRAGS;
} }
Program::Xdp(Xdp { data }) Program::Xdp(Xdp { data })
} }
ProgramSection::SkMsg { .. } => Program::SkMsg(SkMsg { ProgramSection::SkMsg { .. } => Program::SkMsg(SkMsg {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
}), }),
ProgramSection::CgroupSysctl { .. } => { ProgramSection::CgroupSysctl { .. } => {
Program::CgroupSysctl(CgroupSysctl { Program::CgroupSysctl(CgroupSysctl {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
}) })
} }
ProgramSection::CgroupSockopt { attach_type, .. } => { ProgramSection::CgroupSockopt { attach_type, .. } => {
Program::CgroupSockopt(CgroupSockopt { Program::CgroupSockopt(CgroupSockopt {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
attach_type: *attach_type, attach_type: *attach_type,
}) })
} }
ProgramSection::SkSkbStreamParser { .. } => Program::SkSkb(SkSkb { ProgramSection::SkSkbStreamParser { .. } => Program::SkSkb(SkSkb {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
kind: SkSkbKind::StreamParser, kind: SkSkbKind::StreamParser,
}), }),
ProgramSection::SkSkbStreamVerdict { .. } => Program::SkSkb(SkSkb { ProgramSection::SkSkbStreamVerdict { .. } => Program::SkSkb(SkSkb {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
kind: SkSkbKind::StreamVerdict, kind: SkSkbKind::StreamVerdict,
}), }),
ProgramSection::SockOps { .. } => Program::SockOps(SockOps { ProgramSection::SockOps { .. } => Program::SockOps(SockOps {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
}), }),
ProgramSection::SchedClassifier { .. } => { ProgramSection::SchedClassifier { .. } => {
Program::SchedClassifier(SchedClassifier { Program::SchedClassifier(SchedClassifier {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
name: unsafe { name: unsafe {
CString::from_vec_unchecked(Vec::from(name.clone())) CString::from_vec_unchecked(Vec::from(name.clone()))
.into_boxed_c_str() .into_boxed_c_str()
@ -539,37 +547,37 @@ impl<'a> BpfLoader<'a> {
}) })
} }
ProgramSection::CgroupSkb { .. } => Program::CgroupSkb(CgroupSkb { ProgramSection::CgroupSkb { .. } => Program::CgroupSkb(CgroupSkb {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
expected_attach_type: None, expected_attach_type: None,
}), }),
ProgramSection::CgroupSkbIngress { .. } => Program::CgroupSkb(CgroupSkb { ProgramSection::CgroupSkbIngress { .. } => Program::CgroupSkb(CgroupSkb {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
expected_attach_type: Some(CgroupSkbAttachType::Ingress), expected_attach_type: Some(CgroupSkbAttachType::Ingress),
}), }),
ProgramSection::CgroupSkbEgress { .. } => Program::CgroupSkb(CgroupSkb { ProgramSection::CgroupSkbEgress { .. } => Program::CgroupSkb(CgroupSkb {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
expected_attach_type: Some(CgroupSkbAttachType::Egress), expected_attach_type: Some(CgroupSkbAttachType::Egress),
}), }),
ProgramSection::CgroupSockAddr { attach_type, .. } => { ProgramSection::CgroupSockAddr { attach_type, .. } => {
Program::CgroupSockAddr(CgroupSockAddr { Program::CgroupSockAddr(CgroupSockAddr {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
attach_type: *attach_type, attach_type: *attach_type,
}) })
} }
ProgramSection::LircMode2 { .. } => Program::LircMode2(LircMode2 { ProgramSection::LircMode2 { .. } => Program::LircMode2(LircMode2 {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
}), }),
ProgramSection::PerfEvent { .. } => Program::PerfEvent(PerfEvent { ProgramSection::PerfEvent { .. } => Program::PerfEvent(PerfEvent {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
}), }),
ProgramSection::RawTracePoint { .. } => { ProgramSection::RawTracePoint { .. } => {
Program::RawTracePoint(RawTracePoint { Program::RawTracePoint(RawTracePoint {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
}) })
} }
ProgramSection::Lsm { sleepable, .. } => { ProgramSection::Lsm { sleepable, .. } => {
let mut data = let mut data =
ProgramData::new(prog_name, obj, btf_fd, verifier_log_level); ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level);
if *sleepable { if *sleepable {
data.flags = BPF_F_SLEEPABLE; data.flags = BPF_F_SLEEPABLE;
} }
@ -577,30 +585,30 @@ impl<'a> BpfLoader<'a> {
} }
ProgramSection::BtfTracePoint { .. } => { ProgramSection::BtfTracePoint { .. } => {
Program::BtfTracePoint(BtfTracePoint { Program::BtfTracePoint(BtfTracePoint {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
}) })
} }
ProgramSection::FEntry { .. } => Program::FEntry(FEntry { ProgramSection::FEntry { .. } => Program::FEntry(FEntry {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
}), }),
ProgramSection::FExit { .. } => Program::FExit(FExit { ProgramSection::FExit { .. } => Program::FExit(FExit {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
}), }),
ProgramSection::Extension { .. } => Program::Extension(Extension { ProgramSection::Extension { .. } => Program::Extension(Extension {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
}), }),
ProgramSection::SkLookup { .. } => Program::SkLookup(SkLookup { ProgramSection::SkLookup { .. } => Program::SkLookup(SkLookup {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
}), }),
ProgramSection::CgroupSock { attach_type, .. } => { ProgramSection::CgroupSock { attach_type, .. } => {
Program::CgroupSock(CgroupSock { Program::CgroupSock(CgroupSock {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
attach_type: *attach_type, attach_type: *attach_type,
}) })
} }
ProgramSection::CgroupDevice { .. } => { ProgramSection::CgroupDevice { .. } => {
Program::CgroupDevice(CgroupDevice { Program::CgroupDevice(CgroupDevice {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
}) })
} }
} }
@ -906,23 +914,16 @@ pub enum BpfError {
ProgramError(#[from] ProgramError), ProgramError(#[from] ProgramError),
} }
fn load_btf(raw_btf: Vec<u8>) -> Result<RawFd, BtfError> { fn load_btf(raw_btf: Vec<u8>, verifier_log_level: VerifierLogLevel) -> Result<RawFd, BtfError> {
let mut logger = VerifierLog::new(); let (ret, verifier_log) = retry_with_verifier_logs(10, |logger| {
let ret = retry_with_verifier_logs(10, &mut logger, |logger| { bpf_load_btf(raw_btf.as_slice(), logger, verifier_log_level)
bpf_load_btf(raw_btf.as_slice(), logger)
}); });
match ret { match ret {
Ok(fd) => Ok(fd as RawFd), Ok(fd) => Ok(fd as RawFd),
Err((_, io_error)) => { Err((_, io_error)) => Err(BtfError::LoadError {
logger.truncate();
Err(BtfError::LoadError {
io_error, io_error,
verifier_log: logger verifier_log,
.as_c_str() }),
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| "[none]".to_owned()),
})
}
} }
} }

@ -49,6 +49,7 @@ use std::{
use libc::{getrlimit, rlimit, RLIMIT_MEMLOCK, RLIM_INFINITY}; use libc::{getrlimit, rlimit, RLIMIT_MEMLOCK, RLIM_INFINITY};
use log::warn; use log::warn;
use procfs::KernelVersion;
use thiserror::Error; use thiserror::Error;
use crate::{ use crate::{
@ -56,7 +57,7 @@ use crate::{
pin::PinError, pin::PinError,
sys::{ sys::{
bpf_create_map, bpf_get_object, bpf_map_get_info_by_fd, bpf_map_get_next_key, bpf_create_map, bpf_get_object, bpf_map_get_info_by_fd, bpf_map_get_next_key,
bpf_pin_object, kernel_version, bpf_pin_object,
}, },
util::nr_cpus, util::nr_cpus,
PinningType, Pod, PinningType, Pod,
@ -489,9 +490,13 @@ impl MapData {
let c_name = CString::new(name).map_err(|_| MapError::InvalidName { name: name.into() })?; let c_name = CString::new(name).map_err(|_| MapError::InvalidName { name: name.into() })?;
let fd = bpf_create_map(&c_name, &self.obj, self.btf_fd).map_err(|(code, io_error)| { #[cfg(not(test))]
let k_ver = kernel_version().unwrap(); let kernel_version = KernelVersion::current().unwrap();
if k_ver < (5, 11, 0) { #[cfg(test)]
let kernel_version = KernelVersion::new(0xff, 0xff, 0xff);
let fd = bpf_create_map(&c_name, &self.obj, self.btf_fd, kernel_version).map_err(
|(code, io_error)| {
if kernel_version < KernelVersion::new(5, 11, 0) {
maybe_warn_rlimit(); maybe_warn_rlimit();
} }
@ -500,7 +505,8 @@ impl MapData {
code, code,
io_error, io_error,
} }
})? as RawFd; },
)? as RawFd;
self.fd = Some(fd); self.fd = Some(fd);

@ -1,4 +1,6 @@
//! Cgroup device programs. //! Cgroup device programs.
use procfs::KernelVersion;
use std::os::fd::{AsRawFd, RawFd}; use std::os::fd::{AsRawFd, RawFd};
use crate::{ use crate::{
@ -6,7 +8,7 @@ use crate::{
programs::{ programs::{
define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
}, },
sys::{bpf_link_create, bpf_prog_attach, kernel_version}, sys::{bpf_link_create, bpf_prog_attach},
}; };
/// A program used to watch or prevent device interaction from a cgroup. /// A program used to watch or prevent device interaction from a cgroup.
@ -62,8 +64,7 @@ impl CgroupDevice {
let prog_fd = self.data.fd_or_err()?; let prog_fd = self.data.fd_or_err()?;
let cgroup_fd = cgroup.as_raw_fd(); let cgroup_fd = cgroup.as_raw_fd();
let k_ver = kernel_version().unwrap(); if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
if k_ver >= (5, 7, 0) {
let link_fd = bpf_link_create(prog_fd, cgroup_fd, BPF_CGROUP_DEVICE, None, 0).map_err( let link_fd = bpf_link_create(prog_fd, cgroup_fd, BPF_CGROUP_DEVICE, None, 0).map_err(
|(_, io_error)| ProgramError::SyscallError { |(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create", call: "bpf_link_create",

@ -1,4 +1,6 @@
//! Cgroup skb programs. //! Cgroup skb programs.
use procfs::KernelVersion;
use std::{ use std::{
hash::Hash, hash::Hash,
os::fd::{AsRawFd, RawFd}, os::fd::{AsRawFd, RawFd},
@ -13,7 +15,8 @@ use crate::{
programs::{ programs::{
define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
}, },
sys::{bpf_link_create, bpf_prog_attach, kernel_version}, sys::{bpf_link_create, bpf_prog_attach},
VerifierLogLevel,
}; };
/// A program used to inspect or filter network activity for a given cgroup. /// A program used to inspect or filter network activity for a given cgroup.
@ -96,8 +99,7 @@ impl CgroupSkb {
CgroupSkbAttachType::Ingress => BPF_CGROUP_INET_INGRESS, CgroupSkbAttachType::Ingress => BPF_CGROUP_INET_INGRESS,
CgroupSkbAttachType::Egress => BPF_CGROUP_INET_EGRESS, CgroupSkbAttachType::Egress => BPF_CGROUP_INET_EGRESS,
}; };
let k_ver = kernel_version().unwrap(); if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
if k_ver >= (5, 7, 0) {
let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err( let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
|(_, io_error)| ProgramError::SyscallError { |(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create", call: "bpf_link_create",
@ -150,7 +152,7 @@ impl CgroupSkb {
path: P, path: P,
expected_attach_type: CgroupSkbAttachType, expected_attach_type: CgroupSkbAttachType,
) -> Result<Self, ProgramError> { ) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path)?; let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
Ok(Self { Ok(Self {
data, data,
expected_attach_type: Some(expected_attach_type), expected_attach_type: Some(expected_attach_type),

@ -1,6 +1,8 @@
//! Cgroup socket programs. //! Cgroup socket programs.
pub use aya_obj::programs::CgroupSockAttachType; pub use aya_obj::programs::CgroupSockAttachType;
use procfs::KernelVersion;
use std::{ use std::{
hash::Hash, hash::Hash,
os::fd::{AsRawFd, RawFd}, os::fd::{AsRawFd, RawFd},
@ -12,7 +14,8 @@ use crate::{
programs::{ programs::{
define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
}, },
sys::{bpf_link_create, bpf_prog_attach, kernel_version}, sys::{bpf_link_create, bpf_prog_attach},
VerifierLogLevel,
}; };
/// A program that is called on socket creation, bind and release. /// A program that is called on socket creation, bind and release.
@ -71,8 +74,7 @@ impl CgroupSock {
let prog_fd = self.data.fd_or_err()?; let prog_fd = self.data.fd_or_err()?;
let cgroup_fd = cgroup.as_raw_fd(); let cgroup_fd = cgroup.as_raw_fd();
let attach_type = self.data.expected_attach_type.unwrap(); let attach_type = self.data.expected_attach_type.unwrap();
let k_ver = kernel_version().unwrap(); if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
if k_ver >= (5, 7, 0) {
let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err( let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
|(_, io_error)| ProgramError::SyscallError { |(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create", call: "bpf_link_create",
@ -125,7 +127,7 @@ impl CgroupSock {
path: P, path: P,
attach_type: CgroupSockAttachType, attach_type: CgroupSockAttachType,
) -> Result<Self, ProgramError> { ) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path)?; let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
Ok(Self { data, attach_type }) Ok(Self { data, attach_type })
} }
} }

@ -1,6 +1,8 @@
//! Cgroup socket address programs. //! Cgroup socket address programs.
pub use aya_obj::programs::CgroupSockAddrAttachType; pub use aya_obj::programs::CgroupSockAddrAttachType;
use procfs::KernelVersion;
use std::{ use std::{
hash::Hash, hash::Hash,
os::fd::{AsRawFd, RawFd}, os::fd::{AsRawFd, RawFd},
@ -12,7 +14,8 @@ use crate::{
programs::{ programs::{
define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
}, },
sys::{bpf_link_create, bpf_prog_attach, kernel_version}, sys::{bpf_link_create, bpf_prog_attach},
VerifierLogLevel,
}; };
/// A program that can be used to inspect or modify socket addresses (`struct sockaddr`). /// A program that can be used to inspect or modify socket addresses (`struct sockaddr`).
@ -72,8 +75,7 @@ impl CgroupSockAddr {
let prog_fd = self.data.fd_or_err()?; let prog_fd = self.data.fd_or_err()?;
let cgroup_fd = cgroup.as_raw_fd(); let cgroup_fd = cgroup.as_raw_fd();
let attach_type = self.data.expected_attach_type.unwrap(); let attach_type = self.data.expected_attach_type.unwrap();
let k_ver = kernel_version().unwrap(); if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
if k_ver >= (5, 7, 0) {
let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err( let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
|(_, io_error)| ProgramError::SyscallError { |(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create", call: "bpf_link_create",
@ -131,7 +133,7 @@ impl CgroupSockAddr {
path: P, path: P,
attach_type: CgroupSockAddrAttachType, attach_type: CgroupSockAddrAttachType,
) -> Result<Self, ProgramError> { ) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path)?; let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
Ok(Self { data, attach_type }) Ok(Self { data, attach_type })
} }
} }

@ -1,6 +1,8 @@
//! Cgroup socket option programs. //! Cgroup socket option programs.
pub use aya_obj::programs::CgroupSockoptAttachType; pub use aya_obj::programs::CgroupSockoptAttachType;
use procfs::KernelVersion;
use std::{ use std::{
hash::Hash, hash::Hash,
os::fd::{AsRawFd, RawFd}, os::fd::{AsRawFd, RawFd},
@ -12,7 +14,8 @@ use crate::{
programs::{ programs::{
define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
}, },
sys::{bpf_link_create, bpf_prog_attach, kernel_version}, sys::{bpf_link_create, bpf_prog_attach},
VerifierLogLevel,
}; };
/// A program that can be used to get or set options on sockets. /// A program that can be used to get or set options on sockets.
@ -69,8 +72,7 @@ impl CgroupSockopt {
let prog_fd = self.data.fd_or_err()?; let prog_fd = self.data.fd_or_err()?;
let cgroup_fd = cgroup.as_raw_fd(); let cgroup_fd = cgroup.as_raw_fd();
let attach_type = self.data.expected_attach_type.unwrap(); let attach_type = self.data.expected_attach_type.unwrap();
let k_ver = kernel_version().unwrap(); if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
if k_ver >= (5, 7, 0) {
let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err( let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
|(_, io_error)| ProgramError::SyscallError { |(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create", call: "bpf_link_create",
@ -126,7 +128,7 @@ impl CgroupSockopt {
path: P, path: P,
attach_type: CgroupSockoptAttachType, attach_type: CgroupSockoptAttachType,
) -> Result<Self, ProgramError> { ) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path)?; let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
Ok(Self { data, attach_type }) Ok(Self { data, attach_type })
} }
} }

@ -1,4 +1,6 @@
//! Cgroup sysctl programs. //! Cgroup sysctl programs.
use procfs::KernelVersion;
use std::{ use std::{
hash::Hash, hash::Hash,
os::fd::{AsRawFd, RawFd}, os::fd::{AsRawFd, RawFd},
@ -9,7 +11,7 @@ use crate::{
programs::{ programs::{
define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
}, },
sys::{bpf_link_create, bpf_prog_attach, kernel_version}, sys::{bpf_link_create, bpf_prog_attach},
}; };
/// A program used to watch for sysctl changes. /// A program used to watch for sysctl changes.
@ -64,8 +66,7 @@ impl CgroupSysctl {
let prog_fd = self.data.fd_or_err()?; let prog_fd = self.data.fd_or_err()?;
let cgroup_fd = cgroup.as_raw_fd(); let cgroup_fd = cgroup.as_raw_fd();
let k_ver = kernel_version().unwrap(); if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
if k_ver >= (5, 7, 0) {
let link_fd = bpf_link_create(prog_fd, cgroup_fd, BPF_CGROUP_SYSCTL, None, 0).map_err( let link_fd = bpf_link_create(prog_fd, cgroup_fd, BPF_CGROUP_SYSCTL, None, 0).map_err(
|(_, io_error)| ProgramError::SyscallError { |(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create", call: "bpf_link_create",

@ -10,6 +10,7 @@ use crate::{
probe::{attach, ProbeKind}, probe::{attach, ProbeKind},
ProgramData, ProgramError, ProgramData, ProgramError,
}, },
VerifierLogLevel,
}; };
/// A kernel probe. /// A kernel probe.
@ -91,7 +92,7 @@ impl KProbe {
/// On drop, any managed links are detached and the program is unloaded. This will not result in /// On drop, any managed links are detached and the program is unloaded. This will not result in
/// the program being unloaded from the kernel if it is still pinned. /// the program being unloaded from the kernel if it is still pinned.
pub fn from_pin<P: AsRef<Path>>(path: P, kind: ProbeKind) -> Result<Self, ProgramError> { pub fn from_pin<P: AsRef<Path>>(path: P, kind: ProbeKind) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path)?; let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
Ok(Self { data, kind }) Ok(Self { data, kind })
} }
} }

@ -65,7 +65,9 @@ mod utils;
pub mod xdp; pub mod xdp;
use libc::ENOSPC; use libc::ENOSPC;
use procfs::KernelVersion;
use std::{ use std::{
borrow::Cow,
ffi::CString, ffi::CString,
io, io,
os::unix::io::{AsRawFd, RawFd}, os::unix::io::{AsRawFd, RawFd},
@ -105,14 +107,14 @@ pub use xdp::{Xdp, XdpError, XdpFlags};
use crate::{ use crate::{
generated::{bpf_attach_type, bpf_prog_info, bpf_prog_type}, generated::{bpf_attach_type, bpf_prog_info, bpf_prog_type},
maps::MapError, maps::MapError,
obj::{self, btf::BtfError, Function, KernelVersion}, obj::{self, btf::BtfError, Function},
pin::PinError, pin::PinError,
sys::{ sys::{
bpf_btf_get_fd_by_id, bpf_get_object, bpf_load_program, bpf_pin_object, bpf_btf_get_fd_by_id, bpf_get_object, bpf_load_program, bpf_pin_object,
bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd, bpf_prog_get_next_id, bpf_prog_query, bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd, bpf_prog_get_next_id, bpf_prog_query,
retry_with_verifier_logs, BpfLoadProgramAttrs, retry_with_verifier_logs, BpfLoadProgramAttrs,
}, },
util::VerifierLog, VerifierLogLevel,
}; };
/// Error type returned when working with programs. /// Error type returned when working with programs.
@ -141,7 +143,7 @@ pub enum ProgramError {
#[source] #[source]
io_error: io::Error, io_error: io::Error,
/// The error log produced by the kernel verifier. /// The error log produced by the kernel verifier.
verifier_log: String, verifier_log: Cow<'static, str>,
}, },
/// A syscall failed. /// A syscall failed.
@ -413,7 +415,7 @@ pub(crate) struct ProgramData<T: Link> {
pub(crate) attach_btf_id: Option<u32>, pub(crate) attach_btf_id: Option<u32>,
pub(crate) attach_prog_fd: Option<RawFd>, pub(crate) attach_prog_fd: Option<RawFd>,
pub(crate) btf_fd: Option<RawFd>, pub(crate) btf_fd: Option<RawFd>,
pub(crate) verifier_log_level: u32, pub(crate) verifier_log_level: VerifierLogLevel,
pub(crate) path: Option<PathBuf>, pub(crate) path: Option<PathBuf>,
pub(crate) flags: u32, pub(crate) flags: u32,
} }
@ -423,7 +425,7 @@ impl<T: Link> ProgramData<T> {
name: Option<String>, name: Option<String>,
obj: (obj::Program, obj::Function), obj: (obj::Program, obj::Function),
btf_fd: Option<RawFd>, btf_fd: Option<RawFd>,
verifier_log_level: u32, verifier_log_level: VerifierLogLevel,
) -> ProgramData<T> { ) -> ProgramData<T> {
ProgramData { ProgramData {
name, name,
@ -446,6 +448,7 @@ impl<T: Link> ProgramData<T> {
fd: RawFd, fd: RawFd,
path: &Path, path: &Path,
info: bpf_prog_info, info: bpf_prog_info,
verifier_log_level: VerifierLogLevel,
) -> Result<ProgramData<T>, ProgramError> { ) -> Result<ProgramData<T>, ProgramError> {
let attach_btf_id = if info.attach_btf_id > 0 { let attach_btf_id = if info.attach_btf_id > 0 {
Some(info.attach_btf_id) Some(info.attach_btf_id)
@ -474,7 +477,7 @@ impl<T: Link> ProgramData<T> {
attach_btf_id, attach_btf_id,
attach_prog_fd: None, attach_prog_fd: None,
btf_fd: None, btf_fd: None,
verifier_log_level: 0, verifier_log_level,
path: Some(path.to_path_buf()), path: Some(path.to_path_buf()),
flags: 0, flags: 0,
}) })
@ -482,6 +485,7 @@ impl<T: Link> ProgramData<T> {
pub(crate) fn from_pinned_path<P: AsRef<Path>>( pub(crate) fn from_pinned_path<P: AsRef<Path>>(
path: P, path: P,
verifier_log_level: VerifierLogLevel,
) -> Result<ProgramData<T>, ProgramError> { ) -> Result<ProgramData<T>, ProgramError> {
let path_string = let path_string =
CString::new(path.as_ref().as_os_str().to_string_lossy().as_bytes()).unwrap(); CString::new(path.as_ref().as_os_str().to_string_lossy().as_bytes()).unwrap();
@ -496,9 +500,8 @@ impl<T: Link> ProgramData<T> {
io_error, io_error,
})?; })?;
let info = ProgramInfo(info); let name = ProgramInfo(info).name_as_str().map(|s| s.to_string());
let name = info.name_as_str().map(|s| s.to_string()); ProgramData::from_bpf_prog_info(name, fd, path.as_ref(), info, verifier_log_level)
ProgramData::from_bpf_prog_info(name, fd, path.as_ref(), info.0)
} }
} }
@ -548,7 +551,20 @@ fn load_program<T: Link>(
prog_type: bpf_prog_type, prog_type: bpf_prog_type,
data: &mut ProgramData<T>, data: &mut ProgramData<T>,
) -> Result<(), ProgramError> { ) -> Result<(), ProgramError> {
let ProgramData { obj, fd, .. } = data; let ProgramData {
name,
obj,
fd,
links: _,
expected_attach_type,
attach_btf_obj_fd,
attach_btf_id,
attach_prog_fd,
btf_fd,
verifier_log_level,
path: _,
flags,
} = data;
if fd.is_some() { if fd.is_some() {
return Err(ProgramError::AlreadyLoaded); return Err(ProgramError::AlreadyLoaded);
} }
@ -573,17 +589,16 @@ fn load_program<T: Link>(
}, },
) = obj; ) = obj;
let target_kernel_version = match *kernel_version { let target_kernel_version = kernel_version.unwrap_or_else(|| {
KernelVersion::Any => { let KernelVersion {
let (major, minor, patch) = crate::sys::kernel_version().unwrap(); major,
(major << 16) + (minor << 8) + patch minor,
} patch,
_ => (*kernel_version).into(), } = KernelVersion::current().unwrap();
}; (u32::from(major) << 16) + (u32::from(minor) << 8) + u32::from(patch)
});
let mut logger = VerifierLog::new();
let prog_name = if let Some(name) = &data.name { let prog_name = if let Some(name) = name {
let mut name = name.clone(); let mut name = name.clone();
if name.len() > 15 { if name.len() > 15 {
name.truncate(15); name.truncate(15);
@ -601,21 +616,20 @@ fn load_program<T: Link>(
insns: instructions, insns: instructions,
license, license,
kernel_version: target_kernel_version, kernel_version: target_kernel_version,
expected_attach_type: data.expected_attach_type, expected_attach_type: *expected_attach_type,
prog_btf_fd: data.btf_fd, prog_btf_fd: *btf_fd,
attach_btf_obj_fd: data.attach_btf_obj_fd, attach_btf_obj_fd: *attach_btf_obj_fd,
attach_btf_id: data.attach_btf_id, attach_btf_id: *attach_btf_id,
attach_prog_fd: data.attach_prog_fd, attach_prog_fd: *attach_prog_fd,
func_info_rec_size: *func_info_rec_size, func_info_rec_size: *func_info_rec_size,
func_info: func_info.clone(), func_info: func_info.clone(),
line_info_rec_size: *line_info_rec_size, line_info_rec_size: *line_info_rec_size,
line_info: line_info.clone(), line_info: line_info.clone(),
flags: data.flags, flags: *flags,
}; };
let verifier_log_level = data.verifier_log_level; let (ret, verifier_log) = retry_with_verifier_logs(10, |logger| {
let ret = retry_with_verifier_logs(10, &mut logger, |logger| { bpf_load_program(&attr, logger, *verifier_log_level)
bpf_load_program(&attr, logger, verifier_log_level)
}); });
match ret { match ret {
@ -623,16 +637,10 @@ fn load_program<T: Link>(
*fd = Some(prog_fd as RawFd); *fd = Some(prog_fd as RawFd);
Ok(()) Ok(())
} }
Err((_, io_error)) => { Err((_, io_error)) => Err(ProgramError::LoadError {
logger.truncate();
return Err(ProgramError::LoadError {
io_error, io_error,
verifier_log: logger verifier_log,
.as_c_str() }),
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| "[none]".to_owned()),
});
}
} }
} }
@ -829,7 +837,7 @@ macro_rules! impl_from_pin {
/// On drop, any managed links are detached and the program is unloaded. This will not result in /// On drop, any managed links are detached and the program is unloaded. This will not result in
/// the program being unloaded from the kernel if it is still pinned. /// the program being unloaded from the kernel if it is still pinned.
pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> { pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path)?; let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
Ok(Self { data }) Ok(Self { data })
} }
} }

@ -1,4 +1,5 @@
use libc::pid_t; use libc::pid_t;
use procfs::KernelVersion;
use std::{ use std::{
fs::{self, OpenOptions}, fs::{self, OpenOptions},
io::{self, Write}, io::{self, Write},
@ -13,7 +14,7 @@ use crate::{
trace_point::read_sys_fs_trace_point_id, uprobe::UProbeError, utils::find_tracefs_path, trace_point::read_sys_fs_trace_point_id, uprobe::UProbeError, utils::find_tracefs_path,
Link, ProgramData, ProgramError, Link, ProgramData, ProgramError,
}, },
sys::{kernel_version, perf_event_open_probe, perf_event_open_trace_point}, sys::{perf_event_open_probe, perf_event_open_trace_point},
}; };
static PROBE_NAME_INDEX: AtomicUsize = AtomicUsize::new(0); static PROBE_NAME_INDEX: AtomicUsize = AtomicUsize::new(0);
@ -49,8 +50,7 @@ pub(crate) fn attach<T: Link + From<PerfLinkInner>>(
) -> Result<T::Id, ProgramError> { ) -> Result<T::Id, ProgramError> {
// https://github.com/torvalds/linux/commit/e12f03d7031a977356e3d7b75a68c2185ff8d155 // https://github.com/torvalds/linux/commit/e12f03d7031a977356e3d7b75a68c2185ff8d155
// Use debugfs to create probe // Use debugfs to create probe
let k_ver = kernel_version().unwrap(); if KernelVersion::current().unwrap() >= KernelVersion::new(4, 17, 0) {
if k_ver < (4, 17, 0) {
let (fd, event_alias) = create_as_trace_point(kind, fn_name, offset, pid)?; let (fd, event_alias) = create_as_trace_point(kind, fn_name, offset, pid)?;
let link = T::from(perf_attach_debugfs( let link = T::from(perf_attach_debugfs(
program_data.fd_or_err()?, program_data.fd_or_err()?,

@ -13,6 +13,7 @@ use crate::{
ProgramError, ProgramError,
}, },
sys::bpf_prog_attach, sys::bpf_prog_attach,
VerifierLogLevel,
}; };
/// The kind of [`SkSkb`] program. /// The kind of [`SkSkb`] program.
@ -113,7 +114,7 @@ impl SkSkb {
/// On drop, any managed links are detached and the program is unloaded. This will not result in /// On drop, any managed links are detached and the program is unloaded. This will not result in
/// the program being unloaded from the kernel if it is still pinned. /// the program being unloaded from the kernel if it is still pinned.
pub fn from_pin<P: AsRef<Path>>(path: P, kind: SkSkbKind) -> Result<Self, ProgramError> { pub fn from_pin<P: AsRef<Path>>(path: P, kind: SkSkbKind) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path)?; let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
Ok(Self { data, kind }) Ok(Self { data, kind })
} }
} }

@ -17,6 +17,7 @@ use crate::{
netlink_qdisc_detach, netlink_qdisc_detach,
}, },
util::{ifindex_from_ifname, tc_handler_make}, util::{ifindex_from_ifname, tc_handler_make},
VerifierLogLevel,
}; };
/// Traffic control attach type. /// Traffic control attach type.
@ -199,7 +200,7 @@ impl SchedClassifier {
/// On drop, any managed links are detached and the program is unloaded. This will not result in /// On drop, any managed links are detached and the program is unloaded. This will not result in
/// the program being unloaded from the kernel if it is still pinned. /// the program being unloaded from the kernel if it is still pinned.
pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> { pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path)?; let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
let cname = CString::new(data.name.clone().unwrap_or_default()) let cname = CString::new(data.name.clone().unwrap_or_default())
.unwrap() .unwrap()
.into_boxed_c_str(); .into_boxed_c_str();

@ -21,6 +21,7 @@ use crate::{
probe::{attach, ProbeKind}, probe::{attach, ProbeKind},
ProgramData, ProgramError, ProgramData, ProgramError,
}, },
VerifierLogLevel,
}; };
const LD_SO_CACHE_FILE: &str = "/etc/ld.so.cache"; const LD_SO_CACHE_FILE: &str = "/etc/ld.so.cache";
@ -145,7 +146,7 @@ impl UProbe {
/// On drop, any managed links are detached and the program is unloaded. This will not result in /// On drop, any managed links are detached and the program is unloaded. This will not result in
/// the program being unloaded from the kernel if it is still pinned. /// the program being unloaded from the kernel if it is still pinned.
pub fn from_pin<P: AsRef<Path>>(path: P, kind: ProbeKind) -> Result<Self, ProgramError> { pub fn from_pin<P: AsRef<Path>>(path: P, kind: ProbeKind) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path)?; let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
Ok(Self { data, kind }) Ok(Self { data, kind })
} }
} }

@ -1,6 +1,8 @@
//! eXpress Data Path (XDP) programs. //! eXpress Data Path (XDP) programs.
use bitflags; use bitflags;
use libc::if_nametoindex; use libc::if_nametoindex;
use procfs::KernelVersion;
use std::{convert::TryFrom, ffi::CString, hash::Hash, io, mem, os::unix::io::RawFd}; use std::{convert::TryFrom, ffi::CString, hash::Hash, io, mem, os::unix::io::RawFd};
use thiserror::Error; use thiserror::Error;
@ -15,10 +17,7 @@ use crate::{
programs::{ programs::{
define_link_wrapper, load_program, FdLink, Link, LinkError, ProgramData, ProgramError, define_link_wrapper, load_program, FdLink, Link, LinkError, ProgramData, ProgramError,
}, },
sys::{ sys::{bpf_link_create, bpf_link_get_info_by_fd, bpf_link_update, netlink_set_xdp_fd},
bpf_link_create, bpf_link_get_info_by_fd, bpf_link_update, kernel_version,
netlink_set_xdp_fd,
},
}; };
/// The type returned when attaching an [`Xdp`] program fails on kernels `< 5.9`. /// The type returned when attaching an [`Xdp`] program fails on kernels `< 5.9`.
@ -126,8 +125,7 @@ impl Xdp {
let prog_fd = self.data.fd_or_err()?; let prog_fd = self.data.fd_or_err()?;
let if_index = if_index as RawFd; let if_index = if_index as RawFd;
let k_ver = kernel_version().unwrap(); if KernelVersion::current().unwrap() >= KernelVersion::new(5, 9, 0) {
if k_ver >= (5, 9, 0) {
let link_fd = bpf_link_create(prog_fd, if_index, BPF_XDP, None, flags.bits()).map_err( let link_fd = bpf_link_create(prog_fd, if_index, BPF_XDP, None, flags.bits()).map_err(
|(_, io_error)| ProgramError::SyscallError { |(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create", call: "bpf_link_create",
@ -224,8 +222,7 @@ impl Link for NlLink {
} }
fn detach(self) -> Result<(), ProgramError> { fn detach(self) -> Result<(), ProgramError> {
let k_ver = kernel_version().unwrap(); let flags = if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
let flags = if k_ver >= (5, 7, 0) {
self.flags.bits() | XDP_FLAGS_REPLACE self.flags.bits() | XDP_FLAGS_REPLACE
} else { } else {
self.flags.bits() self.flags.bits()

@ -1,4 +1,5 @@
use std::{ use std::{
borrow::Cow,
cmp::{self, min}, cmp::{self, min},
ffi::{CStr, CString}, ffi::{CStr, CString},
io, io,
@ -12,6 +13,7 @@ use obj::{
maps::{bpf_map_def, LegacyMap}, maps::{bpf_map_def, LegacyMap},
BpfSectionKind, BpfSectionKind,
}; };
use procfs::KernelVersion;
use crate::{ use crate::{
generated::{ generated::{
@ -27,12 +29,16 @@ use crate::{
}, },
copy_instructions, copy_instructions,
}, },
sys::{kernel_version, syscall, SysResult, Syscall}, sys::{syscall, SysResult, Syscall},
util::VerifierLog, Btf, Pod, VerifierLogLevel, BPF_OBJ_NAME_LEN,
Btf, Pod, BPF_OBJ_NAME_LEN,
}; };
pub(crate) fn bpf_create_map(name: &CStr, def: &obj::Map, btf_fd: Option<RawFd>) -> SysResult { pub(crate) fn bpf_create_map(
name: &CStr,
def: &obj::Map,
btf_fd: Option<RawFd>,
kernel_version: KernelVersion,
) -> SysResult {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() }; let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_1 }; let u = unsafe { &mut attr.__bindgen_anon_1 };
@ -78,8 +84,7 @@ pub(crate) fn bpf_create_map(name: &CStr, def: &obj::Map, btf_fd: Option<RawFd>)
// https://github.com/torvalds/linux/commit/ad5b177bd73f5107d97c36f56395c4281fb6f089 // https://github.com/torvalds/linux/commit/ad5b177bd73f5107d97c36f56395c4281fb6f089
// The map name was added as a parameter in kernel 4.15+ so we skip adding it on // The map name was added as a parameter in kernel 4.15+ so we skip adding it on
// older kernels for compatibility // older kernels for compatibility
let k_ver = kernel_version().unwrap(); if kernel_version >= KernelVersion::new(4, 15, 0) {
if k_ver >= (4, 15, 0) {
// u.map_name is 16 bytes max and must be NULL terminated // u.map_name is 16 bytes max and must be NULL terminated
let name_len = cmp::min(name.to_bytes().len(), BPF_OBJ_NAME_LEN - 1); let name_len = cmp::min(name.to_bytes().len(), BPF_OBJ_NAME_LEN - 1);
u.map_name[..name_len] u.map_name[..name_len]
@ -124,8 +129,8 @@ pub(crate) struct BpfLoadProgramAttrs<'a> {
pub(crate) fn bpf_load_program( pub(crate) fn bpf_load_program(
aya_attr: &BpfLoadProgramAttrs, aya_attr: &BpfLoadProgramAttrs,
logger: &mut VerifierLog, log_buf: &mut [u8],
verifier_log_level: u32, verifier_log_level: VerifierLogLevel,
) -> SysResult { ) -> SysResult {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() }; let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
@ -169,11 +174,10 @@ pub(crate) fn bpf_load_program(
u.func_info_rec_size = aya_attr.func_info_rec_size as u32; u.func_info_rec_size = aya_attr.func_info_rec_size as u32;
} }
} }
let log_buf = logger.buf(); if !log_buf.is_empty() {
if log_buf.capacity() > 0 { u.log_level = verifier_log_level.bits();
u.log_level = verifier_log_level;
u.log_buf = log_buf.as_mut_ptr() as u64; u.log_buf = log_buf.as_mut_ptr() as u64;
u.log_size = log_buf.capacity() as u32; u.log_size = log_buf.len() as u32;
} }
if let Some(v) = aya_attr.attach_btf_obj_fd { if let Some(v) = aya_attr.attach_btf_obj_fd {
u.__bindgen_anon_1.attach_btf_obj_fd = v; u.__bindgen_anon_1.attach_btf_obj_fd = v;
@ -544,16 +548,19 @@ pub(crate) fn bpf_raw_tracepoint_open(name: Option<&CStr>, prog_fd: RawFd) -> Sy
sys_bpf(bpf_cmd::BPF_RAW_TRACEPOINT_OPEN, &attr) sys_bpf(bpf_cmd::BPF_RAW_TRACEPOINT_OPEN, &attr)
} }
pub(crate) fn bpf_load_btf(raw_btf: &[u8], log: &mut VerifierLog) -> SysResult { pub(crate) fn bpf_load_btf(
raw_btf: &[u8],
log_buf: &mut [u8],
verifier_log_level: VerifierLogLevel,
) -> SysResult {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() }; let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_7 }; let u = unsafe { &mut attr.__bindgen_anon_7 };
u.btf = raw_btf.as_ptr() as *const _ as u64; u.btf = raw_btf.as_ptr() as *const _ as u64;
u.btf_size = mem::size_of_val(raw_btf) as u32; u.btf_size = mem::size_of_val(raw_btf) as u32;
let log_buf = log.buf(); if !log_buf.is_empty() {
if log_buf.capacity() > 0 { u.btf_log_level = verifier_log_level.bits();
u.btf_log_level = 1;
u.btf_log_buf = log_buf.as_mut_ptr() as u64; u.btf_log_buf = log_buf.as_mut_ptr() as u64;
u.btf_log_size = log_buf.capacity() as u32; u.btf_log_size = log_buf.len() as u32;
} }
sys_bpf(bpf_cmd::BPF_BTF_LOAD, &attr) sys_bpf(bpf_cmd::BPF_BTF_LOAD, &attr)
} }
@ -988,35 +995,41 @@ pub(crate) fn bpf_prog_get_next_id(id: u32) -> Result<Option<u32>, (c_long, io::
pub(crate) fn retry_with_verifier_logs<F>( pub(crate) fn retry_with_verifier_logs<F>(
max_retries: usize, max_retries: usize,
log: &mut VerifierLog,
f: F, f: F,
) -> SysResult ) -> (SysResult, Cow<'static, str>)
where where
F: Fn(&mut VerifierLog) -> SysResult, F: Fn(&mut [u8]) -> SysResult,
{ {
// 1. Try the syscall const MIN_LOG_BUF_SIZE: usize = 1024 * 10;
let ret = f(log); const MAX_LOG_BUF_SIZE: usize = (std::u32::MAX >> 8) as usize;
if ret.is_ok() {
return ret;
}
// 2. Grow the log buffer so we can capture verifier output let mut log_buf = Vec::new();
// Retry this up to max_retries times
log.grow();
let mut retries = 0; let mut retries = 0;
loop { loop {
let ret = f(log); let ret = f(log_buf.as_mut_slice());
match ret { if retries != max_retries {
Err((v, io_error)) if retries == 0 || io_error.raw_os_error() == Some(ENOSPC) => { if let Err((_, io_error)) = &ret {
if retries == max_retries { if retries == 0 || io_error.raw_os_error() == Some(ENOSPC) {
return Err((v, io_error)); let len = (log_buf.capacity() * 10).clamp(MIN_LOG_BUF_SIZE, MAX_LOG_BUF_SIZE);
log_buf.resize(len, 0);
if let Some(first) = log_buf.first_mut() {
*first = 0;
} }
retries += 1; retries += 1;
log.grow(); continue;
}
} }
r => return r,
} }
if let Some(pos) = log_buf.iter().position(|b| *b == 0) {
log_buf.truncate(pos);
}
let log_buf = if log_buf.is_empty() {
"none".into()
} else {
String::from_utf8(log_buf).unwrap().into()
};
break (ret, log_buf);
} }
} }

@ -5,15 +5,9 @@ mod perf_event;
#[cfg(test)] #[cfg(test)]
mod fake; mod fake;
use std::io; use std::{io, mem};
#[cfg(not(test))]
use std::{ffi::CString, mem};
#[cfg(not(test))]
use std::{fs::File, io::Read};
#[cfg(not(test))] use libc::{c_int, c_long, pid_t, SYS_bpf, SYS_perf_event_open};
use libc::utsname;
use libc::{c_int, c_long, pid_t};
pub(crate) use bpf::*; pub(crate) use bpf::*;
#[cfg(test)] #[cfg(test)]
@ -25,7 +19,6 @@ use crate::generated::{bpf_attr, bpf_cmd, perf_event_attr};
pub(crate) type SysResult = Result<c_long, (c_long, io::Error)>; pub(crate) type SysResult = Result<c_long, (c_long, io::Error)>;
#[cfg_attr(test, allow(dead_code))]
pub(crate) enum Syscall<'a> { pub(crate) enum Syscall<'a> {
Bpf { Bpf {
cmd: bpf_cmd, cmd: bpf_cmd,
@ -46,126 +39,28 @@ pub(crate) enum Syscall<'a> {
} }
fn syscall(call: Syscall) -> SysResult { fn syscall(call: Syscall) -> SysResult {
#[cfg(not(test))]
return unsafe { syscall_impl(call) };
#[cfg(test)] #[cfg(test)]
return TEST_SYSCALL.with(|test_impl| unsafe { test_impl.borrow()(call) }); return TEST_SYSCALL.with(|test_impl| unsafe { test_impl.borrow()(call) });
}
#[cfg(not(test))]
unsafe fn syscall_impl(call: Syscall) -> SysResult {
use libc::{SYS_bpf, SYS_perf_event_open};
use Syscall::*; #[cfg_attr(test, allow(unreachable_code))]
let ret = match call { match unsafe {
Bpf { cmd, attr } => libc::syscall(SYS_bpf, cmd, attr, mem::size_of::<bpf_attr>()), match call {
PerfEventOpen { Syscall::Bpf { cmd, attr } => {
libc::syscall(SYS_bpf, cmd, attr, mem::size_of::<bpf_attr>())
}
Syscall::PerfEventOpen {
attr, attr,
pid, pid,
cpu, cpu,
group, group,
flags, flags,
} => libc::syscall(SYS_perf_event_open, &attr, pid, cpu, group, flags), } => libc::syscall(SYS_perf_event_open, &attr, pid, cpu, group, flags),
PerfEventIoctl { fd, request, arg } => { Syscall::PerfEventIoctl { fd, request, arg } => {
libc::ioctl(fd, request.try_into().unwrap(), arg) as libc::c_long libc::ioctl(fd, request.try_into().unwrap(), arg) as libc::c_long
} }
};
if ret < 0 {
return Err((ret, io::Error::last_os_error()));
}
Ok(ret)
}
#[cfg(test)]
pub(crate) fn kernel_version() -> Result<(u32, u32, u32), ()> {
Ok((0xff, 0xff, 0xff))
}
#[cfg(not(test))]
fn ubuntu_kernel_version() -> Result<(u32, u32, u32), ()> {
if let Ok(mut file) = File::open("/proc/version_signature") {
let mut buf = String::new();
let mut major = 0u32;
let mut minor = 0u32;
let mut patch = 0u32;
let format = CString::new("%*s %*s %u.%u.%u\n").unwrap();
file.read_to_string(&mut buf).map_err(|_| ())?;
unsafe {
if libc::sscanf(
buf.as_ptr() as *const _,
format.as_ptr(),
&mut major as *mut u32,
&mut minor as *mut _,
&mut patch as *mut _,
) == 3
{
return Ok((major, minor, patch));
}
}
}
Err(())
}
#[cfg(not(test))]
pub(crate) fn kernel_version() -> Result<(u32, u32, u32), ()> {
if let Ok(version) = ubuntu_kernel_version() {
return Ok(version);
}
unsafe {
let mut v = mem::zeroed::<utsname>();
if libc::uname(&mut v as *mut _) != 0 {
return Err(());
}
let mut major = 0u32;
let mut minor = 0u32;
let mut patch = 0u32;
let debian_marker = CString::new("Debian").unwrap();
let p = libc::strstr(v.version.as_ptr(), debian_marker.as_ptr());
if !p.is_null() {
let debian_format = CString::new("Debian %u.%u.%u").map_err(|_| ())?;
if libc::sscanf(
p,
debian_format.as_ptr(),
&mut major as *mut u32,
&mut minor as *mut _,
&mut patch as *mut _,
) == 3
{
// On Debian 10, kernels after 4.19.229 expect 4.19.255 due to broken Makefile patches.
let patch_level_limit = if major == 4 && minor == 19 { 230 } else { 255 };
if patch >= patch_level_limit {
patch = 255;
}
return Ok((major, minor, patch));
} }
} } {
ret @ 0.. => Ok(ret),
let format = CString::new("%u.%u.%u").unwrap(); ret => Err((ret, io::Error::last_os_error())),
if libc::sscanf(
v.release.as_ptr(),
format.as_ptr(),
&mut major as *mut u32,
&mut minor as *mut _,
&mut patch as *mut _,
) != 3
{
return Err(());
}
Ok((major, minor, patch))
} }
} }

@ -1,7 +1,7 @@
//! Utility functions. //! Utility functions.
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
ffi::{CStr, CString}, ffi::CString,
fs::{self, File}, fs::{self, File},
io::{self, BufReader}, io::{self, BufReader},
mem, slice, mem, slice,
@ -200,57 +200,6 @@ pub(crate) fn bytes_of_slice<T: Pod>(val: &[T]) -> &[u8] {
unsafe { slice::from_raw_parts(val.as_ptr().cast(), size) } unsafe { slice::from_raw_parts(val.as_ptr().cast(), size) }
} }
const MIN_LOG_BUF_SIZE: usize = 1024 * 10;
const MAX_LOG_BUF_SIZE: usize = (std::u32::MAX >> 8) as usize;
pub(crate) struct VerifierLog {
buf: Vec<u8>,
}
impl VerifierLog {
pub(crate) fn new() -> VerifierLog {
VerifierLog { buf: Vec::new() }
}
pub(crate) fn buf(&mut self) -> &mut Vec<u8> {
&mut self.buf
}
pub(crate) fn grow(&mut self) {
let len = (self.buf.capacity() * 10).clamp(MIN_LOG_BUF_SIZE, MAX_LOG_BUF_SIZE);
self.buf.resize(len, 0);
self.reset();
}
pub(crate) fn reset(&mut self) {
if !self.buf.is_empty() {
self.buf[0] = 0;
}
}
pub(crate) fn truncate(&mut self) {
if self.buf.is_empty() {
return;
}
let pos = self
.buf
.iter()
.position(|b| *b == 0)
.unwrap_or(self.buf.len() - 1);
self.buf[pos] = 0;
self.buf.truncate(pos + 1);
}
pub(crate) fn as_c_str(&self) -> Option<&CStr> {
if self.buf.is_empty() {
None
} else {
Some(CStr::from_bytes_with_nul(&self.buf).unwrap())
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

@ -3,6 +3,8 @@
use aya_bpf::{bindings::xdp_action, macros::xdp, programs::XdpContext}; use aya_bpf::{bindings::xdp_action, macros::xdp, programs::XdpContext};
// Note: the `frags` attribute causes this probe to be incompatible with kernel versions < 5.18.0.
// See https://github.com/torvalds/linux/commit/c2f2cdb.
#[xdp(name = "pass", frags = "true")] #[xdp(name = "pass", frags = "true")]
pub fn pass(ctx: XdpContext) -> u32 { pub fn pass(ctx: XdpContext) -> u32 {
match unsafe { try_pass(ctx) } { match unsafe { try_pass(ctx) } {

@ -12,7 +12,7 @@ aya-obj = { path = "../../aya-obj" }
libc = { version = "0.2.105" } libc = { version = "0.2.105" }
log = "0.4" log = "0.4"
object = { version = "0.31", default-features = false, features = ["std", "read_core", "elf"] } object = { version = "0.31", default-features = false, features = ["std", "read_core", "elf"] }
procfs = "0.15.1"
rbpf = "0.2.0" rbpf = "0.2.0"
regex = "1"
tempfile = "3.3.0" tempfile = "3.3.0"
tokio = { version = "1.24", features = ["rt", "rt-multi-thread", "sync", "time"] } tokio = { version = "1.24", features = ["rt", "rt-multi-thread", "sync", "time"] }

@ -1,4 +1,5 @@
use anyhow::{Context, Result}; use anyhow::{bail, Context as _, Result};
use procfs::KernelVersion;
use std::{path::PathBuf, process::Command, thread::sleep, time::Duration}; use std::{path::PathBuf, process::Command, thread::sleep, time::Duration};
use tempfile::TempDir; use tempfile::TempDir;
@ -60,6 +61,11 @@ fn relocate_enum() {
#[test] #[test]
fn relocate_enum_signed() { fn relocate_enum_signed() {
let kernel_version = KernelVersion::current().unwrap();
if kernel_version < KernelVersion::new(6, 0, 0) {
eprintln!("skipping test on kernel {kernel_version:?}, support for signed enum was added in 6.0.0; see https://github.com/torvalds/linux/commit/6089fb3");
return;
}
let test = RelocationTest { let test = RelocationTest {
local_definition: r#" local_definition: r#"
enum foo { D = -0x7AAAAAAA }; enum foo { D = -0x7AAAAAAA };
@ -80,6 +86,11 @@ fn relocate_enum_signed() {
#[test] #[test]
fn relocate_enum64() { fn relocate_enum64() {
let kernel_version = KernelVersion::current().unwrap();
if kernel_version < KernelVersion::new(6, 0, 0) {
eprintln!("skipping test on kernel {kernel_version:?}, support for enum64 was added in 6.0.0; see https://github.com/torvalds/linux/commit/6089fb3");
return;
}
let test = RelocationTest { let test = RelocationTest {
local_definition: r#" local_definition: r#"
enum foo { D = 0xAAAAAAAABBBBBBBB }; enum foo { D = 0xAAAAAAAABBBBBBBB };
@ -100,6 +111,11 @@ fn relocate_enum64() {
#[test] #[test]
fn relocate_enum64_signed() { fn relocate_enum64_signed() {
let kernel_version = KernelVersion::current().unwrap();
if kernel_version < KernelVersion::new(6, 0, 0) {
eprintln!("skipping test on kernel {kernel_version:?}, support for enum64 was added in 6.0.0; see https://github.com/torvalds/linux/commit/6089fb3");
return;
}
let test = RelocationTest { let test = RelocationTest {
local_definition: r#" local_definition: r#"
enum foo { D = -0xAAAAAAABBBBBBBB }; enum foo { D = -0xAAAAAAABBBBBBBB };
@ -266,15 +282,20 @@ impl RelocationTest {
"# "#
)) ))
.context("Failed to compile BTF")?; .context("Failed to compile BTF")?;
Command::new("llvm-objcopy") let mut cmd = Command::new("llvm-objcopy");
.current_dir(tmp_dir.path()) cmd.current_dir(tmp_dir.path())
.args(["--dump-section", ".BTF=target.btf"]) .args(["--dump-section", ".BTF=target.btf"])
.arg(compiled_file) .arg(compiled_file);
let status = cmd
.status() .status()
.context("Failed to run llvm-objcopy")? .with_context(|| format!("Failed to run {cmd:?}"))?;
.success() match status.code() {
.then_some(()) Some(code) => match code {
.context("Failed to extract BTF")?; 0 => {}
code => bail!("{cmd:?} exited with code {code}"),
},
None => bail!("{cmd:?} terminated by signal"),
}
let btf = Btf::parse_file(tmp_dir.path().join("target.btf"), Endianness::default()) let btf = Btf::parse_file(tmp_dir.path().join("target.btf"), Endianness::default())
.context("Error parsing generated BTF")?; .context("Error parsing generated BTF")?;
Ok(btf) Ok(btf)
@ -287,15 +308,20 @@ fn compile(source_code: &str) -> Result<(TempDir, PathBuf)> {
let tmp_dir = tempfile::tempdir().context("Error making temp dir")?; let tmp_dir = tempfile::tempdir().context("Error making temp dir")?;
let source = tmp_dir.path().join("source.c"); let source = tmp_dir.path().join("source.c");
std::fs::write(&source, source_code).context("Writing bpf program failed")?; std::fs::write(&source, source_code).context("Writing bpf program failed")?;
Command::new("clang") let mut cmd = Command::new("clang");
.current_dir(&tmp_dir) cmd.current_dir(&tmp_dir)
.args(["-c", "-g", "-O2", "-target", "bpf"]) .args(["-c", "-g", "-O2", "-target", "bpf"])
.arg(&source) .arg(&source);
let status = cmd
.status() .status()
.context("Failed to run clang")? .with_context(|| format!("Failed to run {cmd:?}"))?;
.success() match status.code() {
.then_some(()) Some(code) => match code {
.context("Failed to compile eBPF source")?; 0 => {}
code => bail!("{cmd:?} exited with code {code}"),
},
None => bail!("{cmd:?} terminated by signal"),
}
Ok((tmp_dir, source.with_extension("o"))) Ok((tmp_dir, source.with_extension("o")))
} }

@ -1,23 +0,0 @@
use anyhow::bail;
use libc::{uname, utsname};
use regex::Regex;
use std::{cell::OnceCell, ffi::CStr, mem};
pub fn kernel_version() -> anyhow::Result<(u8, u8, u8)> {
static mut RE: OnceCell<Regex> = OnceCell::new();
let re =
unsafe { &mut RE }.get_or_init(|| Regex::new(r"^([0-9]+)\.([0-9]+)\.([0-9]+)").unwrap());
let mut data: utsname = unsafe { mem::zeroed() };
let ret = unsafe { uname(&mut data) };
assert!(ret >= 0, "libc::uname failed.");
let release_cstr = unsafe { CStr::from_ptr(data.release.as_ptr()) };
let release = release_cstr.to_string_lossy();
if let Some(caps) = re.captures(&release) {
let major = caps.get(1).unwrap().as_str().parse().unwrap();
let minor = caps.get(2).unwrap().as_str().parse().unwrap();
let patch = caps.get(3).unwrap().as_str().parse().unwrap();
Ok((major, minor, patch))
} else {
bail!("no kernel version found");
}
}

@ -1,3 +1,4 @@
use procfs::KernelVersion;
use std::{convert::TryInto as _, thread, time}; use std::{convert::TryInto as _, thread, time};
use aya::{ use aya::{
@ -10,9 +11,6 @@ use aya::{
Bpf, Bpf,
}; };
mod common;
use common::kernel_version;
const MAX_RETRIES: u32 = 100; const MAX_RETRIES: u32 = 100;
const RETRY_DURATION_MS: u64 = 10; const RETRY_DURATION_MS: u64 = 10;
@ -133,8 +131,9 @@ fn unload_kprobe() {
#[test] #[test]
fn pin_link() { fn pin_link() {
if kernel_version().unwrap() < (5, 9, 0) { let kernel_version = KernelVersion::current().unwrap();
eprintln!("skipping test, XDP uses netlink"); if kernel_version < KernelVersion::new(5, 9, 0) {
eprintln!("skipping test on kernel {kernel_version:?}, XDP uses netlink");
return; return;
} }
@ -168,8 +167,9 @@ fn pin_link() {
#[test] #[test]
fn pin_lifecycle() { fn pin_lifecycle() {
if kernel_version().unwrap() < (5, 9, 0) { let kernel_version = KernelVersion::current().unwrap();
eprintln!("skipping test, XDP uses netlink"); if kernel_version < KernelVersion::new(5, 18, 0) {
eprintln!("skipping test on kernel {kernel_version:?}, support for BPF_F_XDP_HAS_FRAGS was added in 5.18.0; see https://github.com/torvalds/linux/commit/c2f2cdb");
return; return;
} }

@ -1,14 +1,19 @@
use procfs::KernelVersion;
use aya::{ use aya::{
include_bytes_aligned, include_bytes_aligned,
programs::{Extension, Xdp, XdpFlags}, programs::{Extension, Xdp, XdpFlags},
Bpf, BpfLoader, Bpf, BpfLoader,
}; };
mod common;
use common::kernel_version;
#[test] #[test]
fn xdp() { fn xdp() {
let kernel_version = KernelVersion::current().unwrap();
if kernel_version < KernelVersion::new(5, 18, 0) {
eprintln!("skipping test on kernel {kernel_version:?}, support for BPF_F_XDP_HAS_FRAGS was added in 5.18.0; see https://github.com/torvalds/linux/commit/c2f2cdb");
return;
}
let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/pass"); let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/pass");
let mut bpf = Bpf::load(bytes).unwrap(); let mut bpf = Bpf::load(bytes).unwrap();
let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap();
@ -18,15 +23,11 @@ fn xdp() {
#[test] #[test]
fn extension() { fn extension() {
let (major, minor, _) = kernel_version().unwrap(); let kernel_version = KernelVersion::current().unwrap();
if major < 5 || (minor == 5 && minor < 9) { if kernel_version < KernelVersion::new(5, 9, 0) {
eprintln!( eprintln!("skipping test on kernel {kernel_version:?}, XDP uses netlink");
"skipping as {}.{} does not meet version requirement of 5.9",
major, minor
);
return; return;
} }
// TODO: Check kernel version == 5.9 or later
let main_bytes = let main_bytes =
include_bytes_aligned!("../../../target/bpfel-unknown-none/release/main.bpf.o"); include_bytes_aligned!("../../../target/bpfel-unknown-none/release/main.bpf.o");
let mut bpf = Bpf::load(main_bytes).unwrap(); let mut bpf = Bpf::load(main_bytes).unwrap();

@ -12,5 +12,4 @@ clap = { version = "4", features = ["derive"] }
indoc = "2.0" indoc = "2.0"
proc-macro2 = "1" proc-macro2 = "1"
quote = "1" quote = "1"
serde_json = "1"
syn = "2" syn = "2"

@ -4,13 +4,13 @@ use std::{
ffi::{OsStr, OsString}, ffi::{OsStr, OsString},
fs, fs,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::{Command, Output}, process::Command,
}; };
use anyhow::{bail, Context}; use anyhow::Result;
use clap::Parser; use clap::Parser;
use crate::utils::workspace_root; use crate::utils::{exec, workspace_root};
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum Architecture { pub enum Architecture {
@ -49,61 +49,56 @@ pub struct BuildEbpfOptions {
pub libbpf_dir: PathBuf, pub libbpf_dir: PathBuf,
} }
pub fn build_ebpf(opts: BuildEbpfOptions) -> anyhow::Result<()> { pub fn build_ebpf(opts: BuildEbpfOptions) -> Result<()> {
build_rust_ebpf(&opts)?; build_rust_ebpf(&opts)?;
build_c_ebpf(&opts) build_c_ebpf(&opts)
} }
fn build_rust_ebpf(opts: &BuildEbpfOptions) -> anyhow::Result<()> { fn build_rust_ebpf(opts: &BuildEbpfOptions) -> Result<()> {
let BuildEbpfOptions {
target,
libbpf_dir: _,
} = opts;
let mut dir = PathBuf::from(workspace_root()); let mut dir = PathBuf::from(workspace_root());
dir.push("test/integration-ebpf"); dir.push("test/integration-ebpf");
let target = format!("--target={}", opts.target); exec(
let args = vec![ Command::new("cargo")
"+nightly",
"build",
"--release",
"--verbose",
target.as_str(),
"-Z",
"build-std=core",
];
let status = Command::new("cargo")
.current_dir(&dir) .current_dir(&dir)
.args(&args) .args(["+nightly", "build", "--release", "--target"])
.status() .arg(target.to_string())
.expect("failed to build bpf program"); .args(["-Z", "build-std=core"])
assert!(status.success()); .current_dir(&dir),
Ok(()) )
} }
fn get_libbpf_headers<P: AsRef<Path>>(libbpf_dir: P, include_path: P) -> anyhow::Result<()> { fn get_libbpf_headers(libbpf_dir: &Path, include_path: &Path) -> Result<()> {
let dir = include_path.as_ref(); fs::create_dir_all(include_path)?;
fs::create_dir_all(dir)?;
let mut includedir = OsString::new(); let mut includedir = OsString::new();
includedir.push("INCLUDEDIR="); includedir.push("INCLUDEDIR=");
includedir.push(dir.as_os_str()); includedir.push(include_path);
let status = Command::new("make") exec(
.current_dir(libbpf_dir.as_ref().join("src")) Command::new("make")
.current_dir(libbpf_dir.join("src"))
.arg(includedir) .arg(includedir)
.arg("install_headers") .arg("install_headers"),
.status() )
.expect("failed to build get libbpf headers");
assert!(status.success());
Ok(())
} }
fn build_c_ebpf(opts: &BuildEbpfOptions) -> anyhow::Result<()> { fn build_c_ebpf(opts: &BuildEbpfOptions) -> Result<()> {
let BuildEbpfOptions { target, libbpf_dir } = opts;
let mut src = PathBuf::from(workspace_root()); let mut src = PathBuf::from(workspace_root());
src.push("test/integration-ebpf/src/bpf"); src.push("test/integration-ebpf/src/bpf");
let mut out_path = PathBuf::from(workspace_root()); let mut out_path = PathBuf::from(workspace_root());
out_path.push("target"); out_path.push("target");
out_path.push(opts.target.to_string()); out_path.push(target.to_string());
out_path.push("release"); out_path.push("release");
let include_path = out_path.join("include"); let include_path = out_path.join("include");
get_libbpf_headers(&opts.libbpf_dir, &include_path)?; get_libbpf_headers(libbpf_dir, &include_path)?;
let files = fs::read_dir(&src).unwrap(); let files = fs::read_dir(&src).unwrap();
for file in files { for file in files {
let p = file.unwrap().path(); let p = file.unwrap().path();
@ -120,11 +115,7 @@ fn build_c_ebpf(opts: &BuildEbpfOptions) -> anyhow::Result<()> {
} }
/// Build eBPF programs with clang and libbpf headers. /// Build eBPF programs with clang and libbpf headers.
fn compile_with_clang<P: Clone + AsRef<Path>>( fn compile_with_clang(src: &Path, out: &Path, include_path: &Path) -> Result<()> {
src: P,
out: P,
include_path: P,
) -> anyhow::Result<()> {
let clang: Cow<'_, _> = match env::var_os("CLANG") { let clang: Cow<'_, _> = match env::var_os("CLANG") {
Some(val) => val.into(), Some(val) => val.into(),
None => OsStr::new("/usr/bin/clang").into(), None => OsStr::new("/usr/bin/clang").into(),
@ -134,36 +125,14 @@ fn compile_with_clang<P: Clone + AsRef<Path>>(
"aarch64" => "arm64", "aarch64" => "arm64",
arch => arch, arch => arch,
}; };
let mut cmd = Command::new(clang); exec(
cmd.arg("-v") Command::new(clang)
.arg("-I") .arg("-I")
.arg(include_path.as_ref()) .arg(include_path)
.arg("-g") .args(["-g", "-O2", "-target", "bpf", "-c"])
.arg("-O2")
.arg("-target")
.arg("bpf")
.arg("-c")
.arg(format!("-D__TARGET_ARCH_{arch}")) .arg(format!("-D__TARGET_ARCH_{arch}"))
.arg(src.as_ref().as_os_str()) .arg(src)
.arg("-o") .arg("-o")
.arg(out.as_ref().as_os_str()); .arg(out),
)
let Output {
status,
stdout,
stderr,
} = cmd.output().context("Failed to execute clang")?;
if !status.success() {
bail!(
"Failed to compile eBPF programs\n \
stdout=\n \
{}\n \
stderr=\n \
{}\n",
String::from_utf8(stdout).unwrap(),
String::from_utf8(stderr).unwrap()
);
}
Ok(())
} }

@ -1,7 +1,8 @@
use anyhow::Result;
use clap::Parser; use clap::Parser;
use std::process::Command; use std::process::Command;
use crate::build_ebpf; use crate::{build_ebpf, utils::exec};
#[derive(Parser)] #[derive(Parser)]
pub struct Options { pub struct Options {
@ -13,20 +14,19 @@ pub struct Options {
pub ebpf_options: build_ebpf::BuildEbpfOptions, pub ebpf_options: build_ebpf::BuildEbpfOptions,
} }
pub fn build_test(opts: Options) -> anyhow::Result<()> { pub fn build_test(opts: Options) -> Result<()> {
build_ebpf::build_ebpf(opts.ebpf_options)?; let Options {
musl_target,
ebpf_options,
} = opts;
let mut args = ["build", "-p", "integration-test", "--verbose"] build_ebpf::build_ebpf(ebpf_options)?;
.iter()
.map(|s| s.to_string()) let mut cmd = Command::new("cargo");
.collect::<Vec<_>>(); cmd.args(["build", "-p", "integration-test"]);
if let Some(target) = opts.musl_target {
args.push(format!("--target={target}")); if let Some(target) = musl_target {
cmd.args(["--target", &target]);
} }
let status = Command::new("cargo") exec(&mut cmd)
.args(&args)
.status()
.expect("failed to build bpf program");
assert!(status.success());
Ok(())
} }

@ -1,3 +1,5 @@
use crate::utils::exec;
use anyhow::{Context as _, Result};
use std::{ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
process::Command, process::Command,
@ -7,7 +9,7 @@ use std::{fs, io, io::Write};
use indoc::indoc; use indoc::indoc;
pub fn docs() -> Result<(), anyhow::Error> { pub fn docs() -> Result<()> {
let current_dir = PathBuf::from("."); let current_dir = PathBuf::from(".");
let header_path = current_dir.join("header.html"); let header_path = current_dir.join("header.html");
let mut header = fs::File::create(&header_path).expect("can't create header.html"); let mut header = fs::File::create(&header_path).expect("can't create header.html");
@ -19,8 +21,11 @@ pub fn docs() -> Result<(), anyhow::Error> {
build_docs(&current_dir.join("aya"), &abs_header_path)?; build_docs(&current_dir.join("aya"), &abs_header_path)?;
build_docs(&current_dir.join("bpf/aya-bpf"), &abs_header_path)?; build_docs(&current_dir.join("bpf/aya-bpf"), &abs_header_path)?;
copy_dir_all("./target/doc", "./site/user")?; copy_dir_all("./target/doc".as_ref(), "./site/user".as_ref())?;
copy_dir_all("./target/bpfel-unknown-none/doc", "./site/bpf")?; copy_dir_all(
"./target/bpfel-unknown-none/doc".as_ref(),
"./site/bpf".as_ref(),
)?;
let mut robots = fs::File::create("site/robots.txt").expect("can't create robots.txt"); let mut robots = fs::File::create("site/robots.txt").expect("can't create robots.txt");
robots robots
@ -53,17 +58,15 @@ pub fn docs() -> Result<(), anyhow::Error> {
Ok(()) Ok(())
} }
fn build_docs(working_dir: &PathBuf, abs_header_path: &Path) -> Result<(), anyhow::Error> { fn build_docs(working_dir: &Path, abs_header_path: &Path) -> Result<()> {
let replace = Command::new("sed") exec(Command::new("sed").current_dir(working_dir).args([
.current_dir(working_dir) "-i.bak",
.args(vec!["-i.bak", "s/crabby.svg/crabby_dev.svg/", "src/lib.rs"]) "s/crabby.svg/crabby_dev.svg/",
.status() "src/lib.rs",
.expect("failed to replace logo"); ]))?;
assert!(replace.success());
let args = vec!["+nightly", "doc", "--no-deps", "--all-features"];
let status = Command::new("cargo") exec(
Command::new("cargo")
.current_dir(working_dir) .current_dir(working_dir)
.env( .env(
"RUSTDOCFLAGS", "RUSTDOCFLAGS",
@ -72,30 +75,29 @@ fn build_docs(working_dir: &PathBuf, abs_header_path: &Path) -> Result<(), anyho
abs_header_path.to_str().unwrap() abs_header_path.to_str().unwrap()
), ),
) )
.args(args) .args(["+nightly", "doc", "--no-deps", "--all-features"]),
.status() )?;
.expect("failed to build aya docs");
assert!(status.success());
fs::rename( fs::rename(
working_dir.join("src/lib.rs.bak"), working_dir.join("src/lib.rs.bak"),
working_dir.join("src/lib.rs"), working_dir.join("src/lib.rs"),
) )
.unwrap(); .context("Failed to rename lib.rs.bak to lib.rs")
Ok(())
} }
fn copy_dir_all<P1: AsRef<Path>, P2: AsRef<Path>>(src: P1, dst: P2) -> io::Result<()> { fn copy_dir_all(src: &Path, dst: &Path) -> io::Result<()> {
fs::create_dir_all(&dst)?; fs::create_dir_all(dst)?;
for entry in fs::read_dir(src)? { for entry in fs::read_dir(src)? {
let entry = entry?; let entry = entry?;
let ty = entry.file_type()?; let ty = entry.file_type()?;
let src = entry.path();
let src = src.as_path();
let dst = dst.join(entry.file_name());
let dst = dst.as_path();
if ty.is_dir() { if ty.is_dir() {
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; copy_dir_all(src, dst)?;
} else { } else if !dst.exists() {
let new_path = dst.as_ref().join(entry.file_name()); fs::copy(src, dst)?;
if !new_path.exists() {
fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
}
} }
} }
Ok(()) Ok(())

@ -5,7 +5,7 @@ use std::{
process::{Command, Stdio}, process::{Command, Stdio},
}; };
use anyhow::Context as _; use anyhow::{Context as _, Result};
use cargo_metadata::{Artifact, CompilerMessage, Message, Target}; use cargo_metadata::{Artifact, CompilerMessage, Message, Target};
use clap::Parser; use clap::Parser;
@ -31,12 +31,14 @@ pub struct Options {
} }
/// Build the project /// Build the project
fn build(release: bool) -> Result<Vec<(PathBuf, PathBuf)>, anyhow::Error> { fn build(release: bool) -> Result<Vec<(PathBuf, PathBuf)>> {
let mut cmd = Command::new("cargo"); let mut cmd = Command::new("cargo");
cmd.arg("build") cmd.args([
.arg("--tests") "build",
.arg("--message-format=json") "--tests",
.arg("--package=integration-test"); "--message-format=json",
"--package=integration-test",
]);
if release { if release {
cmd.arg("--release"); cmd.arg("--release");
} }
@ -83,7 +85,7 @@ fn build(release: bool) -> Result<Vec<(PathBuf, PathBuf)>, anyhow::Error> {
} }
/// Build and run the project /// Build and run the project
pub fn run(opts: Options) -> Result<(), anyhow::Error> { pub fn run(opts: Options) -> Result<()> {
let Options { let Options {
bpf_target, bpf_target,
release, release,
@ -116,10 +118,8 @@ pub fn run(opts: Options) -> Result<(), anyhow::Error> {
println!("{} running {cmd:?}", src_path.display()); println!("{} running {cmd:?}", src_path.display());
let status = cmd let status = cmd
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status() .status()
.context("failed to run {cmd:?}")?; .with_context(|| format!("failed to run {cmd:?}"))?;
match status.code() { match status.code() {
Some(code) => match code { Some(code) => match code {
0 => {} 0 => {}

@ -1,15 +1,24 @@
use serde_json::Value;
use std::{cell::OnceCell, process::Command}; use std::{cell::OnceCell, process::Command};
use anyhow::{bail, Context as _, Result};
pub fn workspace_root() -> &'static str { pub fn workspace_root() -> &'static str {
static mut WORKSPACE_ROOT: OnceCell<String> = OnceCell::new(); static mut WORKSPACE_ROOT: OnceCell<String> = OnceCell::new();
unsafe { &mut WORKSPACE_ROOT }.get_or_init(|| { unsafe { &mut WORKSPACE_ROOT }.get_or_init(|| {
let output = Command::new("cargo").arg("metadata").output().unwrap(); let cmd = cargo_metadata::MetadataCommand::new();
if !output.status.success() { cmd.exec().unwrap().workspace_root.to_string()
panic!("unable to run cargo metadata")
}
let stdout = String::from_utf8(output.stdout).unwrap();
let v: Value = serde_json::from_str(&stdout).unwrap();
v["workspace_root"].as_str().unwrap().to_string()
}) })
} }
pub fn exec(cmd: &mut Command) -> Result<()> {
let status = cmd
.status()
.with_context(|| format!("failed to run {cmd:?}"))?;
match status.code() {
Some(code) => match code {
0 => Ok(()),
code => bail!("{cmd:?} exited with code {code}"),
},
None => bail!("{cmd:?} terminated by signal"),
}
}

Loading…
Cancel
Save