Repository: libbpf/libbpf-rs (language: rust)

Issue:
Feature request: looping over programs in loaded Object
I think it would be really handy for non-CO-RE programs to have something like the libbpf function and macro that allows for looping over all the available programs in a BPF object. Currently I don't think the `progs` Vec is populated when an `Object` is created, so you _have_ to search for the program(s) by name. Because you have to know the program names ahead of time, you can't just load and attach programs generically.

I would guess that you could implement an iterator such that you could do the following code
```rust
for program in bpf_object.programs {
    // do something
 }
 ```
The iterator would just call `bpf_program__next` instead of trying to grab the next from the `progs` vec.

There might be some naming issues that would need to be addressed if you assume that the `progs` vec is empty by default though.

I thought about just implementing this and making a PR, but I want to see the receptiveness (since this library is "opinionated") and if any of the actual maintainers want to do this themselves.
Populate and return list of progs and maps in Object
This PR is an attempt to allow users to iterate over maps and programs in a BPF Object without knowing names ahead of time.
Adjustments and changes are welcome. This code currently does compile and runs well.


Discussion / hints:
Here's my naive and probably not so clean solution that might work. I haven't tested it but I might be able to get to it today.
(Doing a last edit, this solution works although if you just want to cache programs and maps only after a user reads them, you may not want them in `Object::new`)

object.rs
```rust
fn object_prog_next(
    obj: *const libbpf_sys::bpf_object,
    prog: *mut libbpf_sys::bpf_program, // needed to know what the next program is
) -> Result<Option<*mut libbpf_sys::bpf_program>> {
    let next_prog_ptr = unsafe { libbpf_sys::bpf_object__program_next(prog, obj) };
    Ok(util::ptr_to_option(next_prog_ptr))
}

fn object_map_next(
    obj: *const libbpf_sys::bpf_object,
    map: *mut libbpf_sys::bpf_map, // needed to know what the next map is
) -> Result<Option<*mut libbpf_sys::bpf_map>> {
    let next_map_ptr: *mut libbpf_sys::bpf_map = unsafe { libbpf_sys::bpf_map__next(map, obj) };
    Ok(util::ptr_to_option(next_map_ptr))
}

impl Object {
    fn new(ptr: *mut libbpf_sys::bpf_object) -> Self {

        let mut progs: HashMap<String, Program> = HashMap::new();
        let mut prog: *mut libbpf_sys::bpf_program = std::ptr::null_mut();

        loop {
            // while there's another program to grab
            if let Some(next_prog_ptr) = object_prog_next(ptr, prog).unwrap() {

                // get program name
                let pname = unsafe { libbpf_sys::bpf_program__name(next_prog_ptr) };
                let err = unsafe { libbpf_sys::libbpf_get_error(pname as *const _) };
                if err != 0 {
                    std::process::exit(err as i32);
                }

                // get section title
                let title = unsafe { libbpf_sys::bpf_program__title(next_prog_ptr, false) };
                let err = unsafe { libbpf_sys::libbpf_get_error(title as *const _) };
                if err != 0 {
                    std::process::exit(err as i32);
                }

                // convert to strings
                let name = util::c_ptr_to_string(pname).unwrap();
                let section = util::c_ptr_to_string(title).unwrap();

                // push into progs
                progs.insert(name.clone(), Program::new(next_prog_ptr, name, section));
                prog = next_prog_ptr;

            } else {
                break;
            }
        }

        let mut maps: HashMap<String, Map> = HashMap::new();
        let mut map: *mut libbpf_sys::bpf_map = std::ptr::null_mut();

        loop {
            // while there's another program to grab
            if let Some(next_map_ptr) = object_map_next(ptr, map).unwrap() {

                // get map name
                let mname = unsafe { libbpf_sys::bpf_map__name(next_map_ptr) };
                let err = unsafe { libbpf_sys::libbpf_get_error(mname as *const _) };
                if err != 0 {
                    std::process::exit(err as i32);
                }

                let fd = unsafe { libbpf_sys::bpf_map__fd(next_map_ptr) };
                if fd < 0 {
                    std::process::exit(errno::errno());
                } else {
                    let def = unsafe { ptr::read(libbpf_sys::bpf_map__def(next_map_ptr)) };

                    // convert to strings
                    let name = util::c_ptr_to_string(mname).unwrap();

                    maps.insert(
                        name.clone(),
                        Map::new(fd, name, def.type_, def.key_size, def.value_size, next_map_ptr),
                    );
                    map = next_map_ptr;
                }

            } else {
                break;
            }
        }


        Object {
            ptr,
            maps,
            progs,
        }
    }
```

Seems reasonable to me (the idea; didn't look closely at code). @anakryiko WDYT? I have a vague recollection of us discussing this at one point.
Okay sorry for spamming with comments, but I think having functions do this outside `new()` would be better. Then you can keep the `HashMap`s empty if you don't want to do that work prior to a user indicating they need it. If they do indicate they need it, you can set the struct's `HashMap` and return it. This is for programs but obviously this could be done for maps too.
```rust
    pub fn get_progs(&mut self) -> Result<&mut HashMap<String, Program>> {

        // if populated already return the populated hashmap
        if !self.progs.is_empty() {
            Ok(&mut self.progs)

        } else {
            // build the hashmap and then return it
            let mut progs: HashMap<String, Program> = HashMap::new();
            let mut prog: *mut libbpf_sys::bpf_program = std::ptr::null_mut();

            loop {
                // while there's another program to grab
                if let Some(next_prog_ptr) = object_prog_next(self.ptr, prog)? {

                    // get program name
                    let pname = unsafe { libbpf_sys::bpf_program__name(next_prog_ptr) };
                    let err = unsafe { libbpf_sys::libbpf_get_error(pname as *const _) };
                    if err != 0 {
                        std::process::exit(err as i32);
                    }

                    // get section title
                    let title = unsafe { libbpf_sys::bpf_program__title(next_prog_ptr, false) };
                    let err = unsafe { libbpf_sys::libbpf_get_error(title as *const _) };
                    if err != 0 {
                        std::process::exit(err as i32);
                    }

                    // convert to strings
                    let name = util::c_ptr_to_string(pname)?;
                    let section = util::c_ptr_to_string(title)?;

                    // push into progs
                    progs.insert(name.clone(), Program::new(next_prog_ptr, name, section));
                    prog = next_prog_ptr;

                } else {
                    break;
                }
            }

            self.progs = progs;

            Ok(&mut self.progs)
        }
    }
```
Then, to loop over the programs you'd just loop over the `HashMap` like any normal one. I'm not sure if the reference to the `HashMap` would impact the attaching of programs though.
This is some actual code I've tested and how I think it would be used.
```rust
        for (_prog_name, prog) in object.get_progs()? {
            dbg!(_prog_name);
            program_links.push(prog.attach()?);
        }
```
Having ability to iterate BpfObject's maps and progs are obviously a good feature. Don't remember in what context we were discussing this, though, and if there are some Rust-specific gotchas there?
I don't think there are any gotchas. @rtkaratekid feel free to put up a PR -- will be easier to discuss the code there
I'll get one up today or tomorrow
[PR submitted](https://github.com/libbpf/libbpf-rs/pull/79). I've got another PR I might submit soon regarding being able to define the `ctx` that is passed to the perf buffer opts when it's built. Makes it a bit easier to give context to callback functions.
I'm not sure why `rust_fmt` failed.
Here's what I had in mind: http://ix.io/3mwu

It's not the most beautiful macro ever but it generates the right code so whatever :p . Should probably add more tests too

Fix patch:
diff --git a/libbpf-cargo/src/gen.rs b/libbpf-cargo/src/gen.rs
index b7b29bfd..cc6e26b2 100644
--- a/libbpf-cargo/src/gen.rs
+++ b/libbpf-cargo/src/gen.rs
@@ -662,7 +662,7 @@ fn gen_skel_contents(_debug: bool, raw_obj_name: &str, obj_file_path: &Path) ->
                     return Err(libbpf_rs::Error::System(-ret));
                 }}
 
-                let obj = unsafe {{ libbpf_rs::OpenObject::from_ptr(skel_config.object_ptr()) }};
+                let obj = unsafe {{ libbpf_rs::OpenObject::from_ptr(skel_config.object_ptr())? }};
 
                 Ok(Open{name}Skel {{
                     obj,
@@ -693,7 +693,7 @@ fn gen_skel_contents(_debug: bool, raw_obj_name: &str, obj_file_path: &Path) ->
                     return Err(libbpf_rs::Error::System(-ret));
                 }}
 
-                let obj = unsafe {{ libbpf_rs::Object::from_ptr(self.obj.take_ptr()) }};
+                let obj = unsafe {{ libbpf_rs::Object::from_ptr(self.obj.take_ptr())? }};
 
                 Ok({name}Skel {{
                     obj,
diff --git a/libbpf-rs/src/object.rs b/libbpf-rs/src/object.rs
index af389a32..21f663b4 100644
--- a/libbpf-rs/src/object.rs
+++ b/libbpf-rs/src/object.rs
@@ -6,8 +6,6 @@ use std::os::raw::c_char;
 use std::path::Path;
 use std::ptr;
 
-use nix::errno;
-
 use crate::util;
 use crate::*;
 
@@ -98,7 +96,7 @@ impl ObjectBuilder {
             return Err(Error::System(err as i32));
         }
 
-        Ok(OpenObject::new(obj))
+        OpenObject::new(obj)
     }
 
     pub fn open_memory<T: AsRef<str>>(&mut self, name: T, mem: &[u8]) -> Result<OpenObject> {
@@ -126,7 +124,7 @@ impl ObjectBuilder {
             return Err(Error::System(err as i32));
         }
 
-        Ok(OpenObject::new(obj))
+        OpenObject::new(obj)
     }
 }
 
@@ -139,24 +137,6 @@ impl Default for ObjectBuilder {
     }
 }
 
-fn find_map_in_object(
-    obj: *const libbpf_sys::bpf_object,
-    name: &str,
-) -> Result<Option<*mut libbpf_sys::bpf_map>> {
-    let c_name = util::str_to_cstring(name)?;
-    let ptr = unsafe { libbpf_sys::bpf_object__find_map_by_name(obj, c_name.as_ptr()) };
-    Ok(util::ptr_to_option(ptr))
-}
-
-fn find_prog_in_object(
-    obj: *const libbpf_sys::bpf_object,
-    name: &str,
-) -> Result<Option<*mut libbpf_sys::bpf_program>> {
-    let c_name = util::str_to_cstring(name)?;
-    let ptr = unsafe { libbpf_sys::bpf_object__find_program_by_name(obj, c_name.as_ptr()) };
-    Ok(util::ptr_to_option(ptr))
-}
-
 /// Represents an opened (but not yet loaded) BPF object file.
 ///
 /// Use this object to access [`OpenMap`]s and [`OpenProgram`]s.
@@ -167,12 +147,53 @@ pub struct OpenObject {
 }
 
 impl OpenObject {
-    fn new(ptr: *mut libbpf_sys::bpf_object) -> Self {
-        OpenObject {
+    fn new(ptr: *mut libbpf_sys::bpf_object) -> Result<Self> {
+        let mut obj = OpenObject {
             ptr,
             maps: HashMap::new(),
             progs: HashMap::new(),
+        };
+
+        // Populate obj.maps
+        let mut map: *mut libbpf_sys::bpf_map = std::ptr::null_mut();
+        loop {
+            // Get the pointer to the next BPF map
+            let next_ptr = unsafe { libbpf_sys::bpf_map__next(map, obj.ptr) };
+            if next_ptr.is_null() {
+                break;
+            }
+
+            // Get the map name
+            // bpf_map__name can return null but only if it's passed a null.
+            // We already know next_ptr is not null.
+            let name = unsafe { libbpf_sys::bpf_map__name(next_ptr) };
+            let name = util::c_ptr_to_string(name)?;
+
+            // Add the map to the hashmap
+            obj.maps.insert(name, OpenMap::new(next_ptr));
+            map = next_ptr;
+        }
+
+        // Populate obj.progs
+        let mut prog: *mut libbpf_sys::bpf_program = std::ptr::null_mut();
+        loop {
+            // Get the pointer to the next BPF program
+            let next_ptr = unsafe { libbpf_sys::bpf_program__next(prog, obj.ptr) };
+            if next_ptr.is_null() {
+                break;
+            }
+
+            // Get the program name.
+            // bpf_program__name never returns NULL, so no need to check the pointer.
+            let name = unsafe { libbpf_sys::bpf_program__name(next_ptr) };
+            let name = util::c_ptr_to_string(name)?;
+
+            // Add the program to the hashmap
+            obj.progs.insert(name, OpenProgram::new(next_ptr));
+            prog = next_ptr;
         }
+
+        Ok(obj)
     }
 
     /// Takes ownership from pointer.
@@ -183,7 +204,7 @@ impl OpenObject {
     /// undefined.
     ///
     /// It is not safe to manipulate `ptr` after this operation.
-    pub unsafe fn from_ptr(ptr: *mut libbpf_sys::bpf_object) -> Self {
+    pub unsafe fn from_ptr(ptr: *mut libbpf_sys::bpf_object) -> Result<Self> {
         Self::new(ptr)
     }
 
@@ -208,38 +229,36 @@ impl OpenObject {
         }
     }
 
-    pub fn map<T: AsRef<str>>(&mut self, name: T) -> Result<Option<&mut OpenMap>> {
-        if self.maps.contains_key(name.as_ref()) {
-            Ok(self.maps.get_mut(name.as_ref()))
-        } else if let Some(ptr) = find_map_in_object(self.ptr, name.as_ref())? {
-            self.maps
-                .insert(name.as_ref().to_owned(), OpenMap::new(ptr));
-            Ok(self.maps.get_mut(name.as_ref()))
-        } else {
-            Ok(None)
-        }
+    /// Get a mutable reference to `OpenMap` with the name `name`, if one exists.
+    pub fn map<T: AsRef<str>>(&mut self, name: T) -> Option<&mut OpenMap> {
+        self.maps.get_mut(name.as_ref())
     }
 
     /// Same as [`OpenObject::map`] except will panic if `Err` or `None` is encountered.
     pub fn map_unwrap<T: AsRef<str>>(&mut self, name: T) -> &mut OpenMap {
-        self.map(name).unwrap().unwrap()
+        self.map(name).unwrap()
     }
 
-    pub fn prog<T: AsRef<str>>(&mut self, name: T) -> Result<Option<&mut OpenProgram>> {
-        if self.progs.contains_key(name.as_ref()) {
-            Ok(self.progs.get_mut(name.as_ref()))
-        } else if let Some(ptr) = find_prog_in_object(self.ptr, name.as_ref())? {
-            let owned_name = name.as_ref().to_owned();
-            self.progs.insert(owned_name, OpenProgram::new(ptr));
-            Ok(self.progs.get_mut(name.as_ref()))
-        } else {
-            Ok(None)
-        }
+    /// Get an iterator over references to all `OpenMap`s.
+    /// Note that this will include automatically generated .data, .rodata, .bss, and
+    /// .kconfig maps.
+    pub fn maps_iter(&mut self) -> impl Iterator<Item = &mut OpenMap> {
+        self.maps.values_mut()
+    }
+
+    /// Get a mutable reference to `OpenProgram` with the name `name`, if one exists.
+    pub fn prog<T: AsRef<str>>(&mut self, name: T) -> Option<&mut OpenProgram> {
+        self.progs.get_mut(name.as_ref())
     }
 
     /// Same as [`OpenObject::prog`] except will panic if `Err` or `None` is encountered.
     pub fn prog_unwrap<T: AsRef<str>>(&mut self, name: T) -> &mut OpenProgram {
-        self.prog(name).unwrap().unwrap()
+        self.prog(name).unwrap()
+    }
+
+    /// Get an iterator over references to all `OpenProgram`s.
+    pub fn progs_iter(&mut self) -> impl Iterator<Item = &mut OpenProgram> {
+        self.progs.values_mut()
     }
 
     /// Load the maps and programs contained in this BPF object into the system.
@@ -250,7 +269,7 @@ impl OpenObject {
             return Err(Error::System(-ret));
         }
 
-        let obj = Object::new(self.ptr);
+        let obj = Object::new(self.ptr)?;
 
         // Prevent object from being closed once `self` is dropped
         self.ptr = ptr::null_mut();
@@ -284,12 +303,79 @@ pub struct Object {
 }
 
 impl Object {
-    fn new(ptr: *mut libbpf_sys::bpf_object) -> Self {
-        Object {
+    fn new(ptr: *mut libbpf_sys::bpf_object) -> Result<Self> {
+        let mut obj = Object {
             ptr,
             maps: HashMap::new(),
             progs: HashMap::new(),
+        };
+
+        // Populate obj.maps
+        let mut map: *mut libbpf_sys::bpf_map = std::ptr::null_mut();
+        loop {
+            // Get the pointer to the next BPF map
+            let next_ptr = unsafe { libbpf_sys::bpf_map__next(map, obj.ptr) };
+            if next_ptr.is_null() {
+                break;
+            }
+
+            // Get the map name
+            // bpf_map__name can return null but only if it's passed a null.
+            // We already know next_ptr is not null.
+            let name = unsafe { libbpf_sys::bpf_map__name(next_ptr) };
+            let name = util::c_ptr_to_string(name)?;
+
+            // Get the map def
+            // bpf_map__def can return null but only if it's passed a null.
+            // We already know next_ptr is not null.
+            let def = unsafe { ptr::read(libbpf_sys::bpf_map__def(next_ptr)) };
+
+            // Get the map fd
+            let fd = unsafe { libbpf_sys::bpf_map__fd(next_ptr) };
+            if fd < 0 {
+                return Err(Error::System(-fd));
+            }
+
+            // Add the map to the hashmap
+            obj.maps.insert(
+                name.clone(),
+                Map::new(fd, name, def.type_, def.key_size, def.value_size, next_ptr),
+            );
+            map = next_ptr;
         }
+
+        // Populate obj.progs
+        let mut prog: *mut libbpf_sys::bpf_program = std::ptr::null_mut();
+        loop {
+            // Get the pointer to the next BPF program
+            let next_ptr = unsafe { libbpf_sys::bpf_program__next(prog, obj.ptr) };
+            if next_ptr.is_null() {
+                break;
+            }
+
+            // Get the program name
+            // bpf_program__name never returns NULL, so no need to check the pointer.
+            let name = unsafe { libbpf_sys::bpf_program__name(next_ptr) };
+            let name = util::c_ptr_to_string(name)?;
+
+            // Get the program section
+            // bpf_program__section_name never returns NULL, so no need to check the pointer.
+            let section = unsafe { libbpf_sys::bpf_program__section_name(next_ptr) };
+            let section = util::c_ptr_to_string(section)?;
+
+            // Get the program fd
+            let fd = unsafe { libbpf_sys::bpf_program__fd(next_ptr) };
+            if fd < 0 {
+                return Err(Error::System(-fd));
+            }
+
+            // Add the program to the hashmap
+            obj.progs
+                .insert(name.clone(), Program::new(next_ptr, name, section));
+            prog = next_ptr;
+        }
+
+        Ok(obj)
     }
 
     /// Takes ownership from pointer.
@@ -300,65 +386,40 @@ impl Object {
     /// undefined.
     ///
     /// It is not safe to manipulate `ptr` after this operation.
-    pub unsafe fn from_ptr(ptr: *mut libbpf_sys::bpf_object) -> Self {
+    pub unsafe fn from_ptr(ptr: *mut libbpf_sys::bpf_object) -> Result<Self> {
         Self::new(ptr)
     }
 
-    pub fn map<T: AsRef<str>>(&mut self, name: T) -> Result<Option<&mut Map>> {
-        if self.maps.contains_key(name.as_ref()) {
-            Ok(self.maps.get_mut(name.as_ref()))
-        } else if let Some(ptr) = find_map_in_object(self.ptr, name.as_ref())? {
-            let owned_name = name.as_ref().to_owned();
-            let fd = unsafe { libbpf_sys::bpf_map__fd(ptr) };
-            if fd < 0 {
-                Err(Error::System(errno::errno()))
-            } else {
-                // bpf_map__def can return null but only if it's passed a null. Object::map
-                // already error checks that condition for us.
-                let def = unsafe { ptr::read(libbpf_sys::bpf_map__def(ptr)) };
-
-                self.maps.insert(
-                    owned_name.clone(),
-                    Map::new(fd, owned_name, def.type_, def.key_size, def.value_size, ptr),
-                );
-
-                Ok(self.maps.get_mut(name.as_ref()))
-            }
-        } else {
-            Ok(None)
-        }
+    /// Get a mutable reference to `Map` with the name `name`, if one exists.
+    pub fn map<T: AsRef<str>>(&mut self, name: T) -> Option<&mut Map> {
+        self.maps.get_mut(name.as_ref())
     }
 
-    /// Same as [`Object::map`] except will panic if `Err` or `None` is encountered.
-    pub fn map_unwrap<T: AsRef<str>>(&mut self, name: T) -> &mut Map {
-        self.map(name).unwrap().unwrap()
+    /// Get an iterator over references to all `Map`s.
+    /// Note that this will include automatically generated .data, .rodata, .bss, and
+    /// .kconfig maps. You may wish to filter these out depending on your use case.
+    pub fn maps_iter(&mut self) -> impl Iterator<Item = &mut Map> {
+        return self.maps.values_mut().into_iter();
     }
 
-    pub fn prog<T: AsRef<str>>(&mut self, name: T) -> Result<Option<&mut Program>> {
-        if self.progs.contains_key(name.as_ref()) {
-            Ok(self.progs.get_mut(name.as_ref()))
-        } else if let Some(ptr) = find_prog_in_object(self.ptr, name.as_ref())? {
-            let owned_name = name.as_ref().to_owned();
-
-            let title = unsafe { libbpf_sys::bpf_program__title(ptr, false) };
-            let err = unsafe { libbpf_sys::libbpf_get_error(title as *const _) };
-            if err != 0 {
-                return Err(Error::System(err as i32));
-            }
-            let section = util::c_ptr_to_string(title)?;
-
-            self.progs
-                .insert(owned_name.clone(), Program::new(ptr, owned_name, section));
+    /// Same as [`Object::map`] except will panic if `None` is encountered.
+    pub fn map_unwrap<T: AsRef<str>>(&mut self, name: T) -> &mut Map {
+        self.map(name).unwrap()
+    }
 
-            Ok(self.progs.get_mut(name.as_ref()))
-        } else {
-            Ok(None)
-        }
+    /// Get a mutable reference to `Program` with the name `name`, if one exists.
+    pub fn prog<T: AsRef<str>>(&mut self, name: T) -> Option<&mut Program> {
+        self.progs.get_mut(name.as_ref())
     }
 
-    /// Same as [`Object::prog`] except will panic if `Err` or `None` is encountered.
+    /// Same as [`Object::prog`] except will panic if `None` is encountered.
     pub fn prog_unwrap<T: AsRef<str>>(&mut self, name: T) -> &mut Program {
-        self.prog(name).unwrap().unwrap()
+        self.prog(name).unwrap()
+    }
+
+    /// Get an iterator over references to all `Program`s.
+    pub fn progs_iter(&mut self) -> impl Iterator<Item = &mut Program> {
+        self.progs.values_mut()
     }
 }
 
diff --git a/libbpf-rs/src/util.rs b/libbpf-rs/src/util.rs
index de03db3e..df375475 100644
--- a/libbpf-rs/src/util.rs
+++ b/libbpf-rs/src/util.rs
@@ -27,11 +27,3 @@ pub fn c_ptr_to_string(p: *const c_char) -> Result<String> {
         .map_err(|e| Error::Internal(e.to_string()))?
         .to_owned())
 }
-
-pub fn ptr_to_option<T>(p: *mut T) -> Option<*mut T> {
-    if p.is_null() {
-        None
-    } else {
-        Some(p)
-    }
-}


Test patch:
diff --git a/libbpf-rs/tests/test.rs b/libbpf-rs/tests/test.rs
index 387bcaf0..4498cd6d 100644
--- a/libbpf-rs/tests/test.rs
+++ b/libbpf-rs/tests/test.rs
@@ -85,13 +85,21 @@ fn test_object_maps() {
     bump_rlimit_mlock();
 
     let mut obj = get_test_object("runqslower.bpf.o");
-    obj.map("start")
-        .expect("error finding map")
-        .expect("failed to find map");
-    obj.map("events")
-        .expect("error finding map")
-        .expect("failed to find map");
-    assert!(obj.map("asdf").expect("error finding map").is_none());
+    obj.map("start").expect("failed to find map");
+    obj.map("events").expect("failed to find map");
+    assert!(obj.map("asdf").is_none());
+}
+
+#[test]
+fn test_object_maps_iter() {
+    bump_rlimit_mlock();
+
+    let mut obj = get_test_object("runqslower.bpf.o");
+    for map in obj.maps_iter() {
+        eprintln!("{}", map.name());
+    }
+    // This will include .rodata, so our expected count is 3, not 2
+    assert!(obj.maps_iter().count() == 3);
 }
 
 #[test]
@@ -99,10 +107,7 @@ fn test_object_map_key_value_size() {
     bump_rlimit_mlock();
 
     let mut obj = get_test_object("runqslower.bpf.o");
-    let start = obj
-        .map("start")
-        .expect("error finding map")
-        .expect("failed to find map");
+    let start = obj.map("start").expect("failed to find map");
 
     assert!(start.lookup(&[1, 2, 3, 4, 5], MapFlags::empty()).is_err());
     assert!(start.delete(&[1]).is_err());
@@ -117,10 +122,7 @@ fn test_object_map_empty_lookup() {
     bump_rlimit_mlock();
 
     let mut obj = get_test_object("runqslower.bpf.o");
-    let start = obj
-        .map("start")
-        .expect("error finding map")
-        .expect("failed to find map");
+    let start = obj.map("start").expect("failed to find map");
 
     assert!(start
         .lookup(&[1, 2, 3, 4], MapFlags::empty())
@@ -133,10 +135,7 @@ fn test_object_map_mutation() {
     bump_rlimit_mlock();
 
     let mut obj = get_test_object("runqslower.bpf.o");
-    let start = obj
-        .map("start")
-        .expect("error finding map")
-        .expect("failed to find map");
+    let start = obj.map("start").expect("failed to find map");
 
     start
         .update(&[1, 2, 3, 4], &[1, 2, 3, 4, 5, 6, 7, 8], MapFlags::empty())
@@ -161,10 +160,7 @@ fn test_object_map_lookup_flags() {
     bump_rlimit_mlock();
 
     let mut obj = get_test_object("runqslower.bpf.o");
-    let start = obj
-        .map("start")
-        .expect("error finding map")
-        .expect("failed to find map");
+    let start = obj.map("start").expect("failed to find map");
 
     start
         .update(&[1, 2, 3, 4], &[1, 2, 3, 4, 5, 6, 7, 8], MapFlags::NO_EXIST)
@@ -180,10 +176,7 @@ fn test_object_map_key_iter() {
 
     let mut obj = get_test_object("runqslower.bpf.o");
 
-    let start = obj
-        .map("start")
-        .expect("error finding map")
-        .expect("failed to find map");
+    let start = obj.map("start").expect("failed to find map");
 
     let key1 = vec![1, 2, 3, 4];
     let key2 = vec![1, 2, 3, 5];
@@ -214,10 +207,7 @@ fn test_object_map_key_iter_empty() {
     bump_rlimit_mlock();
 
     let mut obj = get_test_object("runqslower.bpf.o");
-    let start = obj
-        .map("start")
-        .expect("error finding map")
-        .expect("failed to find map");
+    let start = obj.map("start").expect("failed to find map");
 
     let mut count = 0;
     for _ in start.keys() {
@@ -231,10 +221,7 @@ fn test_object_map_pin() {
     bump_rlimit_mlock();
 
     let mut obj = get_test_object("runqslower.bpf.o");
-    let map = obj
-        .map("start")
-        .expect("error finding map")
-        .expect("failed to find map");
+    let map = obj.map("start").expect("failed to find map");
 
     let path = "/sys/fs/bpf/mymap";
 
@@ -255,15 +242,20 @@ fn test_object_programs() {
 
     let mut obj = get_test_object("runqslower.bpf.o");
     obj.prog("handle__sched_wakeup")
-        .expect("error finding program")
         .expect("failed to find program");
     obj.prog("handle__sched_wakeup_new")
-        .expect("error finding program")
         .expect("failed to find program");
     obj.prog("handle__sched_switch")
-        .expect("error finding program")
         .expect("failed to find program");
-    assert!(obj.prog("asdf").expect("error finding program").is_none());
+    assert!(obj.prog("asdf").is_none());
+}
+
+#[test]
+fn test_object_programs_iter_mut() {
+    bump_rlimit_mlock();
+
+    let mut obj = get_test_object("runqslower.bpf.o");
+    assert!(obj.progs_iter().count() == 3);
 }
 
 #[test]
@@ -273,7 +265,6 @@ fn test_object_program_pin() {
     let mut obj = get_test_object("runqslower.bpf.o");
     let prog = obj
         .prog("handle__sched_wakeup")
-        .expect("error finding program")
         .expect("failed to find program");
 
     let path = "/sys/fs/bpf/myprog";
@@ -303,7 +294,6 @@ fn test_object_link_pin() {
     let mut obj = get_test_object("runqslower.bpf.o");
     let prog = obj
         .prog("handle__sched_wakeup")
-        .expect("error finding program")
         .expect("failed to find program");
     let mut link = prog.attach().expect("failed to attach prog");
 
@@ -338,10 +328,7 @@ fn test_object_reuse_pined_map() {
     // Pin a map
     {
         let mut obj = get_test_object("runqslower.bpf.o");
-        let map = obj
-            .map("start")
-            .expect("error finding map")
-            .expect("failed to find map");
+        let map = obj.map("start").expect("failed to find map");
 
         map.update(&key, &val, MapFlags::empty())
             .expect("failed to write");
@@ -362,18 +349,12 @@ fn test_object_reuse_pined_map() {
     builder.debug(true);
     let mut open_obj = builder.open_file(obj_path).expect("failed to open object");
 
-    let start = open_obj
-        .map("start")
-        .expect("error finding map")
-        .expect("failed to find map");
+    let start = open_obj.map("start").expect("failed to find map");
     assert!(start.reuse_pinned_map("/asdf").is_err());
     start.reuse_pinned_map(path).expect("failed to reuse map");
 
     let mut obj = open_obj.load().expect("Failed to load object");
-    let reused_map = obj
-        .map("start")
-        .expect("error finding map")
-        .expect("failed to find map");
+    let reused_map = obj.map("start").expect("failed to find map");
 
     let found_val = reused_map
         .lookup(&key, MapFlags::empty())
@@ -393,7 +374,6 @@ fn test_object_ringbuf() {
     let mut obj = get_test_object("ringbuf.bpf.o");
     let prog = obj
         .prog("handle__sys_enter_getpid")
-        .expect("error finding program")
         .expect("failed to find program");
     let _link = prog.attach().expect("failed to attach prog");
 
@@ -434,18 +414,12 @@ fn test_object_ringbuf() {
     let mut builder = libbpf_rs::RingBufferBuilder::new();
 
     // Add a first map and callback
-    let map1 = obj
-        .map("ringbuf1")
-        .expect("Error getting ringbuf1 map")
-        .expect("Failed to get ringbuf1 map");
+    let map1 = obj.map("ringbuf1").expect("Failed to get ringbuf1 map");
 
     builder.add(map1, callback1).expect("Failed to add ringbuf");
 
     // Add a second map and callback
-    let map2 = obj
-        .map("ringbuf2")
-        .expect("Error getting ringbuf2 map")
-        .expect("Failed to get ringbuf2 map");
+    let map2 = obj.map("ringbuf2").expect("Failed to get ringbuf2 map");
 
     builder.add(map2, callback2).expect("Failed to add ringbuf");
 
@@ -484,7 +458,6 @@ fn test_object_ringbuf_closure() {
     let mut obj = get_test_object("ringbuf.bpf.o");
     let prog = obj
         .prog("handle__sys_enter_getpid")
-        .expect("error finding program")
         .expect("failed to find program");
     let _link = prog.attach().expect("failed to attach prog");
 
@@ -520,18 +493,12 @@ fn test_object_ringbuf_closure() {
     let mut builder = libbpf_rs::RingBufferBuilder::new();
 
     // Add a first map and callback
-    let map1 = obj
-        .map("ringbuf1")
-        .expect("Error getting ringbuf1 map")
-        .expect("Failed to get ringbuf1 map");
+    let map1 = obj.map("ringbuf1").expect("Failed to get ringbuf1 map");
 
     builder.add(map1, callback1).expect("Failed to add ringbuf");
 
     // Add a second map and callback
-    let map2 = obj
-        .map("ringbuf2")
-        .expect("Error getting ringbuf2 map")
-        .expect("Failed to get ringbuf2 map");
+    let map2 = obj.map("ringbuf2").expect("Failed to get ringbuf2 map");
 
     builder.add(map2, callback2).expect("Failed to add ringbuf");
 
@@ -555,10 +522,7 @@ fn test_object_task_iter() {
     bump_rlimit_mlock();
 
     let mut obj = get_test_object("taskiter.bpf.o");
-    let prog = obj
-        .prog("dump_pid")
-        .expect("Error finding program")
-        .expect("Failed to find program");
+    let prog = obj.prog("dump_pid").expect("Failed to find program");
     let link = prog.attach().expect("Failed to attach prog");
     let mut iter = Iter::new(&link).expect("Failed to create iterator");