diff --git a/aya-log-common/src/lib.rs b/aya-log-common/src/lib.rs index ac11efdd..ec2cd2b3 100644 --- a/aya-log-common/src/lib.rs +++ b/aya-log-common/src/lib.rs @@ -295,6 +295,7 @@ pub fn write_record_header( mod test { use super::*; + #[test] fn log_value_length_sufficient() { assert!( LOG_BUF_CAPACITY >= LogValueLength::MAX.into(), diff --git a/aya-obj/src/btf/btf.rs b/aya-obj/src/btf/btf.rs index a9049b1e..295fb55e 100644 --- a/aya-obj/src/btf/btf.rs +++ b/aya-obj/src/btf/btf.rs @@ -135,7 +135,7 @@ pub enum BtfError { #[source] io_error: std::io::Error, /// The error log produced by the kernel verifier. - verifier_log: String, + verifier_log: Cow<'static, str>, }, /// offset not found for symbol diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs index e9093e62..e5e2f3ab 100644 --- a/aya-obj/src/obj.rs +++ b/aya-obj/src/obj.rs @@ -109,7 +109,7 @@ pub struct Object { /// Program license pub license: CString, /// Kernel version - pub kernel_version: KernelVersion, + pub kernel_version: Option, /// Program BTF pub btf: Option, /// Program BTF.ext @@ -135,7 +135,7 @@ pub struct Program { /// The license pub license: CString, /// The kernel version - pub kernel_version: KernelVersion, + pub kernel_version: Option, /// The section containing the program pub section: ProgramSection, /// The section index of the program @@ -579,7 +579,7 @@ impl Object { let kernel_version = if let Some(section) = obj.section_by_name("version") { parse_version(Section::try_from(§ion)?.data, endianness)? } else { - KernelVersion::Any + None }; let mut bpf_obj = Object::new(endianness, license, kernel_version); @@ -631,7 +631,7 @@ impl Object { Ok(bpf_obj) } - fn new(endianness: Endianness, license: CString, kernel_version: KernelVersion) -> Object { + fn new(endianness: Endianness, license: CString, kernel_version: Option) -> Object { Object { endianness, license, @@ -1256,7 +1256,7 @@ fn parse_license(data: &[u8]) -> Result { .to_owned()) } -fn parse_version(data: &[u8], endianness: object::Endianness) -> Result { +fn parse_version(data: &[u8], endianness: object::Endianness) -> Result, ParseError> { let data = match data.len() { 4 => data.try_into().unwrap(), _ => { @@ -1271,9 +1271,10 @@ fn parse_version(data: &[u8], endianness: object::Endianness) -> Result u32::from_le_bytes(data), }; - Ok(match v { - KERNEL_VERSION_ANY => KernelVersion::Any, - v => KernelVersion::Version(v), + Ok(if v == KERNEL_VERSION_ANY { + None + } else { + Some(v) }) } @@ -1301,24 +1302,6 @@ fn get_map_field(btf: &Btf, type_id: u32) -> Result { 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 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 // bytes and are relocated based on their section index. fn parse_data_map_section(section: &Section) -> Result { @@ -1592,23 +1575,20 @@ mod tests { Err(ParseError::InvalidKernelVersion { .. }) )); - assert_eq!( - parse_version(&0xFFFF_FFFEu32.to_le_bytes(), Endianness::Little) - .expect("failed to parse magic version"), - KernelVersion::Any - ); + assert!(matches!( + parse_version(&0xFFFF_FFFEu32.to_le_bytes(), Endianness::Little), + Ok(None) + )); - assert_eq!( - parse_version(&0xFFFF_FFFEu32.to_be_bytes(), Endianness::Big) - .expect("failed to parse magic version"), - KernelVersion::Any - ); + assert!(matches!( + parse_version(&0xFFFF_FFFEu32.to_be_bytes(), Endianness::Big), + Ok(None) + )); - assert_eq!( - parse_version(&1234u32.to_le_bytes(), Endianness::Little) - .expect("failed to parse magic version"), - KernelVersion::Version(1234) - ); + assert!(matches!( + parse_version(&1234u32.to_le_bytes(), Endianness::Little), + Ok(Some(1234)) + )); } #[test] @@ -1699,11 +1679,7 @@ mod tests { } fn fake_obj() -> Object { - Object::new( - Endianness::Little, - CString::new("GPL").unwrap(), - KernelVersion::Any, - ) + Object::new(Endianness::Little, CString::new("GPL").unwrap(), None) } #[test] @@ -1753,7 +1729,7 @@ mod tests { obj.parse_program(&fake_section(BpfSectionKind::Program,"kprobe/foo", bytes_of(&fake_ins()))), Ok((Program { license, - kernel_version: KernelVersion::Any, + kernel_version: None, section: ProgramSection::KProbe { .. }, .. }, Function { name, diff --git a/aya/Cargo.toml b/aya/Cargo.toml index f12ebf27..2abcfc47 100644 --- a/aya/Cargo.toml +++ b/aya/Cargo.toml @@ -11,21 +11,31 @@ documentation = "https://docs.rs/aya" edition = "2021" [dependencies] -libc = { version = "0.2.105" } +async-io = { version = "1.3", optional = true } 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" bytes = "1" lazy_static = "1" -parking_lot = { version = "0.12.0", features = ["send_guard"] } -tokio = { version = "1.24.0", features = ["macros", "rt", "rt-multi-thread", "net"], optional = true } -async-io = { version = "1.3", optional = true } +libc = { version = "0.2.105" } 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] -matches = "0.1.8" futures = { version = "0.3.12", default-features = false, features = ["std"] } +matches = "0.1.8" [features] default = [] @@ -35,4 +45,4 @@ async_std = ["async-io", "async"] [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs","-D", "warnings"] +rustdoc-args = ["--cfg", "docsrs", "-D", "warnings"] diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index 74eb5317..0c9d1d51 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -39,7 +39,7 @@ use crate::{ 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, }, - 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; @@ -133,7 +133,7 @@ pub struct BpfLoader<'a> { bitflags! { /// Used to set the verifier log level flags in [BpfLoader](BpfLoader::verifier_log_level()). - #[derive(Debug)] + #[derive(Clone, Copy, Debug)] pub struct VerifierLogLevel: u32 { /// Sets no verifier logging. const DISABLE = 0; @@ -349,14 +349,22 @@ impl<'a> BpfLoader<'a> { /// # Ok::<(), aya::BpfError>(()) /// ``` pub fn load(&mut self, data: &[u8]) -> Result { - 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)?; - obj.patch_map_data(self.globals.clone())?; + obj.patch_map_data(globals.clone())?; let btf_fd = if let Some(features) = &FEATURES.btf() { if let Some(btf) = obj.fixup_and_sanitize_btf(features)? { // load btf to the kernel - Some(load_btf(btf.to_bytes())?) + let btf = load_btf(btf.to_bytes(), *verifier_log_level)?; + Some(btf) } else { None } @@ -364,7 +372,7 @@ impl<'a> BpfLoader<'a> { None }; - if let Some(btf) = &self.btf { + if let Some(btf) = &btf { obj.relocate_btf(btf)?; } let mut maps = HashMap::new(); @@ -375,7 +383,7 @@ impl<'a> BpfLoader<'a> { continue; } - match self.max_entries.get(name.as_str()) { + match max_entries.get(name.as_str()) { Some(size) => obj.set_max_entries(*size), None => { 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() { PinningType::ByName => { - let path = match &self.map_pin_path { + let path = match &map_pin_path { Some(p) => p, None => return Err(BpfError::NoPinPath), }; @@ -466,72 +474,72 @@ impl<'a> BpfLoader<'a> { let section = prog_obj.section.clone(); 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 { - data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), + data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), }) } else { match §ion { 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, }), 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, }), 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, }), 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, }), 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 { .. } => { 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, .. } => { 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 { data.flags = BPF_F_XDP_HAS_FRAGS; } Program::Xdp(Xdp { data }) } 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 { .. } => { 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, .. } => { 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, }) } 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, }), 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, }), 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 { .. } => { 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 { CString::from_vec_unchecked(Vec::from(name.clone())) .into_boxed_c_str() @@ -539,37 +547,37 @@ impl<'a> BpfLoader<'a> { }) } 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, }), 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), }), 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), }), ProgramSection::CgroupSockAddr { attach_type, .. } => { 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, }) } 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 { - data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), + data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), }), ProgramSection::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, .. } => { 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 { data.flags = BPF_F_SLEEPABLE; } @@ -577,30 +585,30 @@ impl<'a> BpfLoader<'a> { } ProgramSection::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 { - 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 { - 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 { - 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 { - 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, .. } => { 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, }) } ProgramSection::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), } -fn load_btf(raw_btf: Vec) -> Result { - let mut logger = VerifierLog::new(); - let ret = retry_with_verifier_logs(10, &mut logger, |logger| { - bpf_load_btf(raw_btf.as_slice(), logger) +fn load_btf(raw_btf: Vec, verifier_log_level: VerifierLogLevel) -> Result { + let (ret, verifier_log) = retry_with_verifier_logs(10, |logger| { + bpf_load_btf(raw_btf.as_slice(), logger, verifier_log_level) }); match ret { Ok(fd) => Ok(fd as RawFd), - Err((_, io_error)) => { - logger.truncate(); - Err(BtfError::LoadError { - io_error, - verifier_log: logger - .as_c_str() - .map(|s| s.to_string_lossy().to_string()) - .unwrap_or_else(|| "[none]".to_owned()), - }) - } + Err((_, io_error)) => Err(BtfError::LoadError { + io_error, + verifier_log, + }), } } diff --git a/aya/src/maps/mod.rs b/aya/src/maps/mod.rs index f2b7a6a5..3961c212 100644 --- a/aya/src/maps/mod.rs +++ b/aya/src/maps/mod.rs @@ -49,6 +49,7 @@ use std::{ use libc::{getrlimit, rlimit, RLIMIT_MEMLOCK, RLIM_INFINITY}; use log::warn; +use procfs::KernelVersion; use thiserror::Error; use crate::{ @@ -56,7 +57,7 @@ use crate::{ pin::PinError, sys::{ 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, PinningType, Pod, @@ -489,18 +490,23 @@ impl MapData { 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)| { - let k_ver = kernel_version().unwrap(); - if k_ver < (5, 11, 0) { - maybe_warn_rlimit(); - } + #[cfg(not(test))] + let kernel_version = KernelVersion::current().unwrap(); + #[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(); + } - MapError::CreateError { - name: name.into(), - code, - io_error, - } - })? as RawFd; + MapError::CreateError { + name: name.into(), + code, + io_error, + } + }, + )? as RawFd; self.fd = Some(fd); diff --git a/aya/src/programs/cgroup_device.rs b/aya/src/programs/cgroup_device.rs index ad211b83..b1a8e39d 100644 --- a/aya/src/programs/cgroup_device.rs +++ b/aya/src/programs/cgroup_device.rs @@ -1,4 +1,6 @@ //! Cgroup device programs. + +use procfs::KernelVersion; use std::os::fd::{AsRawFd, RawFd}; use crate::{ @@ -6,7 +8,7 @@ use crate::{ programs::{ 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. @@ -62,8 +64,7 @@ impl CgroupDevice { let prog_fd = self.data.fd_or_err()?; let cgroup_fd = cgroup.as_raw_fd(); - let k_ver = kernel_version().unwrap(); - if k_ver >= (5, 7, 0) { + if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) { let link_fd = bpf_link_create(prog_fd, cgroup_fd, BPF_CGROUP_DEVICE, None, 0).map_err( |(_, io_error)| ProgramError::SyscallError { call: "bpf_link_create", diff --git a/aya/src/programs/cgroup_skb.rs b/aya/src/programs/cgroup_skb.rs index ed92fd2c..a76bb14a 100644 --- a/aya/src/programs/cgroup_skb.rs +++ b/aya/src/programs/cgroup_skb.rs @@ -1,4 +1,6 @@ //! Cgroup skb programs. + +use procfs::KernelVersion; use std::{ hash::Hash, os::fd::{AsRawFd, RawFd}, @@ -13,7 +15,8 @@ use crate::{ programs::{ 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. @@ -96,8 +99,7 @@ impl CgroupSkb { CgroupSkbAttachType::Ingress => BPF_CGROUP_INET_INGRESS, CgroupSkbAttachType::Egress => BPF_CGROUP_INET_EGRESS, }; - let k_ver = kernel_version().unwrap(); - if k_ver >= (5, 7, 0) { + if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) { let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err( |(_, io_error)| ProgramError::SyscallError { call: "bpf_link_create", @@ -150,7 +152,7 @@ impl CgroupSkb { path: P, expected_attach_type: CgroupSkbAttachType, ) -> Result { - let data = ProgramData::from_pinned_path(path)?; + let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; Ok(Self { data, expected_attach_type: Some(expected_attach_type), diff --git a/aya/src/programs/cgroup_sock.rs b/aya/src/programs/cgroup_sock.rs index a1c32179..19ac6cf8 100644 --- a/aya/src/programs/cgroup_sock.rs +++ b/aya/src/programs/cgroup_sock.rs @@ -1,6 +1,8 @@ //! Cgroup socket programs. + pub use aya_obj::programs::CgroupSockAttachType; +use procfs::KernelVersion; use std::{ hash::Hash, os::fd::{AsRawFd, RawFd}, @@ -12,7 +14,8 @@ use crate::{ programs::{ 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. @@ -71,8 +74,7 @@ impl CgroupSock { let prog_fd = self.data.fd_or_err()?; let cgroup_fd = cgroup.as_raw_fd(); let attach_type = self.data.expected_attach_type.unwrap(); - let k_ver = kernel_version().unwrap(); - if k_ver >= (5, 7, 0) { + if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) { let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err( |(_, io_error)| ProgramError::SyscallError { call: "bpf_link_create", @@ -125,7 +127,7 @@ impl CgroupSock { path: P, attach_type: CgroupSockAttachType, ) -> Result { - let data = ProgramData::from_pinned_path(path)?; + let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; Ok(Self { data, attach_type }) } } diff --git a/aya/src/programs/cgroup_sock_addr.rs b/aya/src/programs/cgroup_sock_addr.rs index de9a48a2..72eca7ec 100644 --- a/aya/src/programs/cgroup_sock_addr.rs +++ b/aya/src/programs/cgroup_sock_addr.rs @@ -1,6 +1,8 @@ //! Cgroup socket address programs. + pub use aya_obj::programs::CgroupSockAddrAttachType; +use procfs::KernelVersion; use std::{ hash::Hash, os::fd::{AsRawFd, RawFd}, @@ -12,7 +14,8 @@ use crate::{ programs::{ 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`). @@ -72,8 +75,7 @@ impl CgroupSockAddr { let prog_fd = self.data.fd_or_err()?; let cgroup_fd = cgroup.as_raw_fd(); let attach_type = self.data.expected_attach_type.unwrap(); - let k_ver = kernel_version().unwrap(); - if k_ver >= (5, 7, 0) { + if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) { let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err( |(_, io_error)| ProgramError::SyscallError { call: "bpf_link_create", @@ -131,7 +133,7 @@ impl CgroupSockAddr { path: P, attach_type: CgroupSockAddrAttachType, ) -> Result { - let data = ProgramData::from_pinned_path(path)?; + let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; Ok(Self { data, attach_type }) } } diff --git a/aya/src/programs/cgroup_sockopt.rs b/aya/src/programs/cgroup_sockopt.rs index 021ba38a..9d8b314a 100644 --- a/aya/src/programs/cgroup_sockopt.rs +++ b/aya/src/programs/cgroup_sockopt.rs @@ -1,6 +1,8 @@ //! Cgroup socket option programs. + pub use aya_obj::programs::CgroupSockoptAttachType; +use procfs::KernelVersion; use std::{ hash::Hash, os::fd::{AsRawFd, RawFd}, @@ -12,7 +14,8 @@ use crate::{ programs::{ 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. @@ -69,8 +72,7 @@ impl CgroupSockopt { let prog_fd = self.data.fd_or_err()?; let cgroup_fd = cgroup.as_raw_fd(); let attach_type = self.data.expected_attach_type.unwrap(); - let k_ver = kernel_version().unwrap(); - if k_ver >= (5, 7, 0) { + if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) { let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err( |(_, io_error)| ProgramError::SyscallError { call: "bpf_link_create", @@ -126,7 +128,7 @@ impl CgroupSockopt { path: P, attach_type: CgroupSockoptAttachType, ) -> Result { - let data = ProgramData::from_pinned_path(path)?; + let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; Ok(Self { data, attach_type }) } } diff --git a/aya/src/programs/cgroup_sysctl.rs b/aya/src/programs/cgroup_sysctl.rs index f2bfd56b..c3ce1011 100644 --- a/aya/src/programs/cgroup_sysctl.rs +++ b/aya/src/programs/cgroup_sysctl.rs @@ -1,4 +1,6 @@ //! Cgroup sysctl programs. + +use procfs::KernelVersion; use std::{ hash::Hash, os::fd::{AsRawFd, RawFd}, @@ -9,7 +11,7 @@ use crate::{ programs::{ 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. @@ -64,8 +66,7 @@ impl CgroupSysctl { let prog_fd = self.data.fd_or_err()?; let cgroup_fd = cgroup.as_raw_fd(); - let k_ver = kernel_version().unwrap(); - if k_ver >= (5, 7, 0) { + if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) { let link_fd = bpf_link_create(prog_fd, cgroup_fd, BPF_CGROUP_SYSCTL, None, 0).map_err( |(_, io_error)| ProgramError::SyscallError { call: "bpf_link_create", diff --git a/aya/src/programs/kprobe.rs b/aya/src/programs/kprobe.rs index 65a505d2..53dac216 100644 --- a/aya/src/programs/kprobe.rs +++ b/aya/src/programs/kprobe.rs @@ -10,6 +10,7 @@ use crate::{ probe::{attach, ProbeKind}, ProgramData, ProgramError, }, + VerifierLogLevel, }; /// 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 /// the program being unloaded from the kernel if it is still pinned. pub fn from_pin>(path: P, kind: ProbeKind) -> Result { - let data = ProgramData::from_pinned_path(path)?; + let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; Ok(Self { data, kind }) } } diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index b47687b2..3ca6854c 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -65,7 +65,9 @@ mod utils; pub mod xdp; use libc::ENOSPC; +use procfs::KernelVersion; use std::{ + borrow::Cow, ffi::CString, io, os::unix::io::{AsRawFd, RawFd}, @@ -105,14 +107,14 @@ pub use xdp::{Xdp, XdpError, XdpFlags}; use crate::{ generated::{bpf_attach_type, bpf_prog_info, bpf_prog_type}, maps::MapError, - obj::{self, btf::BtfError, Function, KernelVersion}, + obj::{self, btf::BtfError, Function}, pin::PinError, sys::{ 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, retry_with_verifier_logs, BpfLoadProgramAttrs, }, - util::VerifierLog, + VerifierLogLevel, }; /// Error type returned when working with programs. @@ -141,7 +143,7 @@ pub enum ProgramError { #[source] io_error: io::Error, /// The error log produced by the kernel verifier. - verifier_log: String, + verifier_log: Cow<'static, str>, }, /// A syscall failed. @@ -413,7 +415,7 @@ pub(crate) struct ProgramData { pub(crate) attach_btf_id: Option, pub(crate) attach_prog_fd: Option, pub(crate) btf_fd: Option, - pub(crate) verifier_log_level: u32, + pub(crate) verifier_log_level: VerifierLogLevel, pub(crate) path: Option, pub(crate) flags: u32, } @@ -423,7 +425,7 @@ impl ProgramData { name: Option, obj: (obj::Program, obj::Function), btf_fd: Option, - verifier_log_level: u32, + verifier_log_level: VerifierLogLevel, ) -> ProgramData { ProgramData { name, @@ -446,6 +448,7 @@ impl ProgramData { fd: RawFd, path: &Path, info: bpf_prog_info, + verifier_log_level: VerifierLogLevel, ) -> Result, ProgramError> { let attach_btf_id = if info.attach_btf_id > 0 { Some(info.attach_btf_id) @@ -474,7 +477,7 @@ impl ProgramData { attach_btf_id, attach_prog_fd: None, btf_fd: None, - verifier_log_level: 0, + verifier_log_level, path: Some(path.to_path_buf()), flags: 0, }) @@ -482,6 +485,7 @@ impl ProgramData { pub(crate) fn from_pinned_path>( path: P, + verifier_log_level: VerifierLogLevel, ) -> Result, ProgramError> { let path_string = CString::new(path.as_ref().as_os_str().to_string_lossy().as_bytes()).unwrap(); @@ -496,9 +500,8 @@ impl ProgramData { io_error, })?; - let info = ProgramInfo(info); - let name = info.name_as_str().map(|s| s.to_string()); - ProgramData::from_bpf_prog_info(name, fd, path.as_ref(), info.0) + let name = ProgramInfo(info).name_as_str().map(|s| s.to_string()); + ProgramData::from_bpf_prog_info(name, fd, path.as_ref(), info, verifier_log_level) } } @@ -548,7 +551,20 @@ fn load_program( prog_type: bpf_prog_type, data: &mut ProgramData, ) -> 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() { return Err(ProgramError::AlreadyLoaded); } @@ -573,17 +589,16 @@ fn load_program( }, ) = obj; - let target_kernel_version = match *kernel_version { - KernelVersion::Any => { - let (major, minor, patch) = crate::sys::kernel_version().unwrap(); - (major << 16) + (minor << 8) + patch - } - _ => (*kernel_version).into(), - }; - - let mut logger = VerifierLog::new(); + let target_kernel_version = kernel_version.unwrap_or_else(|| { + let KernelVersion { + major, + minor, + patch, + } = KernelVersion::current().unwrap(); + (u32::from(major) << 16) + (u32::from(minor) << 8) + u32::from(patch) + }); - let prog_name = if let Some(name) = &data.name { + let prog_name = if let Some(name) = name { let mut name = name.clone(); if name.len() > 15 { name.truncate(15); @@ -601,21 +616,20 @@ fn load_program( insns: instructions, license, kernel_version: target_kernel_version, - expected_attach_type: data.expected_attach_type, - prog_btf_fd: data.btf_fd, - attach_btf_obj_fd: data.attach_btf_obj_fd, - attach_btf_id: data.attach_btf_id, - attach_prog_fd: data.attach_prog_fd, + expected_attach_type: *expected_attach_type, + prog_btf_fd: *btf_fd, + attach_btf_obj_fd: *attach_btf_obj_fd, + attach_btf_id: *attach_btf_id, + attach_prog_fd: *attach_prog_fd, func_info_rec_size: *func_info_rec_size, func_info: func_info.clone(), line_info_rec_size: *line_info_rec_size, line_info: line_info.clone(), - flags: data.flags, + flags: *flags, }; - let verifier_log_level = data.verifier_log_level; - let ret = retry_with_verifier_logs(10, &mut logger, |logger| { - bpf_load_program(&attr, logger, verifier_log_level) + let (ret, verifier_log) = retry_with_verifier_logs(10, |logger| { + bpf_load_program(&attr, logger, *verifier_log_level) }); match ret { @@ -623,16 +637,10 @@ fn load_program( *fd = Some(prog_fd as RawFd); Ok(()) } - Err((_, io_error)) => { - logger.truncate(); - return Err(ProgramError::LoadError { - io_error, - verifier_log: logger - .as_c_str() - .map(|s| s.to_string_lossy().to_string()) - .unwrap_or_else(|| "[none]".to_owned()), - }); - } + Err((_, io_error)) => Err(ProgramError::LoadError { + io_error, + verifier_log, + }), } } @@ -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 /// the program being unloaded from the kernel if it is still pinned. pub fn from_pin>(path: P) -> Result { - let data = ProgramData::from_pinned_path(path)?; + let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; Ok(Self { data }) } } diff --git a/aya/src/programs/probe.rs b/aya/src/programs/probe.rs index f9d17bc0..c6320e1a 100644 --- a/aya/src/programs/probe.rs +++ b/aya/src/programs/probe.rs @@ -1,4 +1,5 @@ use libc::pid_t; +use procfs::KernelVersion; use std::{ fs::{self, OpenOptions}, io::{self, Write}, @@ -13,7 +14,7 @@ use crate::{ trace_point::read_sys_fs_trace_point_id, uprobe::UProbeError, utils::find_tracefs_path, 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); @@ -49,8 +50,7 @@ pub(crate) fn attach>( ) -> Result { // https://github.com/torvalds/linux/commit/e12f03d7031a977356e3d7b75a68c2185ff8d155 // Use debugfs to create probe - let k_ver = kernel_version().unwrap(); - if k_ver < (4, 17, 0) { + if KernelVersion::current().unwrap() >= KernelVersion::new(4, 17, 0) { let (fd, event_alias) = create_as_trace_point(kind, fn_name, offset, pid)?; let link = T::from(perf_attach_debugfs( program_data.fd_or_err()?, diff --git a/aya/src/programs/sk_skb.rs b/aya/src/programs/sk_skb.rs index df7729e8..ec90a174 100644 --- a/aya/src/programs/sk_skb.rs +++ b/aya/src/programs/sk_skb.rs @@ -13,6 +13,7 @@ use crate::{ ProgramError, }, sys::bpf_prog_attach, + VerifierLogLevel, }; /// 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 /// the program being unloaded from the kernel if it is still pinned. pub fn from_pin>(path: P, kind: SkSkbKind) -> Result { - let data = ProgramData::from_pinned_path(path)?; + let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; Ok(Self { data, kind }) } } diff --git a/aya/src/programs/tc.rs b/aya/src/programs/tc.rs index da20c635..fad633a3 100644 --- a/aya/src/programs/tc.rs +++ b/aya/src/programs/tc.rs @@ -17,6 +17,7 @@ use crate::{ netlink_qdisc_detach, }, util::{ifindex_from_ifname, tc_handler_make}, + VerifierLogLevel, }; /// 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 /// the program being unloaded from the kernel if it is still pinned. pub fn from_pin>(path: P) -> Result { - 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()) .unwrap() .into_boxed_c_str(); diff --git a/aya/src/programs/uprobe.rs b/aya/src/programs/uprobe.rs index 5c38e5de..b632407e 100644 --- a/aya/src/programs/uprobe.rs +++ b/aya/src/programs/uprobe.rs @@ -21,6 +21,7 @@ use crate::{ probe::{attach, ProbeKind}, ProgramData, ProgramError, }, + VerifierLogLevel, }; 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 /// the program being unloaded from the kernel if it is still pinned. pub fn from_pin>(path: P, kind: ProbeKind) -> Result { - let data = ProgramData::from_pinned_path(path)?; + let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; Ok(Self { data, kind }) } } diff --git a/aya/src/programs/xdp.rs b/aya/src/programs/xdp.rs index 4940fc7e..01843928 100644 --- a/aya/src/programs/xdp.rs +++ b/aya/src/programs/xdp.rs @@ -1,6 +1,8 @@ //! eXpress Data Path (XDP) programs. + use bitflags; use libc::if_nametoindex; +use procfs::KernelVersion; use std::{convert::TryFrom, ffi::CString, hash::Hash, io, mem, os::unix::io::RawFd}; use thiserror::Error; @@ -15,10 +17,7 @@ use crate::{ programs::{ define_link_wrapper, load_program, FdLink, Link, LinkError, ProgramData, ProgramError, }, - sys::{ - bpf_link_create, bpf_link_get_info_by_fd, bpf_link_update, kernel_version, - netlink_set_xdp_fd, - }, + sys::{bpf_link_create, bpf_link_get_info_by_fd, bpf_link_update, netlink_set_xdp_fd}, }; /// 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 if_index = if_index as RawFd; - let k_ver = kernel_version().unwrap(); - if k_ver >= (5, 9, 0) { + if KernelVersion::current().unwrap() >= KernelVersion::new(5, 9, 0) { let link_fd = bpf_link_create(prog_fd, if_index, BPF_XDP, None, flags.bits()).map_err( |(_, io_error)| ProgramError::SyscallError { call: "bpf_link_create", @@ -224,8 +222,7 @@ impl Link for NlLink { } fn detach(self) -> Result<(), ProgramError> { - let k_ver = kernel_version().unwrap(); - let flags = if k_ver >= (5, 7, 0) { + let flags = if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) { self.flags.bits() | XDP_FLAGS_REPLACE } else { self.flags.bits() diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index e0aa1669..5a7dc3c5 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -1,4 +1,5 @@ use std::{ + borrow::Cow, cmp::{self, min}, ffi::{CStr, CString}, io, @@ -12,6 +13,7 @@ use obj::{ maps::{bpf_map_def, LegacyMap}, BpfSectionKind, }; +use procfs::KernelVersion; use crate::{ generated::{ @@ -27,12 +29,16 @@ use crate::{ }, copy_instructions, }, - sys::{kernel_version, syscall, SysResult, Syscall}, - util::VerifierLog, - Btf, Pod, BPF_OBJ_NAME_LEN, + sys::{syscall, SysResult, Syscall}, + Btf, Pod, VerifierLogLevel, BPF_OBJ_NAME_LEN, }; -pub(crate) fn bpf_create_map(name: &CStr, def: &obj::Map, btf_fd: Option) -> SysResult { +pub(crate) fn bpf_create_map( + name: &CStr, + def: &obj::Map, + btf_fd: Option, + kernel_version: KernelVersion, +) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; 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) // 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 // older kernels for compatibility - let k_ver = kernel_version().unwrap(); - if k_ver >= (4, 15, 0) { + if kernel_version >= KernelVersion::new(4, 15, 0) { // 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); u.map_name[..name_len] @@ -124,8 +129,8 @@ pub(crate) struct BpfLoadProgramAttrs<'a> { pub(crate) fn bpf_load_program( aya_attr: &BpfLoadProgramAttrs, - logger: &mut VerifierLog, - verifier_log_level: u32, + log_buf: &mut [u8], + verifier_log_level: VerifierLogLevel, ) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; @@ -169,11 +174,10 @@ pub(crate) fn bpf_load_program( u.func_info_rec_size = aya_attr.func_info_rec_size as u32; } } - let log_buf = logger.buf(); - if log_buf.capacity() > 0 { - u.log_level = verifier_log_level; + if !log_buf.is_empty() { + u.log_level = verifier_log_level.bits(); 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 { 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) } -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::() }; let u = unsafe { &mut attr.__bindgen_anon_7 }; u.btf = raw_btf.as_ptr() as *const _ as u64; u.btf_size = mem::size_of_val(raw_btf) as u32; - let log_buf = log.buf(); - if log_buf.capacity() > 0 { - u.btf_log_level = 1; + if !log_buf.is_empty() { + u.btf_log_level = verifier_log_level.bits(); 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) } @@ -988,35 +995,41 @@ pub(crate) fn bpf_prog_get_next_id(id: u32) -> Result, (c_long, io:: pub(crate) fn retry_with_verifier_logs( max_retries: usize, - log: &mut VerifierLog, f: F, -) -> SysResult +) -> (SysResult, Cow<'static, str>) where - F: Fn(&mut VerifierLog) -> SysResult, + F: Fn(&mut [u8]) -> SysResult, { - // 1. Try the syscall - let ret = f(log); - if ret.is_ok() { - return ret; - } + const MIN_LOG_BUF_SIZE: usize = 1024 * 10; + const MAX_LOG_BUF_SIZE: usize = (std::u32::MAX >> 8) as usize; - // 2. Grow the log buffer so we can capture verifier output - // Retry this up to max_retries times - log.grow(); + let mut log_buf = Vec::new(); let mut retries = 0; - loop { - let ret = f(log); - match ret { - Err((v, io_error)) if retries == 0 || io_error.raw_os_error() == Some(ENOSPC) => { - if retries == max_retries { - return Err((v, io_error)); + let ret = f(log_buf.as_mut_slice()); + if retries != max_retries { + if let Err((_, io_error)) = &ret { + if retries == 0 || io_error.raw_os_error() == Some(ENOSPC) { + 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; + continue; } - retries += 1; - log.grow(); } - 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); } } diff --git a/aya/src/sys/mod.rs b/aya/src/sys/mod.rs index 294ff386..646d6cfe 100644 --- a/aya/src/sys/mod.rs +++ b/aya/src/sys/mod.rs @@ -5,15 +5,9 @@ mod perf_event; #[cfg(test)] mod fake; -use std::io; -#[cfg(not(test))] -use std::{ffi::CString, mem}; -#[cfg(not(test))] -use std::{fs::File, io::Read}; +use std::{io, mem}; -#[cfg(not(test))] -use libc::utsname; -use libc::{c_int, c_long, pid_t}; +use libc::{c_int, c_long, pid_t, SYS_bpf, SYS_perf_event_open}; pub(crate) use bpf::*; #[cfg(test)] @@ -25,7 +19,6 @@ use crate::generated::{bpf_attr, bpf_cmd, perf_event_attr}; pub(crate) type SysResult = Result; -#[cfg_attr(test, allow(dead_code))] pub(crate) enum Syscall<'a> { Bpf { cmd: bpf_cmd, @@ -46,126 +39,28 @@ pub(crate) enum Syscall<'a> { } fn syscall(call: Syscall) -> SysResult { - #[cfg(not(test))] - return unsafe { syscall_impl(call) }; - #[cfg(test)] 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::*; - let ret = match call { - Bpf { cmd, attr } => libc::syscall(SYS_bpf, cmd, attr, mem::size_of::()), - PerfEventOpen { - attr, - pid, - cpu, - group, - flags, - } => libc::syscall(SYS_perf_event_open, &attr, pid, cpu, group, flags), - PerfEventIoctl { fd, request, arg } => { - 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)); + #[cfg_attr(test, allow(unreachable_code))] + match unsafe { + match call { + Syscall::Bpf { cmd, attr } => { + libc::syscall(SYS_bpf, cmd, attr, mem::size_of::()) } - } - } - - 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::(); - 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)); + Syscall::PerfEventOpen { + attr, + pid, + cpu, + group, + flags, + } => libc::syscall(SYS_perf_event_open, &attr, pid, cpu, group, flags), + Syscall::PerfEventIoctl { fd, request, arg } => { + libc::ioctl(fd, request.try_into().unwrap(), arg) as libc::c_long } } - - let format = CString::new("%u.%u.%u").unwrap(); - 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)) + } { + ret @ 0.. => Ok(ret), + ret => Err((ret, io::Error::last_os_error())), } } diff --git a/aya/src/util.rs b/aya/src/util.rs index e3106046..b0bb3781 100644 --- a/aya/src/util.rs +++ b/aya/src/util.rs @@ -1,7 +1,7 @@ //! Utility functions. use std::{ collections::BTreeMap, - ffi::{CStr, CString}, + ffi::CString, fs::{self, File}, io::{self, BufReader}, mem, slice, @@ -200,57 +200,6 @@ pub(crate) fn bytes_of_slice(val: &[T]) -> &[u8] { 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, -} - -impl VerifierLog { - pub(crate) fn new() -> VerifierLog { - VerifierLog { buf: Vec::new() } - } - - pub(crate) fn buf(&mut self) -> &mut Vec { - &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)] mod tests { use super::*; diff --git a/test/integration-ebpf/src/pass.rs b/test/integration-ebpf/src/pass.rs index b1bdde99..8aaf085f 100644 --- a/test/integration-ebpf/src/pass.rs +++ b/test/integration-ebpf/src/pass.rs @@ -3,6 +3,8 @@ 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")] pub fn pass(ctx: XdpContext) -> u32 { match unsafe { try_pass(ctx) } { diff --git a/test/integration-test/Cargo.toml b/test/integration-test/Cargo.toml index 07eaa927..36f8270b 100644 --- a/test/integration-test/Cargo.toml +++ b/test/integration-test/Cargo.toml @@ -12,7 +12,7 @@ aya-obj = { path = "../../aya-obj" } libc = { version = "0.2.105" } log = "0.4" object = { version = "0.31", default-features = false, features = ["std", "read_core", "elf"] } +procfs = "0.15.1" rbpf = "0.2.0" -regex = "1" tempfile = "3.3.0" tokio = { version = "1.24", features = ["rt", "rt-multi-thread", "sync", "time"] } diff --git a/test/integration-test/tests/btf_relocations.rs b/test/integration-test/tests/btf_relocations.rs index 2811f74a..069bc76c 100644 --- a/test/integration-test/tests/btf_relocations.rs +++ b/test/integration-test/tests/btf_relocations.rs @@ -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 tempfile::TempDir; @@ -60,6 +61,11 @@ fn relocate_enum() { #[test] 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 { local_definition: r#" enum foo { D = -0x7AAAAAAA }; @@ -80,6 +86,11 @@ fn relocate_enum_signed() { #[test] 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 { local_definition: r#" enum foo { D = 0xAAAAAAAABBBBBBBB }; @@ -100,6 +111,11 @@ fn relocate_enum64() { #[test] 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 { local_definition: r#" enum foo { D = -0xAAAAAAABBBBBBBB }; @@ -266,15 +282,20 @@ impl RelocationTest { "# )) .context("Failed to compile BTF")?; - Command::new("llvm-objcopy") - .current_dir(tmp_dir.path()) + let mut cmd = Command::new("llvm-objcopy"); + cmd.current_dir(tmp_dir.path()) .args(["--dump-section", ".BTF=target.btf"]) - .arg(compiled_file) + .arg(compiled_file); + let status = cmd .status() - .context("Failed to run llvm-objcopy")? - .success() - .then_some(()) - .context("Failed to extract BTF")?; + .with_context(|| format!("Failed to run {cmd:?}"))?; + match status.code() { + Some(code) => match code { + 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()) .context("Error parsing generated 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 source = tmp_dir.path().join("source.c"); std::fs::write(&source, source_code).context("Writing bpf program failed")?; - Command::new("clang") - .current_dir(&tmp_dir) + let mut cmd = Command::new("clang"); + cmd.current_dir(&tmp_dir) .args(["-c", "-g", "-O2", "-target", "bpf"]) - .arg(&source) + .arg(&source); + let status = cmd .status() - .context("Failed to run clang")? - .success() - .then_some(()) - .context("Failed to compile eBPF source")?; + .with_context(|| format!("Failed to run {cmd:?}"))?; + match status.code() { + Some(code) => match code { + 0 => {} + code => bail!("{cmd:?} exited with code {code}"), + }, + None => bail!("{cmd:?} terminated by signal"), + } Ok((tmp_dir, source.with_extension("o"))) } diff --git a/test/integration-test/tests/common.rs b/test/integration-test/tests/common.rs deleted file mode 100644 index 3af8a597..00000000 --- a/test/integration-test/tests/common.rs +++ /dev/null @@ -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 = 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"); - } -} diff --git a/test/integration-test/tests/load.rs b/test/integration-test/tests/load.rs index 110025b2..47a851cf 100644 --- a/test/integration-test/tests/load.rs +++ b/test/integration-test/tests/load.rs @@ -1,3 +1,4 @@ +use procfs::KernelVersion; use std::{convert::TryInto as _, thread, time}; use aya::{ @@ -10,9 +11,6 @@ use aya::{ Bpf, }; -mod common; -use common::kernel_version; - const MAX_RETRIES: u32 = 100; const RETRY_DURATION_MS: u64 = 10; @@ -133,8 +131,9 @@ fn unload_kprobe() { #[test] fn pin_link() { - if kernel_version().unwrap() < (5, 9, 0) { - eprintln!("skipping test, XDP uses netlink"); + let kernel_version = KernelVersion::current().unwrap(); + if kernel_version < KernelVersion::new(5, 9, 0) { + eprintln!("skipping test on kernel {kernel_version:?}, XDP uses netlink"); return; } @@ -168,8 +167,9 @@ fn pin_link() { #[test] fn pin_lifecycle() { - if kernel_version().unwrap() < (5, 9, 0) { - eprintln!("skipping test, XDP uses netlink"); + 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; } diff --git a/test/integration-test/tests/smoke.rs b/test/integration-test/tests/smoke.rs index daeed03a..2bc10ff4 100644 --- a/test/integration-test/tests/smoke.rs +++ b/test/integration-test/tests/smoke.rs @@ -1,14 +1,19 @@ +use procfs::KernelVersion; + use aya::{ include_bytes_aligned, programs::{Extension, Xdp, XdpFlags}, Bpf, BpfLoader, }; -mod common; -use common::kernel_version; - #[test] 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 mut bpf = Bpf::load(bytes).unwrap(); let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); @@ -18,15 +23,11 @@ fn xdp() { #[test] fn extension() { - let (major, minor, _) = kernel_version().unwrap(); - if major < 5 || (minor == 5 && minor < 9) { - eprintln!( - "skipping as {}.{} does not meet version requirement of 5.9", - major, minor - ); + let kernel_version = KernelVersion::current().unwrap(); + if kernel_version < KernelVersion::new(5, 9, 0) { + eprintln!("skipping test on kernel {kernel_version:?}, XDP uses netlink"); return; } - // TODO: Check kernel version == 5.9 or later let main_bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/main.bpf.o"); let mut bpf = Bpf::load(main_bytes).unwrap(); diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 2f8a74d5..24d91ef4 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -12,5 +12,4 @@ clap = { version = "4", features = ["derive"] } indoc = "2.0" proc-macro2 = "1" quote = "1" -serde_json = "1" syn = "2" diff --git a/xtask/src/build_ebpf.rs b/xtask/src/build_ebpf.rs index 5a9889c3..bdbf44ed 100644 --- a/xtask/src/build_ebpf.rs +++ b/xtask/src/build_ebpf.rs @@ -4,13 +4,13 @@ use std::{ ffi::{OsStr, OsString}, fs, path::{Path, PathBuf}, - process::{Command, Output}, + process::Command, }; -use anyhow::{bail, Context}; +use anyhow::Result; use clap::Parser; -use crate::utils::workspace_root; +use crate::utils::{exec, workspace_root}; #[derive(Debug, Copy, Clone)] pub enum Architecture { @@ -49,61 +49,56 @@ pub struct BuildEbpfOptions { pub libbpf_dir: PathBuf, } -pub fn build_ebpf(opts: BuildEbpfOptions) -> anyhow::Result<()> { +pub fn build_ebpf(opts: BuildEbpfOptions) -> Result<()> { build_rust_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()); dir.push("test/integration-ebpf"); - let target = format!("--target={}", opts.target); - let args = vec![ - "+nightly", - "build", - "--release", - "--verbose", - target.as_str(), - "-Z", - "build-std=core", - ]; - let status = Command::new("cargo") - .current_dir(&dir) - .args(&args) - .status() - .expect("failed to build bpf program"); - assert!(status.success()); - Ok(()) + exec( + Command::new("cargo") + .current_dir(&dir) + .args(["+nightly", "build", "--release", "--target"]) + .arg(target.to_string()) + .args(["-Z", "build-std=core"]) + .current_dir(&dir), + ) } -fn get_libbpf_headers>(libbpf_dir: P, include_path: P) -> anyhow::Result<()> { - let dir = include_path.as_ref(); - fs::create_dir_all(dir)?; +fn get_libbpf_headers(libbpf_dir: &Path, include_path: &Path) -> Result<()> { + fs::create_dir_all(include_path)?; let mut includedir = OsString::new(); includedir.push("INCLUDEDIR="); - includedir.push(dir.as_os_str()); - let status = Command::new("make") - .current_dir(libbpf_dir.as_ref().join("src")) - .arg(includedir) - .arg("install_headers") - .status() - .expect("failed to build get libbpf headers"); - assert!(status.success()); - Ok(()) + includedir.push(include_path); + exec( + Command::new("make") + .current_dir(libbpf_dir.join("src")) + .arg(includedir) + .arg("install_headers"), + ) } -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()); src.push("test/integration-ebpf/src/bpf"); let mut out_path = PathBuf::from(workspace_root()); out_path.push("target"); - out_path.push(opts.target.to_string()); + out_path.push(target.to_string()); out_path.push("release"); 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(); for file in files { 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. -fn compile_with_clang>( - src: P, - out: P, - include_path: P, -) -> anyhow::Result<()> { +fn compile_with_clang(src: &Path, out: &Path, include_path: &Path) -> Result<()> { let clang: Cow<'_, _> = match env::var_os("CLANG") { Some(val) => val.into(), None => OsStr::new("/usr/bin/clang").into(), @@ -134,36 +125,14 @@ fn compile_with_clang>( "aarch64" => "arm64", arch => arch, }; - let mut cmd = Command::new(clang); - cmd.arg("-v") - .arg("-I") - .arg(include_path.as_ref()) - .arg("-g") - .arg("-O2") - .arg("-target") - .arg("bpf") - .arg("-c") - .arg(format!("-D__TARGET_ARCH_{arch}")) - .arg(src.as_ref().as_os_str()) - .arg("-o") - .arg(out.as_ref().as_os_str()); - - 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(()) + exec( + Command::new(clang) + .arg("-I") + .arg(include_path) + .args(["-g", "-O2", "-target", "bpf", "-c"]) + .arg(format!("-D__TARGET_ARCH_{arch}")) + .arg(src) + .arg("-o") + .arg(out), + ) } diff --git a/xtask/src/build_test.rs b/xtask/src/build_test.rs index b45943a1..0fe4c277 100644 --- a/xtask/src/build_test.rs +++ b/xtask/src/build_test.rs @@ -1,7 +1,8 @@ +use anyhow::Result; use clap::Parser; use std::process::Command; -use crate::build_ebpf; +use crate::{build_ebpf, utils::exec}; #[derive(Parser)] pub struct Options { @@ -13,20 +14,19 @@ pub struct Options { pub ebpf_options: build_ebpf::BuildEbpfOptions, } -pub fn build_test(opts: Options) -> anyhow::Result<()> { - build_ebpf::build_ebpf(opts.ebpf_options)?; +pub fn build_test(opts: Options) -> Result<()> { + let Options { + musl_target, + ebpf_options, + } = opts; - let mut args = ["build", "-p", "integration-test", "--verbose"] - .iter() - .map(|s| s.to_string()) - .collect::>(); - if let Some(target) = opts.musl_target { - args.push(format!("--target={target}")); + build_ebpf::build_ebpf(ebpf_options)?; + + let mut cmd = Command::new("cargo"); + cmd.args(["build", "-p", "integration-test"]); + + if let Some(target) = musl_target { + cmd.args(["--target", &target]); } - let status = Command::new("cargo") - .args(&args) - .status() - .expect("failed to build bpf program"); - assert!(status.success()); - Ok(()) + exec(&mut cmd) } diff --git a/xtask/src/docs/mod.rs b/xtask/src/docs/mod.rs index bfeca3a9..d392798c 100644 --- a/xtask/src/docs/mod.rs +++ b/xtask/src/docs/mod.rs @@ -1,3 +1,5 @@ +use crate::utils::exec; +use anyhow::{Context as _, Result}; use std::{ path::{Path, PathBuf}, process::Command, @@ -7,7 +9,7 @@ use std::{fs, io, io::Write}; use indoc::indoc; -pub fn docs() -> Result<(), anyhow::Error> { +pub fn docs() -> Result<()> { let current_dir = PathBuf::from("."); let header_path = current_dir.join("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(¤t_dir.join("aya"), &abs_header_path)?; build_docs(¤t_dir.join("bpf/aya-bpf"), &abs_header_path)?; - copy_dir_all("./target/doc", "./site/user")?; - copy_dir_all("./target/bpfel-unknown-none/doc", "./site/bpf")?; + copy_dir_all("./target/doc".as_ref(), "./site/user".as_ref())?; + 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"); robots @@ -53,49 +58,46 @@ pub fn docs() -> Result<(), anyhow::Error> { Ok(()) } -fn build_docs(working_dir: &PathBuf, abs_header_path: &Path) -> Result<(), anyhow::Error> { - let replace = Command::new("sed") - .current_dir(working_dir) - .args(vec!["-i.bak", "s/crabby.svg/crabby_dev.svg/", "src/lib.rs"]) - .status() - .expect("failed to replace logo"); - assert!(replace.success()); +fn build_docs(working_dir: &Path, abs_header_path: &Path) -> Result<()> { + exec(Command::new("sed").current_dir(working_dir).args([ + "-i.bak", + "s/crabby.svg/crabby_dev.svg/", + "src/lib.rs", + ]))?; - let args = vec!["+nightly", "doc", "--no-deps", "--all-features"]; + exec( + Command::new("cargo") + .current_dir(working_dir) + .env( + "RUSTDOCFLAGS", + format!( + "--cfg docsrs --html-in-header {} -D warnings", + abs_header_path.to_str().unwrap() + ), + ) + .args(["+nightly", "doc", "--no-deps", "--all-features"]), + )?; - let status = Command::new("cargo") - .current_dir(working_dir) - .env( - "RUSTDOCFLAGS", - format!( - "--cfg docsrs --html-in-header {} -D warnings", - abs_header_path.to_str().unwrap() - ), - ) - .args(args) - .status() - .expect("failed to build aya docs"); - assert!(status.success()); fs::rename( working_dir.join("src/lib.rs.bak"), working_dir.join("src/lib.rs"), ) - .unwrap(); - Ok(()) + .context("Failed to rename lib.rs.bak to lib.rs") } -fn copy_dir_all, P2: AsRef>(src: P1, dst: P2) -> io::Result<()> { - fs::create_dir_all(&dst)?; +fn copy_dir_all(src: &Path, dst: &Path) -> io::Result<()> { + fs::create_dir_all(dst)?; for entry in fs::read_dir(src)? { let entry = entry?; 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() { - copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; - } else { - let new_path = dst.as_ref().join(entry.file_name()); - if !new_path.exists() { - fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; - } + copy_dir_all(src, dst)?; + } else if !dst.exists() { + fs::copy(src, dst)?; } } Ok(()) diff --git a/xtask/src/run.rs b/xtask/src/run.rs index 42ec51b9..0c7a241d 100644 --- a/xtask/src/run.rs +++ b/xtask/src/run.rs @@ -5,7 +5,7 @@ use std::{ process::{Command, Stdio}, }; -use anyhow::Context as _; +use anyhow::{Context as _, Result}; use cargo_metadata::{Artifact, CompilerMessage, Message, Target}; use clap::Parser; @@ -31,12 +31,14 @@ pub struct Options { } /// Build the project -fn build(release: bool) -> Result, anyhow::Error> { +fn build(release: bool) -> Result> { let mut cmd = Command::new("cargo"); - cmd.arg("build") - .arg("--tests") - .arg("--message-format=json") - .arg("--package=integration-test"); + cmd.args([ + "build", + "--tests", + "--message-format=json", + "--package=integration-test", + ]); if release { cmd.arg("--release"); } @@ -83,7 +85,7 @@ fn build(release: bool) -> Result, anyhow::Error> { } /// Build and run the project -pub fn run(opts: Options) -> Result<(), anyhow::Error> { +pub fn run(opts: Options) -> Result<()> { let Options { bpf_target, release, @@ -116,10 +118,8 @@ pub fn run(opts: Options) -> Result<(), anyhow::Error> { println!("{} running {cmd:?}", src_path.display()); let status = cmd - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) .status() - .context("failed to run {cmd:?}")?; + .with_context(|| format!("failed to run {cmd:?}"))?; match status.code() { Some(code) => match code { 0 => {} diff --git a/xtask/src/utils.rs b/xtask/src/utils.rs index e1e3eb2a..f2cd2471 100644 --- a/xtask/src/utils.rs +++ b/xtask/src/utils.rs @@ -1,15 +1,24 @@ -use serde_json::Value; use std::{cell::OnceCell, process::Command}; +use anyhow::{bail, Context as _, Result}; + pub fn workspace_root() -> &'static str { static mut WORKSPACE_ROOT: OnceCell = OnceCell::new(); unsafe { &mut WORKSPACE_ROOT }.get_or_init(|| { - let output = Command::new("cargo").arg("metadata").output().unwrap(); - if !output.status.success() { - 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() + let cmd = cargo_metadata::MetadataCommand::new(); + cmd.exec().unwrap().workspace_root.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"), + } +}