Bladeren bron

Merge pull request #712 from aya-rs/loaded-links

integration-test: remove bpftool dependency
Tamir Duberstein 1 jaar geleden
bovenliggende
commit
368ddf10c4
44 gewijzigde bestanden met toevoegingen van 521 en 591 verwijderingen
  1. 10 6
      aya/src/bpf.rs
  2. 5 6
      aya/src/maps/array/array.rs
  3. 3 3
      aya/src/maps/array/per_cpu_array.rs
  4. 8 5
      aya/src/maps/array/program_array.rs
  5. 7 9
      aya/src/maps/bloom_filter.rs
  6. 19 12
      aya/src/maps/hash_map/hash_map.rs
  7. 10 9
      aya/src/maps/hash_map/mod.rs
  8. 5 6
      aya/src/maps/hash_map/per_cpu_hash_map.rs
  9. 14 13
      aya/src/maps/lpm_trie.rs
  10. 21 34
      aya/src/maps/mod.rs
  11. 5 7
      aya/src/maps/queue.rs
  12. 4 6
      aya/src/maps/sock/sock_hash.rs
  13. 8 5
      aya/src/maps/sock/sock_map.rs
  14. 3 3
      aya/src/maps/stack.rs
  15. 5 7
      aya/src/maps/stack_trace.rs
  16. 4 9
      aya/src/pin.rs
  17. 3 3
      aya/src/programs/cgroup_device.rs
  18. 3 3
      aya/src/programs/cgroup_skb.rs
  19. 3 3
      aya/src/programs/cgroup_sock.rs
  20. 3 3
      aya/src/programs/cgroup_sock_addr.rs
  21. 3 3
      aya/src/programs/cgroup_sockopt.rs
  22. 3 3
      aya/src/programs/cgroup_sysctl.rs
  23. 12 18
      aya/src/programs/extension.rs
  24. 1 6
      aya/src/programs/kprobe.rs
  25. 9 17
      aya/src/programs/links.rs
  26. 9 14
      aya/src/programs/lirc_mode2.rs
  27. 62 103
      aya/src/programs/mod.rs
  28. 4 4
      aya/src/programs/perf_attach.rs
  29. 3 8
      aya/src/programs/perf_event.rs
  30. 6 7
      aya/src/programs/probe.rs
  31. 2 2
      aya/src/programs/sk_lookup.rs
  32. 2 2
      aya/src/programs/sk_msg.rs
  33. 4 6
      aya/src/programs/sk_skb.rs
  34. 2 2
      aya/src/programs/sock_ops.rs
  35. 5 11
      aya/src/programs/trace_point.rs
  36. 1 6
      aya/src/programs/uprobe.rs
  37. 4 5
      aya/src/programs/utils.rs
  38. 4 9
      aya/src/programs/xdp.rs
  39. 121 65
      aya/src/sys/bpf.rs
  40. 13 3
      aya/src/sys/mod.rs
  41. 0 3
      test/README.md
  42. 102 96
      test/integration-test/src/tests/load.rs
  43. 0 2
      test/run.sh
  44. 6 44
      xtask/public-api/aya.txt

+ 10 - 6
aya/src/bpf.rs

@@ -42,6 +42,7 @@ use crate::{
         is_btf_float_supported, is_btf_func_global_supported, is_btf_func_supported,
         is_btf_supported, is_btf_type_tag_supported, is_perf_link_supported,
         is_probe_read_kernel_supported, is_prog_name_supported, retry_with_verifier_logs,
+        SyscallError,
     },
     util::{bytes_of, bytes_of_slice, possible_cpus, POSSIBLE_CPUS},
 };
@@ -505,16 +506,19 @@ impl<'a> BpfLoader<'a> {
             };
             if !map.obj.data().is_empty() && map.obj.section_kind() != BpfSectionKind::Bss {
                 bpf_map_update_elem_ptr(fd, &0 as *const _, map.obj.data_mut().as_mut_ptr(), 0)
-                    .map_err(|(_, io_error)| MapError::SyscallError {
+                    .map_err(|(_, io_error)| SyscallError {
                         call: "bpf_map_update_elem",
                         io_error,
-                    })?;
+                    })
+                    .map_err(MapError::from)?;
             }
             if map.obj.section_kind() == BpfSectionKind::Rodata {
-                bpf_map_freeze(fd).map_err(|(_, io_error)| MapError::SyscallError {
-                    call: "bpf_map_freeze",
-                    io_error,
-                })?;
+                bpf_map_freeze(fd)
+                    .map_err(|(_, io_error)| SyscallError {
+                        call: "bpf_map_freeze",
+                        io_error,
+                    })
+                    .map_err(MapError::from)?;
             }
             maps.insert(name, map);
         }

+ 5 - 6
aya/src/maps/array/array.rs

@@ -5,7 +5,7 @@ use std::{
 
 use crate::{
     maps::{check_bounds, check_kv_size, IterableMap, MapData, MapError},
-    sys::{bpf_map_lookup_elem, bpf_map_update_elem},
+    sys::{bpf_map_lookup_elem, bpf_map_update_elem, SyscallError},
     Pod,
 };
 
@@ -65,12 +65,11 @@ impl<T: Borrow<MapData>, V: Pod> Array<T, V> {
         check_bounds(data, *index)?;
         let fd = data.fd_or_err()?;
 
-        let value = bpf_map_lookup_elem(fd, index, flags).map_err(|(_, io_error)| {
-            MapError::SyscallError {
+        let value =
+            bpf_map_lookup_elem(fd, index, flags).map_err(|(_, io_error)| SyscallError {
                 call: "bpf_map_lookup_elem",
                 io_error,
-            }
-        })?;
+            })?;
         value.ok_or(MapError::KeyNotFound)
     }
 
@@ -93,7 +92,7 @@ impl<T: BorrowMut<MapData>, V: Pod> Array<T, V> {
         check_bounds(data, index)?;
         let fd = data.fd_or_err()?;
         bpf_map_update_elem(fd, Some(&index), value.borrow(), flags).map_err(|(_, io_error)| {
-            MapError::SyscallError {
+            SyscallError {
                 call: "bpf_map_update_elem",
                 io_error,
             }

+ 3 - 3
aya/src/maps/array/per_cpu_array.rs

@@ -5,7 +5,7 @@ use std::{
 
 use crate::{
     maps::{check_bounds, check_kv_size, IterableMap, MapData, MapError, PerCpuValues},
-    sys::{bpf_map_lookup_elem_per_cpu, bpf_map_update_elem_per_cpu},
+    sys::{bpf_map_lookup_elem_per_cpu, bpf_map_update_elem_per_cpu, SyscallError},
     Pod,
 };
 
@@ -85,7 +85,7 @@ impl<T: Borrow<MapData>, V: Pod> PerCpuArray<T, V> {
         let fd = data.fd_or_err()?;
 
         let value = bpf_map_lookup_elem_per_cpu(fd, index, flags).map_err(|(_, io_error)| {
-            MapError::SyscallError {
+            SyscallError {
                 call: "bpf_map_lookup_elem",
                 io_error,
             }
@@ -113,7 +113,7 @@ impl<T: BorrowMut<MapData>, V: Pod> PerCpuArray<T, V> {
         let fd = data.fd_or_err()?;
 
         bpf_map_update_elem_per_cpu(fd, &index, &values, flags).map_err(|(_, io_error)| {
-            MapError::SyscallError {
+            SyscallError {
                 call: "bpf_map_update_elem",
                 io_error,
             }

+ 8 - 5
aya/src/maps/array/program_array.rs

@@ -8,7 +8,7 @@ use std::{
 use crate::{
     maps::{check_bounds, check_kv_size, MapData, MapError, MapKeys},
     programs::ProgramFd,
-    sys::{bpf_map_delete_elem, bpf_map_update_elem},
+    sys::{bpf_map_delete_elem, bpf_map_update_elem, SyscallError},
 };
 
 /// An array of eBPF program file descriptors used as a jump table.
@@ -80,7 +80,7 @@ impl<T: BorrowMut<MapData>> ProgramArray<T> {
         let prog_fd = program.as_raw_fd();
 
         bpf_map_update_elem(fd, Some(&index), &prog_fd, flags).map_err(|(_, io_error)| {
-            MapError::SyscallError {
+            SyscallError {
                 call: "bpf_map_update_elem",
                 io_error,
             }
@@ -99,9 +99,12 @@ impl<T: BorrowMut<MapData>> ProgramArray<T> {
 
         bpf_map_delete_elem(fd, index)
             .map(|_| ())
-            .map_err(|(_, io_error)| MapError::SyscallError {
-                call: "bpf_map_delete_elem",
-                io_error,
+            .map_err(|(_, io_error)| {
+                SyscallError {
+                    call: "bpf_map_delete_elem",
+                    io_error,
+                }
+                .into()
             })
     }
 }

+ 7 - 9
aya/src/maps/bloom_filter.rs

@@ -3,7 +3,7 @@ use std::{borrow::Borrow, marker::PhantomData};
 
 use crate::{
     maps::{check_v_size, MapData, MapError},
-    sys::{bpf_map_lookup_elem_ptr, bpf_map_push_elem},
+    sys::{bpf_map_lookup_elem_ptr, bpf_map_push_elem, SyscallError},
     Pod,
 };
 
@@ -54,7 +54,7 @@ impl<T: Borrow<MapData>, V: Pod> BloomFilter<T, V> {
         let fd = self.inner.borrow().fd_or_err()?;
 
         bpf_map_lookup_elem_ptr::<u32, _>(fd, None, &mut value, flags)
-            .map_err(|(_, io_error)| MapError::SyscallError {
+            .map_err(|(_, io_error)| SyscallError {
                 call: "bpf_map_lookup_elem",
                 io_error,
             })?
@@ -65,11 +65,9 @@ impl<T: Borrow<MapData>, V: Pod> BloomFilter<T, V> {
     /// Inserts a value into the map.
     pub fn insert(&self, value: impl Borrow<V>, flags: u64) -> Result<(), MapError> {
         let fd = self.inner.borrow().fd_or_err()?;
-        bpf_map_push_elem(fd, value.borrow(), flags).map_err(|(_, io_error)| {
-            MapError::SyscallError {
-                call: "bpf_map_push_elem",
-                io_error,
-            }
+        bpf_map_push_elem(fd, value.borrow(), flags).map_err(|(_, io_error)| SyscallError {
+            call: "bpf_map_push_elem",
+            io_error,
         })?;
         Ok(())
     }
@@ -212,7 +210,7 @@ mod tests {
 
         assert_matches!(
             bloom_filter.insert(1, 0),
-            Err(MapError::SyscallError { call, io_error }) if call == "bpf_map_push_elem" && io_error.raw_os_error() == Some(EFAULT)
+            Err(MapError::SyscallError(SyscallError { call: "bpf_map_push_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT)
         );
     }
 
@@ -250,7 +248,7 @@ mod tests {
 
         assert_matches!(
             bloom_filter.contains(&1, 0),
-            Err(MapError::SyscallError { call, io_error }) if call == "bpf_map_lookup_elem" && io_error.raw_os_error() == Some(EFAULT)
+            Err(MapError::SyscallError(SyscallError { call: "bpf_map_lookup_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT)
         );
     }
 

+ 19 - 12
aya/src/maps/hash_map/hash_map.rs

@@ -5,7 +5,7 @@ use std::{
 
 use crate::{
     maps::{check_kv_size, hash_map, IterableMap, MapData, MapError, MapIter, MapKeys},
-    sys::bpf_map_lookup_elem,
+    sys::{bpf_map_lookup_elem, SyscallError},
     Pod,
 };
 
@@ -54,11 +54,9 @@ impl<T: Borrow<MapData>, K: Pod, V: Pod> HashMap<T, K, V> {
     /// Returns a copy of the value associated with the key.
     pub fn get(&self, key: &K, flags: u64) -> Result<V, MapError> {
         let fd = self.inner.borrow().fd_or_err()?;
-        let value = bpf_map_lookup_elem(fd, key, flags).map_err(|(_, io_error)| {
-            MapError::SyscallError {
-                call: "bpf_map_lookup_elem",
-                io_error,
-            }
+        let value = bpf_map_lookup_elem(fd, key, flags).map_err(|(_, io_error)| SyscallError {
+            call: "bpf_map_lookup_elem",
+            io_error,
         })?;
         value.ok_or(MapError::KeyNotFound)
     }
@@ -292,7 +290,7 @@ mod tests {
 
         assert_matches!(
             hm.insert(1, 42, 0),
-            Err(MapError::SyscallError { call, io_error }) if call == "bpf_map_update_elem" && io_error.raw_os_error() == Some(EFAULT)
+            Err(MapError::SyscallError(SyscallError { call: "bpf_map_update_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT)
         );
     }
 
@@ -352,7 +350,7 @@ mod tests {
 
         assert_matches!(
             hm.remove(&1),
-            Err(MapError::SyscallError { call, io_error }) if call == "bpf_map_delete_elem" && io_error.raw_os_error() == Some(EFAULT)
+            Err(MapError::SyscallError(SyscallError { call: "bpf_map_delete_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT)
         );
     }
 
@@ -390,7 +388,7 @@ mod tests {
 
         assert_matches!(
             hm.get(&1, 0),
-            Err(MapError::SyscallError { call, io_error }) if call == "bpf_map_lookup_elem" && io_error.raw_os_error() == Some(EFAULT)
+            Err(MapError::SyscallError(SyscallError { call: "bpf_map_lookup_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT)
         );
     }
 
@@ -535,7 +533,10 @@ mod tests {
         assert_matches!(keys.next(), Some(Ok(20)));
         assert_matches!(
             keys.next(),
-            Some(Err(MapError::SyscallError { call, .. })) if call == "bpf_map_get_next_key"
+            Some(Err(MapError::SyscallError(SyscallError {
+                call: "bpf_map_get_next_key",
+                io_error: _
+            })))
         );
         assert_matches!(keys.next(), None);
     }
@@ -647,7 +648,10 @@ mod tests {
         assert_matches!(iter.next(), Some(Ok((20, 200))));
         assert_matches!(
             iter.next(),
-            Some(Err(MapError::SyscallError { call, .. })) if call == "bpf_map_get_next_key"
+            Some(Err(MapError::SyscallError(SyscallError {
+                call: "bpf_map_get_next_key",
+                io_error: _
+            })))
         );
         assert_matches!(iter.next(), None);
     }
@@ -691,7 +695,10 @@ mod tests {
         assert_matches!(iter.next(), Some(Ok((10, 100))));
         assert_matches!(
             iter.next(),
-            Some(Err(MapError::SyscallError { call, .. })) if call == "bpf_map_lookup_elem"
+            Some(Err(MapError::SyscallError(SyscallError {
+                call: "bpf_map_lookup_elem",
+                io_error: _
+            })))
         );
         assert_matches!(iter.next(), Some(Ok((30, 300))));
         assert_matches!(iter.next(), None);

+ 10 - 9
aya/src/maps/hash_map/mod.rs

@@ -1,7 +1,7 @@
 //! Hash map types.
 use crate::{
     maps::MapError,
-    sys::{bpf_map_delete_elem, bpf_map_update_elem},
+    sys::{bpf_map_delete_elem, bpf_map_update_elem, SyscallError},
     Pod,
 };
 
@@ -21,11 +21,9 @@ pub(crate) fn insert<K: Pod, V: Pod>(
     flags: u64,
 ) -> Result<(), MapError> {
     let fd = map.fd_or_err()?;
-    bpf_map_update_elem(fd, Some(key), value, flags).map_err(|(_, io_error)| {
-        MapError::SyscallError {
-            call: "bpf_map_update_elem",
-            io_error,
-        }
+    bpf_map_update_elem(fd, Some(key), value, flags).map_err(|(_, io_error)| SyscallError {
+        call: "bpf_map_update_elem",
+        io_error,
     })?;
 
     Ok(())
@@ -35,8 +33,11 @@ pub(crate) fn remove<K: Pod>(map: &MapData, key: &K) -> Result<(), MapError> {
     let fd = map.fd_or_err()?;
     bpf_map_delete_elem(fd, key)
         .map(|_| ())
-        .map_err(|(_, io_error)| MapError::SyscallError {
-            call: "bpf_map_delete_elem",
-            io_error,
+        .map_err(|(_, io_error)| {
+            SyscallError {
+                call: "bpf_map_delete_elem",
+                io_error,
+            }
+            .into()
         })
 }

+ 5 - 6
aya/src/maps/hash_map/per_cpu_hash_map.rs

@@ -8,7 +8,7 @@ use crate::{
     maps::{
         check_kv_size, hash_map, IterableMap, MapData, MapError, MapIter, MapKeys, PerCpuValues,
     },
-    sys::{bpf_map_lookup_elem_per_cpu, bpf_map_update_elem_per_cpu},
+    sys::{bpf_map_lookup_elem_per_cpu, bpf_map_update_elem_per_cpu, SyscallError},
     Pod,
 };
 
@@ -64,12 +64,11 @@ impl<T: Borrow<MapData>, K: Pod, V: Pod> PerCpuHashMap<T, K, V> {
     /// Returns a slice of values - one for each CPU - associated with the key.
     pub fn get(&self, key: &K, flags: u64) -> Result<PerCpuValues<V>, MapError> {
         let fd = self.inner.borrow().fd_or_err()?;
-        let values = bpf_map_lookup_elem_per_cpu(fd, key, flags).map_err(|(_, io_error)| {
-            MapError::SyscallError {
+        let values =
+            bpf_map_lookup_elem_per_cpu(fd, key, flags).map_err(|(_, io_error)| SyscallError {
                 call: "bpf_map_lookup_elem",
                 io_error,
-            }
-        })?;
+            })?;
         values.ok_or(MapError::KeyNotFound)
     }
 
@@ -123,7 +122,7 @@ impl<T: BorrowMut<MapData>, K: Pod, V: Pod> PerCpuHashMap<T, K, V> {
     ) -> Result<(), MapError> {
         let fd = self.inner.borrow_mut().fd_or_err()?;
         bpf_map_update_elem_per_cpu(fd, key.borrow(), &values, flags).map_err(
-            |(_, io_error)| MapError::SyscallError {
+            |(_, io_error)| SyscallError {
                 call: "bpf_map_update_elem",
                 io_error,
             },

+ 14 - 13
aya/src/maps/lpm_trie.rs

@@ -6,7 +6,7 @@ use std::{
 
 use crate::{
     maps::{check_kv_size, IterableMap, MapData, MapError, MapIter, MapKeys},
-    sys::{bpf_map_delete_elem, bpf_map_lookup_elem, bpf_map_update_elem},
+    sys::{bpf_map_delete_elem, bpf_map_lookup_elem, bpf_map_update_elem, SyscallError},
     Pod,
 };
 
@@ -129,11 +129,9 @@ impl<T: Borrow<MapData>, K: Pod, V: Pod> LpmTrie<T, K, V> {
     /// Returns a copy of the value associated with the longest prefix matching key in the LpmTrie.
     pub fn get(&self, key: &Key<K>, flags: u64) -> Result<V, MapError> {
         let fd = self.inner.borrow().fd_or_err()?;
-        let value = bpf_map_lookup_elem(fd, key, flags).map_err(|(_, io_error)| {
-            MapError::SyscallError {
-                call: "bpf_map_lookup_elem",
-                io_error,
-            }
+        let value = bpf_map_lookup_elem(fd, key, flags).map_err(|(_, io_error)| SyscallError {
+            call: "bpf_map_lookup_elem",
+            io_error,
         })?;
         value.ok_or(MapError::KeyNotFound)
     }
@@ -161,7 +159,7 @@ impl<T: BorrowMut<MapData>, K: Pod, V: Pod> LpmTrie<T, K, V> {
     ) -> Result<(), MapError> {
         let fd = self.inner.borrow().fd_or_err()?;
         bpf_map_update_elem(fd, Some(key), value.borrow(), flags).map_err(|(_, io_error)| {
-            MapError::SyscallError {
+            SyscallError {
                 call: "bpf_map_update_elem",
                 io_error,
             }
@@ -177,9 +175,12 @@ impl<T: BorrowMut<MapData>, K: Pod, V: Pod> LpmTrie<T, K, V> {
         let fd = self.inner.borrow().fd_or_err()?;
         bpf_map_delete_elem(fd, key)
             .map(|_| ())
-            .map_err(|(_, io_error)| MapError::SyscallError {
-                call: "bpf_map_delete_elem",
-                io_error,
+            .map_err(|(_, io_error)| {
+                SyscallError {
+                    call: "bpf_map_delete_elem",
+                    io_error,
+                }
+                .into()
             })
     }
 }
@@ -349,7 +350,7 @@ mod tests {
         let key = Key::new(16, u32::from(ipaddr).to_be());
         assert_matches!(
             trie.insert(&key, 1, 0),
-            Err(MapError::SyscallError { call, io_error }) if call == "bpf_map_update_elem" && io_error.raw_os_error() == Some(EFAULT)
+            Err(MapError::SyscallError(SyscallError { call: "bpf_map_update_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT)
         );
     }
 
@@ -391,7 +392,7 @@ mod tests {
         let key = Key::new(16, u32::from(ipaddr).to_be());
         assert_matches!(
             trie.remove(&key),
-            Err(MapError::SyscallError { call, io_error }) if call == "bpf_map_delete_elem" && io_error.raw_os_error() == Some(EFAULT)
+            Err(MapError::SyscallError(SyscallError { call: "bpf_map_delete_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT)
         );
     }
 
@@ -432,7 +433,7 @@ mod tests {
 
         assert_matches!(
             trie.get(&key, 0),
-            Err(MapError::SyscallError { call, io_error }) if call == "bpf_map_lookup_elem" && io_error.raw_os_error() == Some(EFAULT)
+            Err(MapError::SyscallError(SyscallError { call: "bpf_map_lookup_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT)
         );
     }
 

+ 21 - 34
aya/src/maps/mod.rs

@@ -58,7 +58,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,
+        bpf_pin_object, SyscallError,
     },
     util::nr_cpus,
     PinningType, Pod,
@@ -167,13 +167,8 @@ pub enum MapError {
     ProgramNotLoaded,
 
     /// Syscall failed
-    #[error("the `{call}` syscall failed")]
-    SyscallError {
-        /// Syscall Name
-        call: &'static str,
-        /// Original io::Error
-        io_error: io::Error,
-    },
+    #[error(transparent)]
+    SyscallError(#[from] SyscallError),
 
     /// Could not pin map by name
     #[error("map `{name:?}` requested pinning by name. pinning failed")]
@@ -538,7 +533,7 @@ impl MapData {
         }
         let map_path = path.as_ref().join(name);
         let path_string = CString::new(map_path.to_str().unwrap()).unwrap();
-        let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| MapError::SyscallError {
+        let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError {
             call: "BPF_OBJ_GET",
             io_error,
         })? as RawFd;
@@ -560,15 +555,12 @@ impl MapData {
                 }
             })?;
 
-        let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| MapError::SyscallError {
+        let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError {
             call: "BPF_OBJ_GET",
             io_error,
         })? as RawFd;
 
-        let info = bpf_map_get_info_by_fd(fd).map_err(|io_error| MapError::SyscallError {
-            call: "BPF_MAP_GET_INFO_BY_FD",
-            io_error,
-        })?;
+        let info = bpf_map_get_info_by_fd(fd)?;
 
         Ok(MapData {
             obj: parse_map_info(info, PinningType::ByName),
@@ -584,10 +576,7 @@ impl MapData {
     /// This API is intended for cases where you have received a valid BPF FD from some other means.
     /// For example, you received an FD over Unix Domain Socket.
     pub fn from_fd(fd: RawFd) -> Result<MapData, MapError> {
-        let info = bpf_map_get_info_by_fd(fd).map_err(|io_error| MapError::SyscallError {
-            call: "BPF_OBJ_GET",
-            io_error,
-        })?;
+        let info = bpf_map_get_info_by_fd(fd)?;
 
         Ok(MapData {
             obj: parse_map_info(info, PinningType::None),
@@ -614,8 +603,8 @@ impl MapData {
                 error: e.to_string(),
             }
         })?;
-        bpf_pin_object(fd, &path_string).map_err(|(_, io_error)| PinError::SyscallError {
-            name: "BPF_OBJ_PIN",
+        bpf_pin_object(fd, &path_string).map_err(|(_, io_error)| SyscallError {
+            call: "BPF_OBJ_PIN",
             io_error,
         })?;
         self.pinned = true;
@@ -692,21 +681,19 @@ impl<K: Pod> Iterator for MapKeys<'_, K> {
             }
         };
 
-        match bpf_map_get_next_key(fd, self.key.as_ref()) {
-            Ok(Some(key)) => {
-                self.key = Some(key);
-                Some(Ok(key))
-            }
-            Ok(None) => {
-                self.key = None;
-                None
-            }
-            Err((_, io_error)) => {
+        let key =
+            bpf_map_get_next_key(fd, self.key.as_ref()).map_err(|(_, io_error)| SyscallError {
+                call: "bpf_map_get_next_key",
+                io_error,
+            });
+        match key {
+            Err(err) => {
                 self.err = true;
-                Some(Err(MapError::SyscallError {
-                    call: "bpf_map_get_next_key",
-                    io_error,
-                }))
+                Some(Err(err.into()))
+            }
+            Ok(key) => {
+                self.key = key;
+                key.map(Ok)
             }
         }
     }

+ 5 - 7
aya/src/maps/queue.rs

@@ -6,7 +6,7 @@ use std::{
 
 use crate::{
     maps::{check_kv_size, MapData, MapError},
-    sys::{bpf_map_lookup_and_delete_elem, bpf_map_push_elem},
+    sys::{bpf_map_lookup_and_delete_elem, bpf_map_push_elem, SyscallError},
     Pod,
 };
 
@@ -65,7 +65,7 @@ impl<T: BorrowMut<MapData>, V: Pod> Queue<T, V> {
         let fd = self.inner.borrow().fd_or_err()?;
 
         let value = bpf_map_lookup_and_delete_elem::<u32, _>(fd, None, flags).map_err(
-            |(_, io_error)| MapError::SyscallError {
+            |(_, io_error)| SyscallError {
                 call: "bpf_map_lookup_and_delete_elem",
                 io_error,
             },
@@ -80,11 +80,9 @@ impl<T: BorrowMut<MapData>, V: Pod> Queue<T, V> {
     /// [`MapError::SyscallError`] if `bpf_map_update_elem` fails.
     pub fn push(&mut self, value: impl Borrow<V>, flags: u64) -> Result<(), MapError> {
         let fd = self.inner.borrow().fd_or_err()?;
-        bpf_map_push_elem(fd, value.borrow(), flags).map_err(|(_, io_error)| {
-            MapError::SyscallError {
-                call: "bpf_map_push_elem",
-                io_error,
-            }
+        bpf_map_push_elem(fd, value.borrow(), flags).map_err(|(_, io_error)| SyscallError {
+            call: "bpf_map_push_elem",
+            io_error,
         })?;
         Ok(())
     }

+ 4 - 6
aya/src/maps/sock/sock_hash.rs

@@ -8,7 +8,7 @@ use crate::{
     maps::{
         check_kv_size, hash_map, sock::SockMapFd, IterableMap, MapData, MapError, MapIter, MapKeys,
     },
-    sys::bpf_map_lookup_elem,
+    sys::{bpf_map_lookup_elem, SyscallError},
     Pod,
 };
 
@@ -83,11 +83,9 @@ impl<T: Borrow<MapData>, K: Pod> SockHash<T, K> {
     /// Returns the fd of the socket stored at the given key.
     pub fn get(&self, key: &K, flags: u64) -> Result<RawFd, MapError> {
         let fd = self.inner.borrow().fd_or_err()?;
-        let value = bpf_map_lookup_elem(fd, key, flags).map_err(|(_, io_error)| {
-            MapError::SyscallError {
-                call: "bpf_map_lookup_elem",
-                io_error,
-            }
+        let value = bpf_map_lookup_elem(fd, key, flags).map_err(|(_, io_error)| SyscallError {
+            call: "bpf_map_lookup_elem",
+            io_error,
         })?;
         value.ok_or(MapError::KeyNotFound)
     }

+ 8 - 5
aya/src/maps/sock/sock_map.rs

@@ -7,7 +7,7 @@ use std::{
 
 use crate::{
     maps::{check_bounds, check_kv_size, sock::SockMapFd, MapData, MapError, MapKeys},
-    sys::{bpf_map_delete_elem, bpf_map_update_elem},
+    sys::{bpf_map_delete_elem, bpf_map_update_elem, SyscallError},
 };
 
 /// An array of TCP or UDP sockets.
@@ -76,7 +76,7 @@ impl<T: BorrowMut<MapData>> SockMap<T> {
         let fd = data.fd_or_err()?;
         check_bounds(data, index)?;
         bpf_map_update_elem(fd, Some(&index), &socket.as_raw_fd(), flags).map_err(
-            |(_, io_error)| MapError::SyscallError {
+            |(_, io_error)| SyscallError {
                 call: "bpf_map_update_elem",
                 io_error,
             },
@@ -91,9 +91,12 @@ impl<T: BorrowMut<MapData>> SockMap<T> {
         check_bounds(data, *index)?;
         bpf_map_delete_elem(fd, index)
             .map(|_| ())
-            .map_err(|(_, io_error)| MapError::SyscallError {
-                call: "bpf_map_delete_elem",
-                io_error,
+            .map_err(|(_, io_error)| {
+                SyscallError {
+                    call: "bpf_map_delete_elem",
+                    io_error,
+                }
+                .into()
             })
     }
 }

+ 3 - 3
aya/src/maps/stack.rs

@@ -6,7 +6,7 @@ use std::{
 
 use crate::{
     maps::{check_kv_size, MapData, MapError},
-    sys::{bpf_map_lookup_and_delete_elem, bpf_map_update_elem},
+    sys::{bpf_map_lookup_and_delete_elem, bpf_map_update_elem, SyscallError},
     Pod,
 };
 
@@ -65,7 +65,7 @@ impl<T: BorrowMut<MapData>, V: Pod> Stack<T, V> {
         let fd = self.inner.borrow().fd_or_err()?;
 
         let value = bpf_map_lookup_and_delete_elem::<u32, _>(fd, None, flags).map_err(
-            |(_, io_error)| MapError::SyscallError {
+            |(_, io_error)| SyscallError {
                 call: "bpf_map_lookup_and_delete_elem",
                 io_error,
             },
@@ -81,7 +81,7 @@ impl<T: BorrowMut<MapData>, V: Pod> Stack<T, V> {
     pub fn push(&mut self, value: impl Borrow<V>, flags: u64) -> Result<(), MapError> {
         let fd = self.inner.borrow().fd_or_err()?;
         bpf_map_update_elem(fd, None::<&u32>, value.borrow(), flags).map_err(|(_, io_error)| {
-            MapError::SyscallError {
+            SyscallError {
                 call: "bpf_map_update_elem",
                 io_error,
             }

+ 5 - 7
aya/src/maps/stack_trace.rs

@@ -5,7 +5,7 @@ use std::{borrow::Borrow, collections::BTreeMap, fs, io, mem, path::Path, str::F
 
 use crate::{
     maps::{IterableMap, MapData, MapError, MapIter, MapKeys},
-    sys::bpf_map_lookup_elem_ptr,
+    sys::{bpf_map_lookup_elem_ptr, SyscallError},
 };
 
 /// A hash map of kernel or user space stack traces.
@@ -77,11 +77,9 @@ impl<T: Borrow<MapData>> StackTraceMap<T> {
         }
 
         let max_stack_depth =
-            sysctl::<usize>("kernel/perf_event_max_stack").map_err(|io_error| {
-                MapError::SyscallError {
-                    call: "sysctl",
-                    io_error,
-                }
+            sysctl::<usize>("kernel/perf_event_max_stack").map_err(|io_error| SyscallError {
+                call: "sysctl",
+                io_error,
             })?;
         let size = data.obj.value_size() as usize;
         if size > max_stack_depth * mem::size_of::<u64>() {
@@ -106,7 +104,7 @@ impl<T: Borrow<MapData>> StackTraceMap<T> {
 
         let mut frames = vec![0; self.max_stack_depth];
         bpf_map_lookup_elem_ptr(fd, Some(stack_id), frames.as_mut_ptr(), flags)
-            .map_err(|(_, io_error)| MapError::SyscallError {
+            .map_err(|(_, io_error)| SyscallError {
                 call: "bpf_map_lookup_elem",
                 io_error,
             })?

+ 4 - 9
aya/src/pin.rs

@@ -1,5 +1,6 @@
 //! Pinning BPF objects to the BPF filesystem.
-use std::io;
+
+use crate::sys::SyscallError;
 use thiserror::Error;
 
 /// An error ocurred working with a pinned BPF object.
@@ -24,12 +25,6 @@ pub enum PinError {
         error: String,
     },
     /// An error ocurred making a syscall.
-    #[error("{name} failed")]
-    SyscallError {
-        /// The syscall name.
-        name: &'static str,
-        /// The [`io::Error`] returned by the syscall.
-        #[source]
-        io_error: io::Error,
-    },
+    #[error(transparent)]
+    SyscallError(#[from] SyscallError),
 }

+ 3 - 3
aya/src/programs/cgroup_device.rs

@@ -8,7 +8,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
-    sys::{bpf_link_create, bpf_prog_attach},
+    sys::{bpf_link_create, bpf_prog_attach, SyscallError},
 };
 
 /// A program used to watch or prevent device interaction from a cgroup.
@@ -66,7 +66,7 @@ impl CgroupDevice {
 
         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 {
+                |(_, io_error)| SyscallError {
                     call: "bpf_link_create",
                     io_error,
                 },
@@ -78,7 +78,7 @@ impl CgroupDevice {
                 )))
         } else {
             bpf_prog_attach(prog_fd, cgroup_fd, BPF_CGROUP_DEVICE).map_err(|(_, io_error)| {
-                ProgramError::SyscallError {
+                SyscallError {
                     call: "bpf_prog_attach",
                     io_error,
                 }

+ 3 - 3
aya/src/programs/cgroup_skb.rs

@@ -15,7 +15,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
-    sys::{bpf_link_create, bpf_prog_attach},
+    sys::{bpf_link_create, bpf_prog_attach, SyscallError},
     VerifierLogLevel,
 };
 
@@ -101,7 +101,7 @@ impl CgroupSkb {
         };
         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 {
+                |(_, io_error)| SyscallError {
                     call: "bpf_link_create",
                     io_error,
                 },
@@ -113,7 +113,7 @@ impl CgroupSkb {
                 ))))
         } else {
             bpf_prog_attach(prog_fd, cgroup_fd, attach_type).map_err(|(_, io_error)| {
-                ProgramError::SyscallError {
+                SyscallError {
                     call: "bpf_prog_attach",
                     io_error,
                 }

+ 3 - 3
aya/src/programs/cgroup_sock.rs

@@ -14,7 +14,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
-    sys::{bpf_link_create, bpf_prog_attach},
+    sys::{bpf_link_create, bpf_prog_attach, SyscallError},
     VerifierLogLevel,
 };
 
@@ -76,7 +76,7 @@ impl CgroupSock {
         let attach_type = self.data.expected_attach_type.unwrap();
         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 {
+                |(_, io_error)| SyscallError {
                     call: "bpf_link_create",
                     io_error,
                 },
@@ -88,7 +88,7 @@ impl CgroupSock {
                 ))))
         } else {
             bpf_prog_attach(prog_fd, cgroup_fd, attach_type).map_err(|(_, io_error)| {
-                ProgramError::SyscallError {
+                SyscallError {
                     call: "bpf_prog_attach",
                     io_error,
                 }

+ 3 - 3
aya/src/programs/cgroup_sock_addr.rs

@@ -14,7 +14,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
-    sys::{bpf_link_create, bpf_prog_attach},
+    sys::{bpf_link_create, bpf_prog_attach, SyscallError},
     VerifierLogLevel,
 };
 
@@ -77,7 +77,7 @@ impl CgroupSockAddr {
         let attach_type = self.data.expected_attach_type.unwrap();
         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 {
+                |(_, io_error)| SyscallError {
                     call: "bpf_link_create",
                     io_error,
                 },
@@ -89,7 +89,7 @@ impl CgroupSockAddr {
                 )))
         } else {
             bpf_prog_attach(prog_fd, cgroup_fd, attach_type).map_err(|(_, io_error)| {
-                ProgramError::SyscallError {
+                SyscallError {
                     call: "bpf_prog_attach",
                     io_error,
                 }

+ 3 - 3
aya/src/programs/cgroup_sockopt.rs

@@ -14,7 +14,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
-    sys::{bpf_link_create, bpf_prog_attach},
+    sys::{bpf_link_create, bpf_prog_attach, SyscallError},
     VerifierLogLevel,
 };
 
@@ -74,7 +74,7 @@ impl CgroupSockopt {
         let attach_type = self.data.expected_attach_type.unwrap();
         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 {
+                |(_, io_error)| SyscallError {
                     call: "bpf_link_create",
                     io_error,
                 },
@@ -86,7 +86,7 @@ impl CgroupSockopt {
                 )))
         } else {
             bpf_prog_attach(prog_fd, cgroup_fd, attach_type).map_err(|(_, io_error)| {
-                ProgramError::SyscallError {
+                SyscallError {
                     call: "bpf_prog_attach",
                     io_error,
                 }

+ 3 - 3
aya/src/programs/cgroup_sysctl.rs

@@ -11,7 +11,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
     },
-    sys::{bpf_link_create, bpf_prog_attach},
+    sys::{bpf_link_create, bpf_prog_attach, SyscallError},
 };
 
 /// A program used to watch for sysctl changes.
@@ -68,7 +68,7 @@ impl CgroupSysctl {
 
         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 {
+                |(_, io_error)| SyscallError {
                     call: "bpf_link_create",
                     io_error,
                 },
@@ -80,7 +80,7 @@ impl CgroupSysctl {
                 )))
         } else {
             bpf_prog_attach(prog_fd, cgroup_fd, BPF_CGROUP_SYSCTL).map_err(|(_, io_error)| {
-                ProgramError::SyscallError {
+                SyscallError {
                     call: "bpf_prog_attach",
                     io_error,
                 }

+ 12 - 18
aya/src/programs/extension.rs

@@ -10,7 +10,7 @@ use crate::{
     programs::{
         define_link_wrapper, load_program, FdLink, FdLinkId, ProgramData, ProgramError, ProgramFd,
     },
-    sys::{self, bpf_link_create},
+    sys::{self, bpf_link_create, SyscallError},
     Btf,
 };
 
@@ -91,7 +91,7 @@ impl Extension {
         let btf_id = self.data.attach_btf_id.ok_or(ProgramError::NotLoaded)?;
         // the attach type must be set as 0, which is bpf_attach_type::BPF_CGROUP_INET_INGRESS
         let link_fd = bpf_link_create(prog_fd, target_fd, BPF_CGROUP_INET_INGRESS, Some(btf_id), 0)
-            .map_err(|(_, io_error)| ProgramError::SyscallError {
+            .map_err(|(_, io_error)| SyscallError {
                 call: "bpf_link_create",
                 io_error,
             })? as RawFd;
@@ -121,7 +121,7 @@ impl Extension {
         let prog_fd = self.data.fd_or_err()?;
         // the attach type must be set as 0, which is bpf_attach_type::BPF_CGROUP_INET_INGRESS
         let link_fd = bpf_link_create(prog_fd, target_fd, BPF_CGROUP_INET_INGRESS, Some(btf_id), 0)
-            .map_err(|(_, io_error)| ProgramError::SyscallError {
+            .map_err(|(_, io_error)| SyscallError {
                 call: "bpf_link_create",
                 io_error,
             })? as RawFd;
@@ -151,11 +151,7 @@ impl Extension {
 /// with the name `func_name` within that BTF object.
 fn get_btf_info(prog_fd: i32, func_name: &str) -> Result<(RawFd, u32), ProgramError> {
     // retrieve program information
-    let info =
-        sys::bpf_prog_get_info_by_fd(prog_fd).map_err(|io_error| ProgramError::SyscallError {
-            call: "bpf_prog_get_info_by_fd",
-            io_error,
-        })?;
+    let info = sys::bpf_prog_get_info_by_fd(prog_fd)?;
 
     // btf_id refers to the ID of the program btf that was loaded with bpf(BPF_BTF_LOAD)
     if info.btf_id == 0 {
@@ -163,11 +159,10 @@ fn get_btf_info(prog_fd: i32, func_name: &str) -> Result<(RawFd, u32), ProgramEr
     }
 
     // the bpf fd of the BTF object
-    let btf_fd =
-        sys::bpf_btf_get_fd_by_id(info.btf_id).map_err(|io_error| ProgramError::SyscallError {
-            call: "bpf_btf_get_fd_by_id",
-            io_error,
-        })?;
+    let btf_fd = sys::bpf_btf_get_fd_by_id(info.btf_id).map_err(|io_error| SyscallError {
+        call: "bpf_btf_get_fd_by_id",
+        io_error,
+    })?;
 
     // we need to read the btf bytes into a buffer but we don't know the size ahead of time.
     // assume 4kb. if this is too small we can resize based on the size obtained in the response.
@@ -176,18 +171,17 @@ fn get_btf_info(prog_fd: i32, func_name: &str) -> Result<(RawFd, u32), ProgramEr
         Ok(info) => {
             if info.btf_size > buf.len() as u32 {
                 buf.resize(info.btf_size as usize, 0u8);
-                let btf_info = sys::btf_obj_get_info_by_fd(btf_fd, &buf).map_err(|io_error| {
-                    ProgramError::SyscallError {
+                let btf_info =
+                    sys::btf_obj_get_info_by_fd(btf_fd, &buf).map_err(|io_error| SyscallError {
                         call: "bpf_prog_get_info_by_fd",
                         io_error,
-                    }
-                })?;
+                    })?;
                 Ok(btf_info)
             } else {
                 Ok(info)
             }
         }
-        Err(io_error) => Err(ProgramError::SyscallError {
+        Err(io_error) => Err(SyscallError {
             call: "bpf_prog_get_info_by_fd",
             io_error,
         }),

+ 1 - 6
aya/src/programs/kprobe.rs

@@ -137,12 +137,7 @@ impl TryFrom<FdLink> for KProbeLink {
     type Error = LinkError;
 
     fn try_from(fd_link: FdLink) -> Result<Self, Self::Error> {
-        let info =
-            bpf_link_get_info_by_fd(fd_link.fd).map_err(|io_error| LinkError::SyscallError {
-                call: "BPF_OBJ_GET_INFO_BY_FD",
-                code: 0,
-                io_error,
-            })?;
+        let info = bpf_link_get_info_by_fd(fd_link.fd)?;
         if info.type_ == (bpf_link_type::BPF_LINK_TYPE_KPROBE_MULTI as u32) {
             return Ok(KProbeLink::new(PerfLinkInner::FdLink(fd_link)));
         }

+ 9 - 17
aya/src/programs/links.rs

@@ -14,7 +14,7 @@ use crate::{
     generated::bpf_attach_type,
     pin::PinError,
     programs::ProgramError,
-    sys::{bpf_get_object, bpf_pin_object, bpf_prog_detach},
+    sys::{bpf_get_object, bpf_pin_object, bpf_prog_detach, SyscallError},
 };
 
 /// A Link.
@@ -152,8 +152,8 @@ impl FdLink {
                     error: e.to_string(),
                 }
             })?;
-        bpf_pin_object(self.fd, &path_string).map_err(|(_, io_error)| PinError::SyscallError {
-            name: "BPF_OBJ_PIN",
+        bpf_pin_object(self.fd, &path_string).map_err(|(_, io_error)| SyscallError {
+            call: "BPF_OBJ_PIN",
             io_error,
         })?;
         Ok(PinnedLink::new(PathBuf::from(path.as_ref()), self))
@@ -208,12 +208,12 @@ impl PinnedLink {
     /// Creates a [`crate::programs::links::PinnedLink`] from a valid path on bpffs.
     pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, LinkError> {
         let path_string = CString::new(path.as_ref().to_string_lossy().to_string()).unwrap();
-        let fd =
-            bpf_get_object(&path_string).map_err(|(code, io_error)| LinkError::SyscallError {
+        let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| {
+            LinkError::SyscallError(SyscallError {
                 call: "BPF_OBJ_GET",
-                code,
                 io_error,
-            })? as RawFd;
+            })
+        })? as RawFd;
         Ok(PinnedLink::new(
             path.as_ref().to_path_buf(),
             FdLink::new(fd),
@@ -339,16 +339,8 @@ pub enum LinkError {
     #[error("Invalid link")]
     InvalidLink,
     /// Syscall failed.
-    #[error("the `{call}` syscall failed with code {code}")]
-    SyscallError {
-        /// Syscall Name.
-        call: &'static str,
-        /// Error code.
-        code: libc::c_long,
-        #[source]
-        /// Original io::Error.
-        io_error: io::Error,
-    },
+    #[error(transparent)]
+    SyscallError(#[from] SyscallError),
 }
 
 #[cfg(test)]

+ 9 - 14
aya/src/programs/lirc_mode2.rs

@@ -4,7 +4,10 @@ use std::os::fd::{AsRawFd, IntoRawFd as _, RawFd};
 use crate::{
     generated::{bpf_attach_type::BPF_LIRC_MODE2, bpf_prog_type::BPF_PROG_TYPE_LIRC_MODE2},
     programs::{load_program, query, Link, ProgramData, ProgramError, ProgramInfo},
-    sys::{bpf_prog_attach, bpf_prog_detach, bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd},
+    sys::{
+        bpf_prog_attach, bpf_prog_detach, bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd,
+        SyscallError,
+    },
 };
 
 use libc::{close, dup};
@@ -65,7 +68,7 @@ impl LircMode2 {
         let lircdev_fd = lircdev.as_raw_fd();
 
         bpf_prog_attach(prog_fd, lircdev_fd, BPF_LIRC_MODE2).map_err(|(_, io_error)| {
-            ProgramError::SyscallError {
+            SyscallError {
                 call: "bpf_prog_attach",
                 io_error,
             }
@@ -96,11 +99,7 @@ impl LircMode2 {
         let mut prog_fds = Vec::with_capacity(prog_ids.len());
 
         for id in prog_ids {
-            let fd = bpf_prog_get_fd_by_id(id).map_err(|io_error| ProgramError::SyscallError {
-                call: "bpf_prog_get_fd_by_id",
-                io_error,
-            })?;
-
+            let fd = bpf_prog_get_fd_by_id(id)?;
             prog_fds.push(fd);
         }
 
@@ -132,13 +131,9 @@ impl LircLink {
 
     /// Get ProgramInfo from this link
     pub fn info(&self) -> Result<ProgramInfo, ProgramError> {
-        match bpf_prog_get_info_by_fd(self.prog_fd) {
-            Ok(info) => Ok(ProgramInfo(info)),
-            Err(io_error) => Err(ProgramError::SyscallError {
-                call: "bpf_prog_get_info_by_fd",
-                io_error,
-            }),
-        }
+        bpf_prog_get_info_by_fd(self.prog_fd)
+            .map(ProgramInfo)
+            .map_err(Into::into)
     }
 }
 

+ 62 - 103
aya/src/programs/mod.rs

@@ -64,7 +64,6 @@ pub mod uprobe;
 mod utils;
 pub mod xdp;
 
-use crate::util::KernelVersion;
 use libc::ENOSPC;
 use std::{
     ffi::CString,
@@ -105,15 +104,17 @@ pub use uprobe::{UProbe, UProbeError};
 pub use xdp::{Xdp, XdpError, XdpFlags};
 
 use crate::{
-    generated::{bpf_attach_type, bpf_prog_info, bpf_prog_type},
+    generated::{bpf_attach_type, bpf_link_info, bpf_prog_info, bpf_prog_type},
     maps::MapError,
     obj::{self, btf::BtfError, Function, VerifierLog},
     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,
+        bpf_btf_get_fd_by_id, bpf_get_object, bpf_link_get_fd_by_id, bpf_link_get_info_by_fd,
+        bpf_load_program, bpf_pin_object, bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd,
+        bpf_prog_query, iter_link_ids, iter_prog_ids, retry_with_verifier_logs,
+        BpfLoadProgramAttrs, SyscallError,
     },
+    util::KernelVersion,
     VerifierLogLevel,
 };
 
@@ -147,14 +148,8 @@ pub enum ProgramError {
     },
 
     /// A syscall failed.
-    #[error("`{call}` failed")]
-    SyscallError {
-        /// The name of the syscall which failed.
-        call: &'static str,
-        /// The [`io::Error`] returned by the syscall.
-        #[source]
-        io_error: io::Error,
-    },
+    #[error(transparent)]
+    SyscallError(#[from] SyscallError),
 
     /// The network interface does not exist.
     #[error("unknown network interface {name}")]
@@ -456,12 +451,11 @@ impl<T: Link> ProgramData<T> {
             None
         };
         let attach_btf_obj_fd = if info.attach_btf_obj_id > 0 {
-            let fd = bpf_btf_get_fd_by_id(info.attach_btf_obj_id).map_err(|io_error| {
-                ProgramError::SyscallError {
+            let fd =
+                bpf_btf_get_fd_by_id(info.attach_btf_obj_id).map_err(|io_error| SyscallError {
                     call: "bpf_btf_get_fd_by_id",
                     io_error,
-                }
-            })?;
+                })?;
             Some(fd as u32)
         } else {
             None
@@ -489,17 +483,12 @@ impl<T: Link> ProgramData<T> {
     ) -> Result<ProgramData<T>, ProgramError> {
         let path_string =
             CString::new(path.as_ref().as_os_str().to_string_lossy().as_bytes()).unwrap();
-        let fd =
-            bpf_get_object(&path_string).map_err(|(_, io_error)| ProgramError::SyscallError {
-                call: "bpf_obj_get",
-                io_error,
-            })? as RawFd;
-
-        let info = bpf_prog_get_info_by_fd(fd).map_err(|io_error| ProgramError::SyscallError {
-            call: "bpf_prog_get_info_by_fd",
+        let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError {
+            call: "bpf_obj_get",
             io_error,
-        })?;
+        })? as RawFd;
 
+        let info = bpf_prog_get_info_by_fd(fd)?;
         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)
     }
@@ -537,8 +526,8 @@ fn pin_program<T: Link, P: AsRef<Path>>(data: &ProgramData<T>, path: P) -> Resul
             error: e.to_string(),
         }
     })?;
-    bpf_pin_object(fd, &path_string).map_err(|(_, io_error)| PinError::SyscallError {
-        name: "BPF_OBJ_PIN",
+    bpf_pin_object(fd, &path_string).map_err(|(_, io_error)| SyscallError {
+        call: "BPF_OBJ_PIN",
         io_error,
     })?;
     Ok(())
@@ -665,15 +654,17 @@ pub(crate) fn query<T: AsRawFd>(
                 prog_ids.resize(prog_cnt as usize, 0);
                 return Ok(prog_ids);
             }
-            Err((_, io_error)) if retries == 0 && io_error.raw_os_error() == Some(ENOSPC) => {
-                prog_ids.resize(prog_cnt as usize, 0);
-                retries += 1;
-            }
             Err((_, io_error)) => {
-                return Err(ProgramError::SyscallError {
-                    call: "bpf_prog_query",
-                    io_error,
-                });
+                if retries == 0 && io_error.raw_os_error() == Some(ENOSPC) {
+                    prog_ids.resize(prog_cnt as usize, 0);
+                    retries += 1;
+                } else {
+                    return Err(SyscallError {
+                        call: "bpf_prog_query",
+                        io_error,
+                    }
+                    .into());
+                }
             }
         }
     }
@@ -951,82 +942,28 @@ impl ProgramInfo {
     ///
     /// The returned fd must be closed when no longer needed.
     pub fn fd(&self) -> Result<RawFd, ProgramError> {
-        let fd =
-            bpf_prog_get_fd_by_id(self.0.id).map_err(|io_error| ProgramError::SyscallError {
-                call: "bpf_prog_get_fd_by_id",
-                io_error,
-            })?;
+        let Self(info) = self;
+        let fd = bpf_prog_get_fd_by_id(info.id)?;
         Ok(fd.into_raw_fd())
     }
 
     /// Loads a program from a pinned path in bpffs.
     pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<ProgramInfo, ProgramError> {
         let path_string = CString::new(path.as_ref().to_str().unwrap()).unwrap();
-        let fd =
-            bpf_get_object(&path_string).map_err(|(_, io_error)| ProgramError::SyscallError {
-                call: "BPF_OBJ_GET",
-                io_error,
-            })? as RawFd;
-
-        let info = bpf_prog_get_info_by_fd(fd).map_err(|io_error| ProgramError::SyscallError {
-            call: "bpf_prog_get_info_by_fd",
+        let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError {
+            call: "BPF_OBJ_GET",
             io_error,
-        })?;
+        })? as RawFd;
+
+        let info = bpf_prog_get_info_by_fd(fd);
         unsafe {
             libc::close(fd);
         }
+        let info = info?;
         Ok(ProgramInfo(info))
     }
 }
 
-/// ProgramsIter is an Iterator over loaded eBPF programs.
-pub struct ProgramsIter {
-    current: u32,
-    error: bool,
-}
-
-impl Iterator for ProgramsIter {
-    type Item = Result<ProgramInfo, ProgramError>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if self.error {
-            return None;
-        }
-        let current = self.current;
-
-        match bpf_prog_get_next_id(current) {
-            Ok(Some(next)) => {
-                self.current = next;
-                Some(
-                    bpf_prog_get_fd_by_id(next)
-                        .map_err(|io_error| ProgramError::SyscallError {
-                            call: "bpf_prog_get_fd_by_id",
-                            io_error,
-                        })
-                        .and_then(|fd| {
-                            bpf_prog_get_info_by_fd(fd.as_raw_fd())
-                                .map_err(|io_error| ProgramError::SyscallError {
-                                    call: "bpf_prog_get_info_by_fd",
-                                    io_error,
-                                })
-                                .map(ProgramInfo)
-                        }),
-                )
-            }
-            Ok(None) => None,
-            Err((_, io_error)) => {
-                // If getting the next program failed, we have to yield None in our next
-                // iteration to avoid an infinite loop.
-                self.error = true;
-                Some(Err(ProgramError::SyscallError {
-                    call: "bpf_prog_get_fd_by_id",
-                    io_error,
-                }))
-            }
-        }
-    }
-}
-
 /// Returns an iterator over all loaded bpf programs.
 ///
 /// This differs from [`crate::Bpf::programs`] since it will return all programs
@@ -1050,9 +987,31 @@ impl Iterator for ProgramsIter {
 /// next program id, get the program fd, or the [`ProgramInfo`] fail. In cases where
 /// iteration can't be performed, for example the caller does not have the necessary privileges,
 /// a single item will be yielded containing the error that occurred.
-pub fn loaded_programs() -> ProgramsIter {
-    ProgramsIter {
-        current: 0,
-        error: false,
-    }
+pub fn loaded_programs() -> impl Iterator<Item = Result<ProgramInfo, ProgramError>> {
+    iter_prog_ids()
+        .map(|id| {
+            let id = id?;
+            bpf_prog_get_fd_by_id(id)
+        })
+        .map(|fd| {
+            let fd = fd?;
+            bpf_prog_get_info_by_fd(fd.as_raw_fd())
+        })
+        .map(|result| result.map(ProgramInfo).map_err(Into::into))
+}
+
+// TODO(https://github.com/aya-rs/aya/issues/645): this API is currently used in tests. Stabilize
+// and remove doc(hidden).
+#[doc(hidden)]
+pub fn loaded_links() -> impl Iterator<Item = Result<bpf_link_info, ProgramError>> {
+    iter_link_ids()
+        .map(|id| {
+            let id = id?;
+            bpf_link_get_fd_by_id(id)
+        })
+        .map(|fd| {
+            let fd = fd?;
+            bpf_link_get_info_by_fd(fd.as_raw_fd())
+        })
+        .map(|result| result.map_err(Into::into))
 }

+ 4 - 4
aya/src/programs/perf_attach.rs

@@ -7,7 +7,7 @@ use crate::{
         probe::{detach_debug_fs, ProbeEvent},
         FdLink, Link, ProgramError,
     },
-    sys::{bpf_link_create, perf_event_ioctl, SysResult},
+    sys::{bpf_link_create, perf_event_ioctl, SysResult, SyscallError},
     FEATURES, PERF_EVENT_IOC_DISABLE, PERF_EVENT_IOC_ENABLE, PERF_EVENT_IOC_SET_BPF,
 };
 
@@ -73,7 +73,7 @@ impl Link for PerfLink {
 pub(crate) fn perf_attach(prog_fd: RawFd, fd: OwnedFd) -> Result<PerfLinkInner, ProgramError> {
     if FEATURES.bpf_perf_link() {
         let link_fd = bpf_link_create(prog_fd, fd.as_raw_fd(), BPF_PERF_EVENT, None, 0).map_err(
-            |(_, io_error)| ProgramError::SyscallError {
+            |(_, io_error)| SyscallError {
                 call: "bpf_link_create",
                 io_error,
             },
@@ -98,13 +98,13 @@ fn perf_attach_either(
     event: Option<ProbeEvent>,
 ) -> Result<PerfLinkInner, ProgramError> {
     perf_event_ioctl(fd.as_fd(), PERF_EVENT_IOC_SET_BPF, prog_fd).map_err(|(_, io_error)| {
-        ProgramError::SyscallError {
+        SyscallError {
             call: "PERF_EVENT_IOC_SET_BPF",
             io_error,
         }
     })?;
     perf_event_ioctl(fd.as_fd(), PERF_EVENT_IOC_ENABLE, 0).map_err(|(_, io_error)| {
-        ProgramError::SyscallError {
+        SyscallError {
             call: "PERF_EVENT_IOC_ENABLE",
             io_error,
         }

+ 3 - 8
aya/src/programs/perf_event.rs

@@ -19,7 +19,7 @@ use crate::{
         perf_attach::{PerfLinkIdInner, PerfLinkInner},
         FdLink, LinkError, ProgramData, ProgramError,
     },
-    sys::{bpf_link_get_info_by_fd, perf_event_open},
+    sys::{bpf_link_get_info_by_fd, perf_event_open, SyscallError},
 };
 
 /// The type of perf event
@@ -165,7 +165,7 @@ impl PerfEvent {
             false,
             0,
         )
-        .map_err(|(_code, io_error)| ProgramError::SyscallError {
+        .map_err(|(_code, io_error)| SyscallError {
             call: "perf_event_open",
             io_error,
         })?;
@@ -206,12 +206,7 @@ impl TryFrom<FdLink> for PerfEventLink {
     type Error = LinkError;
 
     fn try_from(fd_link: FdLink) -> Result<Self, Self::Error> {
-        let info =
-            bpf_link_get_info_by_fd(fd_link.fd).map_err(|io_error| LinkError::SyscallError {
-                call: "BPF_OBJ_GET_INFO_BY_FD",
-                code: 0,
-                io_error,
-            })?;
+        let info = bpf_link_get_info_by_fd(fd_link.fd)?;
         if info.type_ == (bpf_link_type::BPF_LINK_TYPE_PERF_EVENT as u32) {
             return Ok(PerfEventLink::new(PerfLinkInner::FdLink(fd_link)));
         }

+ 6 - 7
aya/src/programs/probe.rs

@@ -15,7 +15,7 @@ use crate::{
         trace_point::read_sys_fs_trace_point_id, uprobe::UProbeError, utils::find_tracefs_path,
         Link, ProgramData, ProgramError,
     },
-    sys::{perf_event_open_probe, perf_event_open_trace_point},
+    sys::{perf_event_open_probe, perf_event_open_trace_point, SyscallError},
 };
 
 static PROBE_NAME_INDEX: AtomicUsize = AtomicUsize::new(0);
@@ -118,10 +118,11 @@ fn create_as_probe(
     };
 
     perf_event_open_probe(perf_ty, ret_bit, fn_name, offset, pid).map_err(|(_code, io_error)| {
-        ProgramError::SyscallError {
+        SyscallError {
             call: "perf_event_open",
             io_error,
         }
+        .into()
     })
 }
 
@@ -144,11 +145,9 @@ fn create_as_trace_point(
 
     let category = format!("{}s", kind.pmu());
     let tpid = read_sys_fs_trace_point_id(tracefs, &category, &event_alias)?;
-    let fd = perf_event_open_trace_point(tpid, pid).map_err(|(_code, io_error)| {
-        ProgramError::SyscallError {
-            call: "perf_event_open",
-            io_error,
-        }
+    let fd = perf_event_open_trace_point(tpid, pid).map_err(|(_code, io_error)| SyscallError {
+        call: "perf_event_open",
+        io_error,
     })?;
 
     Ok((fd, event_alias))

+ 2 - 2
aya/src/programs/sk_lookup.rs

@@ -3,7 +3,7 @@ use std::os::fd::{AsRawFd, RawFd};
 use crate::{
     generated::{bpf_attach_type::BPF_SK_LOOKUP, bpf_prog_type::BPF_PROG_TYPE_SK_LOOKUP},
     programs::{define_link_wrapper, load_program, FdLinkId, ProgramData, ProgramError},
-    sys::bpf_link_create,
+    sys::{bpf_link_create, SyscallError},
 };
 
 use super::links::FdLink;
@@ -65,7 +65,7 @@ impl SkLookup {
         let netns_fd = netns.as_raw_fd();
 
         let link_fd = bpf_link_create(prog_fd, netns_fd, BPF_SK_LOOKUP, None, 0).map_err(
-            |(_, io_error)| ProgramError::SyscallError {
+            |(_, io_error)| SyscallError {
                 call: "bpf_link_create",
                 io_error,
             },

+ 2 - 2
aya/src/programs/sk_msg.rs

@@ -9,7 +9,7 @@ use crate::{
         define_link_wrapper, load_program, ProgAttachLink, ProgAttachLinkId, ProgramData,
         ProgramError,
     },
-    sys::bpf_prog_attach,
+    sys::{bpf_prog_attach, SyscallError},
 };
 
 /// A program used to intercept messages sent with `sendmsg()`/`sendfile()`.
@@ -83,7 +83,7 @@ impl SkMsg {
         let map_fd = map.as_raw_fd();
 
         bpf_prog_attach(prog_fd, map_fd, BPF_SK_MSG_VERDICT).map_err(|(_, io_error)| {
-            ProgramError::SyscallError {
+            SyscallError {
                 call: "bpf_prog_attach",
                 io_error,
             }

+ 4 - 6
aya/src/programs/sk_skb.rs

@@ -12,7 +12,7 @@ use crate::{
         define_link_wrapper, load_program, ProgAttachLink, ProgAttachLinkId, ProgramData,
         ProgramError,
     },
-    sys::bpf_prog_attach,
+    sys::{bpf_prog_attach, SyscallError},
     VerifierLogLevel,
 };
 
@@ -79,11 +79,9 @@ impl SkSkb {
             SkSkbKind::StreamParser => BPF_SK_SKB_STREAM_PARSER,
             SkSkbKind::StreamVerdict => BPF_SK_SKB_STREAM_VERDICT,
         };
-        bpf_prog_attach(prog_fd, map_fd, attach_type).map_err(|(_, io_error)| {
-            ProgramError::SyscallError {
-                call: "bpf_prog_attach",
-                io_error,
-            }
+        bpf_prog_attach(prog_fd, map_fd, attach_type).map_err(|(_, io_error)| SyscallError {
+            call: "bpf_prog_attach",
+            io_error,
         })?;
         self.data.links.insert(SkSkbLink::new(ProgAttachLink::new(
             prog_fd,

+ 2 - 2
aya/src/programs/sock_ops.rs

@@ -7,7 +7,7 @@ use crate::{
         define_link_wrapper, load_program, ProgAttachLink, ProgAttachLinkId, ProgramData,
         ProgramError,
     },
-    sys::bpf_prog_attach,
+    sys::{bpf_prog_attach, SyscallError},
 };
 
 /// A program used to work with sockets.
@@ -63,7 +63,7 @@ impl SockOps {
         let cgroup_fd = cgroup.as_raw_fd();
 
         bpf_prog_attach(prog_fd, cgroup_fd, BPF_CGROUP_SOCK_OPS).map_err(|(_, io_error)| {
-            ProgramError::SyscallError {
+            SyscallError {
                 call: "bpf_prog_attach",
                 io_error,
             }

+ 5 - 11
aya/src/programs/trace_point.rs

@@ -10,7 +10,7 @@ use crate::{
         utils::find_tracefs_path,
         FdLink, LinkError, ProgramData, ProgramError,
     },
-    sys::{bpf_link_get_info_by_fd, perf_event_open_trace_point},
+    sys::{bpf_link_get_info_by_fd, perf_event_open_trace_point, SyscallError},
 };
 
 /// The type returned when attaching a [`TracePoint`] fails.
@@ -80,12 +80,11 @@ impl TracePoint {
     pub fn attach(&mut self, category: &str, name: &str) -> Result<TracePointLinkId, ProgramError> {
         let tracefs = find_tracefs_path()?;
         let id = read_sys_fs_trace_point_id(tracefs, category, name)?;
-        let fd = perf_event_open_trace_point(id, None).map_err(|(_code, io_error)| {
-            ProgramError::SyscallError {
+        let fd =
+            perf_event_open_trace_point(id, None).map_err(|(_code, io_error)| SyscallError {
                 call: "perf_event_open_trace_point",
                 io_error,
-            }
-        })?;
+            })?;
 
         let link = perf_attach(self.data.fd_or_err()?, fd)?;
         self.data.links.insert(TracePointLink::new(link))
@@ -132,12 +131,7 @@ impl TryFrom<FdLink> for TracePointLink {
     type Error = LinkError;
 
     fn try_from(fd_link: FdLink) -> Result<Self, Self::Error> {
-        let info =
-            bpf_link_get_info_by_fd(fd_link.fd).map_err(|io_error| LinkError::SyscallError {
-                call: "BPF_OBJ_GET_INFO_BY_FD",
-                code: 0,
-                io_error,
-            })?;
+        let info = bpf_link_get_info_by_fd(fd_link.fd)?;
         if info.type_ == (bpf_link_type::BPF_LINK_TYPE_TRACING as u32) {
             return Ok(TracePointLink::new(PerfLinkInner::FdLink(fd_link)));
         }

+ 1 - 6
aya/src/programs/uprobe.rs

@@ -177,12 +177,7 @@ impl TryFrom<FdLink> for UProbeLink {
     type Error = LinkError;
 
     fn try_from(fd_link: FdLink) -> Result<Self, Self::Error> {
-        let info =
-            bpf_link_get_info_by_fd(fd_link.fd).map_err(|io_error| LinkError::SyscallError {
-                call: "BPF_OBJ_GET_INFO_BY_FD",
-                code: 0,
-                io_error,
-            })?;
+        let info = bpf_link_get_info_by_fd(fd_link.fd)?;
         if info.type_ == (bpf_link_type::BPF_LINK_TYPE_TRACING as u32) {
             return Ok(UProbeLink::new(PerfLinkInner::FdLink(fd_link)));
         }

+ 4 - 5
aya/src/programs/utils.rs

@@ -3,7 +3,7 @@ use std::{ffi::CStr, io, os::fd::RawFd, path::Path};
 
 use crate::{
     programs::{FdLink, Link, ProgramData, ProgramError},
-    sys::bpf_raw_tracepoint_open,
+    sys::{bpf_raw_tracepoint_open, SyscallError},
 };
 
 /// Attaches the program to a raw tracepoint.
@@ -13,12 +13,11 @@ pub(crate) fn attach_raw_tracepoint<T: Link + From<FdLink>>(
 ) -> Result<T::Id, ProgramError> {
     let prog_fd = program_data.fd_or_err()?;
 
-    let pfd = bpf_raw_tracepoint_open(tp_name, prog_fd).map_err(|(_code, io_error)| {
-        ProgramError::SyscallError {
+    let pfd =
+        bpf_raw_tracepoint_open(tp_name, prog_fd).map_err(|(_code, io_error)| SyscallError {
             call: "bpf_raw_tracepoint_open",
             io_error,
-        }
-    })? as RawFd;
+        })? as RawFd;
 
     program_data.links.insert(FdLink::new(pfd).into())
 }

+ 4 - 9
aya/src/programs/xdp.rs

@@ -1,6 +1,6 @@
 //! eXpress Data Path (XDP) programs.
 
-use crate::util::KernelVersion;
+use crate::{sys::SyscallError, util::KernelVersion};
 use bitflags;
 use libc::if_nametoindex;
 use std::{convert::TryFrom, ffi::CString, hash::Hash, io, mem, os::fd::RawFd};
@@ -127,7 +127,7 @@ impl Xdp {
 
         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 {
+                |(_, io_error)| SyscallError {
                     call: "bpf_link_create",
                     io_error,
                 },
@@ -173,7 +173,7 @@ impl Xdp {
             XdpLinkInner::FdLink(fd_link) => {
                 let link_fd = fd_link.fd;
                 bpf_link_update(link_fd, prog_fd, None, 0).map_err(|(_, io_error)| {
-                    ProgramError::SyscallError {
+                    SyscallError {
                         call: "bpf_link_update",
                         io_error,
                     }
@@ -279,12 +279,7 @@ impl TryFrom<FdLink> for XdpLink {
 
     fn try_from(fd_link: FdLink) -> Result<Self, Self::Error> {
         // unwrap of fd_link.fd will not panic since it's only None when being dropped.
-        let info =
-            bpf_link_get_info_by_fd(fd_link.fd).map_err(|io_error| LinkError::SyscallError {
-                call: "BPF_OBJ_GET_INFO_BY_FD",
-                code: 0,
-                io_error,
-            })?;
+        let info = bpf_link_get_info_by_fd(fd_link.fd)?;
         if info.type_ == (bpf_link_type::BPF_LINK_TYPE_XDP as u32) {
             return Ok(XdpLink::new(XdpLinkInner::FdLink(fd_link)));
         }

+ 121 - 65
aya/src/sys/bpf.rs

@@ -1,7 +1,7 @@
 use std::{
     cmp::{self, min},
     ffi::{CStr, CString},
-    io,
+    io, iter,
     mem::{self, MaybeUninit},
     os::fd::{AsRawFd, BorrowedFd, FromRawFd as _, OwnedFd, RawFd},
     slice,
@@ -28,7 +28,7 @@ use crate::{
         },
         copy_instructions,
     },
-    sys::{syscall, SysResult, Syscall},
+    sys::{syscall, SysResult, Syscall, SyscallError},
     Btf, Pod, VerifierLogLevel, BPF_OBJ_NAME_LEN,
 };
 
@@ -90,7 +90,7 @@ pub(crate) fn bpf_create_map(
             .copy_from_slice(unsafe { slice::from_raw_parts(name.as_ptr(), name_len) });
     }
 
-    sys_bpf(bpf_cmd::BPF_MAP_CREATE, &attr)
+    sys_bpf(bpf_cmd::BPF_MAP_CREATE, &mut attr)
 }
 
 pub(crate) fn bpf_pin_object(fd: RawFd, path: &CStr) -> SysResult<c_long> {
@@ -98,14 +98,14 @@ pub(crate) fn bpf_pin_object(fd: RawFd, path: &CStr) -> SysResult<c_long> {
     let u = unsafe { &mut attr.__bindgen_anon_4 };
     u.bpf_fd = fd as u32;
     u.pathname = path.as_ptr() as u64;
-    sys_bpf(bpf_cmd::BPF_OBJ_PIN, &attr)
+    sys_bpf(bpf_cmd::BPF_OBJ_PIN, &mut attr)
 }
 
 pub(crate) fn bpf_get_object(path: &CStr) -> SysResult<c_long> {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
     let u = unsafe { &mut attr.__bindgen_anon_4 };
     u.pathname = path.as_ptr() as u64;
-    sys_bpf(bpf_cmd::BPF_OBJ_GET, &attr)
+    sys_bpf(bpf_cmd::BPF_OBJ_GET, &mut attr)
 }
 
 pub(crate) struct BpfLoadProgramAttrs<'a> {
@@ -188,7 +188,7 @@ pub(crate) fn bpf_load_program(
     if let Some(v) = aya_attr.attach_btf_id {
         u.attach_btf_id = v;
     }
-    sys_bpf(bpf_cmd::BPF_PROG_LOAD, &attr)
+    sys_bpf(bpf_cmd::BPF_PROG_LOAD, &mut attr)
 }
 
 fn lookup<K: Pod, V: Pod>(
@@ -208,7 +208,7 @@ fn lookup<K: Pod, V: Pod>(
     u.__bindgen_anon_1.value = &mut value as *mut _ as u64;
     u.flags = flags;
 
-    match sys_bpf(cmd, &attr) {
+    match sys_bpf(cmd, &mut attr) {
         Ok(_) => Ok(Some(unsafe { value.assume_init() })),
         Err((_, io_error)) if io_error.raw_os_error() == Some(ENOENT) => Ok(None),
         Err(e) => Err(e),
@@ -260,7 +260,7 @@ pub(crate) fn bpf_map_lookup_elem_ptr<K: Pod, V>(
     u.__bindgen_anon_1.value = value as u64;
     u.flags = flags;
 
-    match sys_bpf(bpf_cmd::BPF_MAP_LOOKUP_ELEM, &attr) {
+    match sys_bpf(bpf_cmd::BPF_MAP_LOOKUP_ELEM, &mut attr) {
         Ok(_) => Ok(Some(())),
         Err((_, io_error)) if io_error.raw_os_error() == Some(ENOENT) => Ok(None),
         Err(e) => Err(e),
@@ -283,7 +283,7 @@ pub(crate) fn bpf_map_update_elem<K: Pod, V: Pod>(
     u.__bindgen_anon_1.value = value as *const _ as u64;
     u.flags = flags;
 
-    sys_bpf(bpf_cmd::BPF_MAP_UPDATE_ELEM, &attr)
+    sys_bpf(bpf_cmd::BPF_MAP_UPDATE_ELEM, &mut attr)
 }
 
 pub(crate) fn bpf_map_push_elem<V: Pod>(fd: RawFd, value: &V, flags: u64) -> SysResult<c_long> {
@@ -294,7 +294,7 @@ pub(crate) fn bpf_map_push_elem<V: Pod>(fd: RawFd, value: &V, flags: u64) -> Sys
     u.__bindgen_anon_1.value = value as *const _ as u64;
     u.flags = flags;
 
-    sys_bpf(bpf_cmd::BPF_MAP_UPDATE_ELEM, &attr)
+    sys_bpf(bpf_cmd::BPF_MAP_UPDATE_ELEM, &mut attr)
 }
 
 pub(crate) fn bpf_map_update_elem_ptr<K, V>(
@@ -311,7 +311,7 @@ pub(crate) fn bpf_map_update_elem_ptr<K, V>(
     u.__bindgen_anon_1.value = value as u64;
     u.flags = flags;
 
-    sys_bpf(bpf_cmd::BPF_MAP_UPDATE_ELEM, &attr)
+    sys_bpf(bpf_cmd::BPF_MAP_UPDATE_ELEM, &mut attr)
 }
 
 pub(crate) fn bpf_map_update_elem_per_cpu<K: Pod, V: Pod>(
@@ -331,7 +331,7 @@ pub(crate) fn bpf_map_delete_elem<K: Pod>(fd: RawFd, key: &K) -> SysResult<c_lon
     u.map_fd = fd as u32;
     u.key = key as *const _ as u64;
 
-    sys_bpf(bpf_cmd::BPF_MAP_DELETE_ELEM, &attr)
+    sys_bpf(bpf_cmd::BPF_MAP_DELETE_ELEM, &mut attr)
 }
 
 pub(crate) fn bpf_map_get_next_key<K: Pod>(
@@ -348,7 +348,7 @@ pub(crate) fn bpf_map_get_next_key<K: Pod>(
     }
     u.__bindgen_anon_1.next_key = &mut next_key as *mut _ as u64;
 
-    match sys_bpf(bpf_cmd::BPF_MAP_GET_NEXT_KEY, &attr) {
+    match sys_bpf(bpf_cmd::BPF_MAP_GET_NEXT_KEY, &mut attr) {
         Ok(_) => Ok(Some(unsafe { next_key.assume_init() })),
         Err((_, io_error)) if io_error.raw_os_error() == Some(ENOENT) => Ok(None),
         Err(e) => Err(e),
@@ -360,7 +360,7 @@ pub(crate) fn bpf_map_freeze(fd: RawFd) -> SysResult<c_long> {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
     let u = unsafe { &mut attr.__bindgen_anon_2 };
     u.map_fd = fd as u32;
-    sys_bpf(bpf_cmd::BPF_MAP_FREEZE, &attr)
+    sys_bpf(bpf_cmd::BPF_MAP_FREEZE, &mut attr)
 }
 
 // since kernel 5.7
@@ -381,7 +381,7 @@ pub(crate) fn bpf_link_create(
         attr.link_create.__bindgen_anon_3.target_btf_id = btf_id;
     }
 
-    sys_bpf(bpf_cmd::BPF_LINK_CREATE, &attr)
+    sys_bpf(bpf_cmd::BPF_LINK_CREATE, &mut attr)
 }
 
 // since kernel 5.7
@@ -402,7 +402,7 @@ pub(crate) fn bpf_link_update(
         attr.link_update.flags = flags;
     }
 
-    sys_bpf(bpf_cmd::BPF_LINK_UPDATE, &attr)
+    sys_bpf(bpf_cmd::BPF_LINK_UPDATE, &mut attr)
 }
 
 pub(crate) fn bpf_prog_attach(
@@ -416,7 +416,7 @@ pub(crate) fn bpf_prog_attach(
     attr.__bindgen_anon_5.target_fd = target_fd as u32;
     attr.__bindgen_anon_5.attach_type = attach_type as u32;
 
-    sys_bpf(bpf_cmd::BPF_PROG_ATTACH, &attr)
+    sys_bpf(bpf_cmd::BPF_PROG_ATTACH, &mut attr)
 }
 
 pub(crate) fn bpf_prog_detach(
@@ -430,7 +430,7 @@ pub(crate) fn bpf_prog_detach(
     attr.__bindgen_anon_5.target_fd = map_fd as u32;
     attr.__bindgen_anon_5.attach_type = attach_type as u32;
 
-    sys_bpf(bpf_cmd::BPF_PROG_DETACH, &attr)
+    sys_bpf(bpf_cmd::BPF_PROG_DETACH, &mut attr)
 }
 
 pub(crate) fn bpf_prog_query(
@@ -449,7 +449,7 @@ pub(crate) fn bpf_prog_query(
     attr.query.prog_cnt = prog_ids.len() as u32;
     attr.query.prog_ids = prog_ids.as_mut_ptr() as u64;
 
-    let ret = sys_bpf(bpf_cmd::BPF_PROG_QUERY, &attr);
+    let ret = sys_bpf(bpf_cmd::BPF_PROG_QUERY, &mut attr);
 
     *prog_cnt = unsafe { attr.query.prog_cnt };
 
@@ -460,57 +460,71 @@ pub(crate) fn bpf_prog_query(
     ret
 }
 
-pub(crate) fn bpf_prog_get_fd_by_id(prog_id: u32) -> Result<OwnedFd, io::Error> {
+pub(crate) fn bpf_prog_get_fd_by_id(prog_id: u32) -> Result<OwnedFd, SyscallError> {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
 
     attr.__bindgen_anon_6.__bindgen_anon_1.prog_id = prog_id;
     // SAFETY: BPF_PROG_GET_FD_BY_ID returns a new file descriptor.
-    unsafe { fd_sys_bpf(bpf_cmd::BPF_PROG_GET_FD_BY_ID, &attr).map_err(|(_, e)| e) }
+    unsafe { fd_sys_bpf(bpf_cmd::BPF_PROG_GET_FD_BY_ID, &mut attr) }.map_err(|(code, io_error)| {
+        assert_eq!(code, -1);
+        SyscallError {
+            call: "bpf_prog_get_fd_by_id",
+            io_error,
+        }
+    })
 }
 
-pub(crate) fn bpf_prog_get_info_by_fd(prog_fd: RawFd) -> Result<bpf_prog_info, io::Error> {
+fn bpf_obj_get_info_by_fd<T>(fd: BorrowedFd<'_>) -> Result<T, SyscallError> {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
     // info gets entirely populated by the kernel
     let info = MaybeUninit::zeroed();
 
-    attr.info.bpf_fd = prog_fd as u32;
+    attr.info.bpf_fd = fd.as_raw_fd() as u32;
     attr.info.info = &info as *const _ as u64;
-    attr.info.info_len = mem::size_of::<bpf_prog_info>() as u32;
+    attr.info.info_len = mem::size_of_val(&info) as u32;
 
-    match sys_bpf(bpf_cmd::BPF_OBJ_GET_INFO_BY_FD, &attr) {
-        Ok(_) => Ok(unsafe { info.assume_init() }),
-        Err((_, err)) => Err(err),
+    match sys_bpf(bpf_cmd::BPF_OBJ_GET_INFO_BY_FD, &mut attr) {
+        Ok(code) => {
+            assert_eq!(code, 0);
+            Ok(unsafe { info.assume_init() })
+        }
+        Err((code, io_error)) => {
+            assert_eq!(code, -1);
+            Err(SyscallError {
+                call: "bpf_obj_get_info_by_fd",
+                io_error,
+            })
+        }
     }
 }
 
-pub(crate) fn bpf_map_get_info_by_fd(prog_fd: RawFd) -> Result<bpf_map_info, io::Error> {
-    let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
-    // info gets entirely populated by the kernel
-    let info = MaybeUninit::zeroed();
-
-    attr.info.bpf_fd = prog_fd as u32;
-    attr.info.info = info.as_ptr() as *const _ as u64;
-    attr.info.info_len = mem::size_of::<bpf_map_info>() as u32;
+pub(crate) fn bpf_prog_get_info_by_fd(fd: RawFd) -> Result<bpf_prog_info, SyscallError> {
+    let fd = unsafe { BorrowedFd::borrow_raw(fd) };
+    bpf_obj_get_info_by_fd::<bpf_prog_info>(fd)
+}
 
-    match sys_bpf(bpf_cmd::BPF_OBJ_GET_INFO_BY_FD, &attr) {
-        Ok(_) => Ok(unsafe { info.assume_init() }),
-        Err((_, err)) => Err(err),
-    }
+pub(crate) fn bpf_map_get_info_by_fd(fd: RawFd) -> Result<bpf_map_info, SyscallError> {
+    let fd = unsafe { BorrowedFd::borrow_raw(fd) };
+    bpf_obj_get_info_by_fd::<bpf_map_info>(fd)
 }
 
-pub(crate) fn bpf_link_get_info_by_fd(link_fd: RawFd) -> Result<bpf_link_info, io::Error> {
+pub(crate) fn bpf_link_get_fd_by_id(link_id: u32) -> Result<OwnedFd, SyscallError> {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
-    // info gets entirely populated by the kernel
-    let info = unsafe { MaybeUninit::zeroed().assume_init() };
 
-    attr.info.bpf_fd = link_fd as u32;
-    attr.info.info = &info as *const _ as u64;
-    attr.info.info_len = mem::size_of::<bpf_link_info>() as u32;
+    attr.__bindgen_anon_6.__bindgen_anon_1.link_id = link_id;
+    // SAFETY: BPF_LINK_GET_FD_BY_ID returns a new file descriptor.
+    unsafe { fd_sys_bpf(bpf_cmd::BPF_LINK_GET_FD_BY_ID, &mut attr) }.map_err(|(code, io_error)| {
+        assert_eq!(code, -1);
+        SyscallError {
+            call: "bpf_link_get_fd_by_id",
+            io_error,
+        }
+    })
+}
 
-    match sys_bpf(bpf_cmd::BPF_OBJ_GET_INFO_BY_FD, &attr) {
-        Ok(_) => Ok(info),
-        Err((_, err)) => Err(err),
-    }
+pub(crate) fn bpf_link_get_info_by_fd(fd: RawFd) -> Result<bpf_link_info, SyscallError> {
+    let fd = unsafe { BorrowedFd::borrow_raw(fd) };
+    bpf_obj_get_info_by_fd::<bpf_link_info>(fd)
 }
 
 pub(crate) fn btf_obj_get_info_by_fd(
@@ -526,7 +540,7 @@ pub(crate) fn btf_obj_get_info_by_fd(
     attr.info.info = &info as *const bpf_btf_info as u64;
     attr.info.info_len = mem::size_of::<bpf_btf_info>() as u32;
 
-    match sys_bpf(bpf_cmd::BPF_OBJ_GET_INFO_BY_FD, &attr) {
+    match sys_bpf(bpf_cmd::BPF_OBJ_GET_INFO_BY_FD, &mut attr) {
         Ok(_) => Ok(info),
         Err((_, err)) => Err(err),
     }
@@ -541,7 +555,7 @@ pub(crate) fn bpf_raw_tracepoint_open(name: Option<&CStr>, prog_fd: RawFd) -> Sy
     };
     attr.raw_tracepoint.prog_fd = prog_fd as u32;
 
-    sys_bpf(bpf_cmd::BPF_RAW_TRACEPOINT_OPEN, &attr)
+    sys_bpf(bpf_cmd::BPF_RAW_TRACEPOINT_OPEN, &mut attr)
 }
 
 pub(crate) fn bpf_load_btf(
@@ -559,11 +573,11 @@ pub(crate) fn bpf_load_btf(
         u.btf_log_size = log_buf.len() as u32;
     }
     // SAFETY: `BPF_BTF_LOAD` returns a newly created fd.
-    unsafe { fd_sys_bpf(bpf_cmd::BPF_BTF_LOAD, &attr) }
+    unsafe { fd_sys_bpf(bpf_cmd::BPF_BTF_LOAD, &mut attr) }
 }
 
 // SAFETY: only use for bpf_cmd that return a new file descriptor on success.
-unsafe fn fd_sys_bpf(cmd: bpf_cmd, attr: &bpf_attr) -> SysResult<OwnedFd> {
+unsafe fn fd_sys_bpf(cmd: bpf_cmd, attr: &mut bpf_attr) -> SysResult<OwnedFd> {
     let fd = sys_bpf(cmd, attr)?;
     let fd = fd.try_into().map_err(|_| {
         (
@@ -581,7 +595,7 @@ pub(crate) fn bpf_btf_get_fd_by_id(id: u32) -> Result<RawFd, io::Error> {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
     attr.__bindgen_anon_6.__bindgen_anon_1.btf_id = id;
 
-    match sys_bpf(bpf_cmd::BPF_BTF_GET_FD_BY_ID, &attr) {
+    match sys_bpf(bpf_cmd::BPF_BTF_GET_FD_BY_ID, &mut attr) {
         Ok(v) => Ok(v as RawFd),
         Err((_, err)) => Err(err),
     }
@@ -612,7 +626,7 @@ pub(crate) fn is_prog_name_supported() -> bool {
     u.insns = insns.as_ptr() as u64;
     u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32;
 
-    match sys_bpf(bpf_cmd::BPF_PROG_LOAD, &attr) {
+    match sys_bpf(bpf_cmd::BPF_PROG_LOAD, &mut attr) {
         Ok(v) => {
             let fd = v as RawFd;
             unsafe { close(fd) };
@@ -643,7 +657,7 @@ pub(crate) fn is_probe_read_kernel_supported() -> bool {
     u.insns = insns.as_ptr() as u64;
     u.prog_type = bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT as u32;
 
-    match sys_bpf(bpf_cmd::BPF_PROG_LOAD, &attr) {
+    match sys_bpf(bpf_cmd::BPF_PROG_LOAD, &mut attr) {
         Ok(v) => {
             let fd = v as RawFd;
             unsafe { close(fd) };
@@ -670,7 +684,7 @@ pub(crate) fn is_perf_link_supported() -> bool {
     u.insns = insns.as_ptr() as u64;
     u.prog_type = bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT as u32;
 
-    if let Ok(fd) = sys_bpf(bpf_cmd::BPF_PROG_LOAD, &attr) {
+    if let Ok(fd) = sys_bpf(bpf_cmd::BPF_PROG_LOAD, &mut attr) {
         if let Err((_, e)) =
             // Uses an invalid target FD so we get EBADF if supported.
             bpf_link_create(fd as i32, -1, bpf_attach_type::BPF_PERF_EVENT, None, 0)
@@ -726,7 +740,7 @@ pub(crate) fn is_bpf_global_data_supported() -> bool {
         u.insns = insns.as_ptr() as u64;
         u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32;
 
-        if let Ok(v) = sys_bpf(bpf_cmd::BPF_PROG_LOAD, &attr) {
+        if let Ok(v) = sys_bpf(bpf_cmd::BPF_PROG_LOAD, &mut attr) {
             let fd = v as RawFd;
 
             unsafe { close(fd) };
@@ -755,7 +769,7 @@ pub(crate) fn is_bpf_cookie_supported() -> bool {
     u.insns = insns.as_ptr() as u64;
     u.prog_type = bpf_prog_type::BPF_PROG_TYPE_KPROBE as u32;
 
-    match sys_bpf(bpf_cmd::BPF_PROG_LOAD, &attr) {
+    match sys_bpf(bpf_cmd::BPF_PROG_LOAD, &mut attr) {
         Ok(v) => {
             let fd = v as RawFd;
             unsafe { close(fd) };
@@ -905,21 +919,63 @@ pub(crate) fn is_btf_type_tag_supported() -> bool {
     bpf_load_btf(btf_bytes.as_slice(), &mut [], Default::default()).is_ok()
 }
 
-pub fn sys_bpf(cmd: bpf_cmd, attr: &bpf_attr) -> SysResult<c_long> {
+fn sys_bpf(cmd: bpf_cmd, attr: &mut bpf_attr) -> SysResult<c_long> {
     syscall(Syscall::Bpf { cmd, attr })
 }
 
-pub(crate) fn bpf_prog_get_next_id(id: u32) -> Result<Option<u32>, (c_long, io::Error)> {
+fn bpf_obj_get_next_id(
+    id: u32,
+    cmd: bpf_cmd,
+    name: &'static str,
+) -> Result<Option<u32>, SyscallError> {
     let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
     let u = unsafe { &mut attr.__bindgen_anon_6 };
     u.__bindgen_anon_1.start_id = id;
-    match sys_bpf(bpf_cmd::BPF_PROG_GET_NEXT_ID, &attr) {
-        Ok(_) => Ok(Some(unsafe { attr.__bindgen_anon_6.next_id })),
-        Err((_, io_error)) if io_error.raw_os_error() == Some(ENOENT) => Ok(None),
-        Err(e) => Err(e),
+    match sys_bpf(cmd, &mut attr) {
+        Ok(code) => {
+            assert_eq!(code, 0);
+            Ok(Some(unsafe { attr.__bindgen_anon_6.next_id }))
+        }
+        Err((code, io_error)) => {
+            assert_eq!(code, -1);
+            if io_error.raw_os_error() == Some(ENOENT) {
+                Ok(None)
+            } else {
+                Err(SyscallError {
+                    call: name,
+                    io_error,
+                })
+            }
+        }
     }
 }
 
+fn iter_obj_ids(
+    cmd: bpf_cmd,
+    name: &'static str,
+) -> impl Iterator<Item = Result<u32, SyscallError>> {
+    let mut current_id = Some(0);
+    iter::from_fn(move || {
+        let next_id = {
+            let current_id = current_id?;
+            bpf_obj_get_next_id(current_id, cmd, name).transpose()
+        };
+        current_id = next_id.as_ref().and_then(|next_id| match next_id {
+            Ok(next_id) => Some(*next_id),
+            Err(SyscallError { .. }) => None,
+        });
+        next_id
+    })
+}
+
+pub(crate) fn iter_prog_ids() -> impl Iterator<Item = Result<u32, SyscallError>> {
+    iter_obj_ids(bpf_cmd::BPF_PROG_GET_NEXT_ID, "bpf_prog_get_next_id")
+}
+
+pub(crate) fn iter_link_ids() -> impl Iterator<Item = Result<u32, SyscallError>> {
+    iter_obj_ids(bpf_cmd::BPF_LINK_GET_NEXT_ID, "bpf_link_get_next_id")
+}
+
 pub(crate) fn retry_with_verifier_logs<T>(
     max_retries: usize,
     f: impl Fn(&mut [u8]) -> SysResult<T>,

+ 13 - 3
aya/src/sys/mod.rs

@@ -5,12 +5,12 @@ mod perf_event;
 #[cfg(test)]
 mod fake;
 
+use libc::{c_int, c_long, pid_t, SYS_bpf, SYS_perf_event_open};
 use std::{
     io, mem,
     os::fd::{AsRawFd as _, BorrowedFd},
 };
-
-use libc::{c_int, c_long, pid_t, SYS_bpf, SYS_perf_event_open};
+use thiserror::Error;
 
 pub(crate) use bpf::*;
 #[cfg(test)]
@@ -27,7 +27,7 @@ pub(crate) type SysResult<T> = Result<T, (c_long, io::Error)>;
 pub(crate) enum Syscall<'a> {
     Bpf {
         cmd: bpf_cmd,
-        attr: &'a bpf_attr,
+        attr: &'a mut bpf_attr,
     },
     PerfEventOpen {
         attr: perf_event_attr,
@@ -43,6 +43,16 @@ pub(crate) enum Syscall<'a> {
     },
 }
 
+#[derive(Debug, Error)]
+#[error("`{call}` failed")]
+pub struct SyscallError {
+    /// The name of the syscall which failed.
+    pub(crate) call: &'static str,
+    /// The [`io::Error`] returned by the syscall.
+    #[source]
+    pub(crate) io_error: io::Error,
+}
+
 impl std::fmt::Debug for Syscall<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {

+ 0 - 3
test/README.md

@@ -11,7 +11,6 @@ To run locally all you need is:
 
 1. Rust nightly
 1. `cargo install bpf-linker`
-1. `bpftool` [^1]
 
 ### Other OSs
 
@@ -52,5 +51,3 @@ Tests should follow these guidelines:
 - You may add a new module, or use an existing one.
 - Test functions should not return `anyhow::Result<()>` since this produces errors without stack
   traces. Prefer to `panic!` instead.
-
-[^1]: TODO(https://github.com/aya-rs/aya/issues/645): Remove this dependency.

+ 102 - 96
test/integration-test/src/tests/load.rs

@@ -1,17 +1,17 @@
-use std::{convert::TryInto as _, process::Command, thread, time};
+use std::{convert::TryInto as _, thread, time};
 
 use aya::{
     maps::Array,
     programs::{
         links::{FdLink, PinnedLink},
-        loaded_programs, KProbe, TracePoint, UProbe, Xdp, XdpFlags,
+        loaded_links, loaded_programs, KProbe, TracePoint, UProbe, Xdp, XdpFlags,
     },
     util::KernelVersion,
     Bpf,
 };
 
-const MAX_RETRIES: u32 = 100;
-const RETRY_DURATION_MS: u64 = 10;
+const MAX_RETRIES: usize = 100;
+const RETRY_DURATION: time::Duration = time::Duration::from_millis(10);
 
 #[test]
 fn long_name() {
@@ -50,61 +50,67 @@ fn multiple_btf_maps() {
     assert_eq!(val_2, 42);
 }
 
-fn is_linked(prog_id: &u32) -> bool {
-    let output = Command::new("bpftool").args(["link"]).output();
-    let output = output.expect("Failed to run 'bpftool link'");
-    let stdout = String::from_utf8(output.stdout).unwrap();
-    stdout.contains(&prog_id.to_string())
-}
-
-macro_rules! assert_loaded_and_linked {
-    ($name:literal, $loaded:expr) => {
-        for i in 0..(MAX_RETRIES + 1) {
+fn poll_loaded_program_id(name: &str) -> impl Iterator<Item = Option<u32>> + '_ {
+    std::iter::once(true)
+        .chain(std::iter::repeat(false))
+        .map(|first| {
+            if !first {
+                thread::sleep(RETRY_DURATION);
+            }
             // Ignore race failures which can happen when the tests delete a
             // program in the middle of a `loaded_programs()` call.
-            let id = loaded_programs()
+            loaded_programs()
                 .filter_map(|prog| prog.ok())
-                .find(|prog| prog.name() == $name.as_bytes())
-                .map(|prog| Some(prog.id()));
-            let mut linked = false;
-            if let Some(prog_id) = id {
-                linked = is_linked(&prog_id.unwrap());
-                if linked == $loaded {
-                    break;
-                }
-            }
-
-            if i == MAX_RETRIES {
-                panic!(
-                    "Expected (loaded/linked: {}) but found (id: {}, linked: {}",
-                    $loaded,
-                    id.is_some(),
-                    linked
-                );
-            }
-            thread::sleep(time::Duration::from_millis(RETRY_DURATION_MS));
-        }
-    };
+                .find_map(|prog| (prog.name() == name.as_bytes()).then(|| prog.id()))
+        })
 }
 
-macro_rules! assert_loaded {
-    ($name:literal, $loaded:expr) => {
-        for i in 0..(MAX_RETRIES + 1) {
+#[track_caller]
+fn assert_loaded_and_linked(name: &str) {
+    let (attempts_used, prog_id) = poll_loaded_program_id(name)
+        .take(MAX_RETRIES)
+        .enumerate()
+        .find_map(|(i, id)| id.map(|id| (i, id)))
+        .unwrap_or_else(|| panic!("{name} not loaded after {MAX_RETRIES}"));
+    let poll_loaded_link_id = std::iter::once(true)
+        .chain(std::iter::repeat(false))
+        .map(|first| {
+            if !first {
+                thread::sleep(RETRY_DURATION);
+            }
             // Ignore race failures which can happen when the tests delete a
             // program in the middle of a `loaded_programs()` call.
-            let state = loaded_programs()
-                .filter_map(|prog| prog.ok())
-                .any(|prog| prog.name() == $name.as_bytes());
+            loaded_links()
+                .filter_map(|link| link.ok())
+                .find_map(|link| (link.prog_id == prog_id).then_some(link.id))
+        });
+    assert!(
+        poll_loaded_link_id
+            .take(MAX_RETRIES)
+            .skip(attempts_used)
+            .any(|id| id.is_some()),
+        "{name} not linked after {MAX_RETRIES}"
+    );
+}
 
-            if state == $loaded {
-                break;
-            }
-            if i == MAX_RETRIES {
-                panic!("Expected loaded: {} but was loaded: {}", $loaded, state);
-            }
-            thread::sleep(time::Duration::from_millis(RETRY_DURATION_MS));
-        }
-    };
+#[track_caller]
+fn assert_loaded(name: &str) {
+    assert!(
+        poll_loaded_program_id(name)
+            .take(MAX_RETRIES)
+            .any(|id| id.is_some()),
+        "{name} not loaded after {MAX_RETRIES}"
+    )
+}
+
+#[track_caller]
+fn assert_unloaded(name: &str) {
+    assert!(
+        poll_loaded_program_id(name)
+            .take(MAX_RETRIES)
+            .any(|id| id.is_none()),
+        "{name} still loaded after {MAX_RETRIES}"
+    )
 }
 
 #[test]
@@ -112,24 +118,24 @@ fn unload_xdp() {
     let mut bpf = Bpf::load(crate::TEST).unwrap();
     let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap();
     prog.load().unwrap();
-    assert_loaded!("pass", true);
+    assert_loaded("pass");
     let link = prog.attach("lo", XdpFlags::default()).unwrap();
     {
         let _link_owned = prog.take_link(link).unwrap();
         prog.unload().unwrap();
-        assert_loaded_and_linked!("pass", true);
+        assert_loaded_and_linked("pass");
     };
 
-    assert_loaded!("pass", false);
+    assert_unloaded("pass");
     prog.load().unwrap();
 
-    assert_loaded!("pass", true);
+    assert_loaded("pass");
     prog.attach("lo", XdpFlags::default()).unwrap();
 
-    assert_loaded!("pass", true);
+    assert_loaded("pass");
     prog.unload().unwrap();
 
-    assert_loaded!("pass", false);
+    assert_unloaded("pass");
 }
 
 #[test]
@@ -137,24 +143,24 @@ fn unload_kprobe() {
     let mut bpf = Bpf::load(crate::TEST).unwrap();
     let prog: &mut KProbe = bpf.program_mut("test_kprobe").unwrap().try_into().unwrap();
     prog.load().unwrap();
-    assert_loaded!("test_kprobe", true);
+    assert_loaded("test_kprobe");
     let link = prog.attach("try_to_wake_up", 0).unwrap();
     {
         let _link_owned = prog.take_link(link).unwrap();
         prog.unload().unwrap();
-        assert_loaded_and_linked!("test_kprobe", true);
+        assert_loaded_and_linked("test_kprobe");
     };
 
-    assert_loaded!("test_kprobe", false);
+    assert_unloaded("test_kprobe");
     prog.load().unwrap();
 
-    assert_loaded!("test_kprobe", true);
+    assert_loaded("test_kprobe");
     prog.attach("try_to_wake_up", 0).unwrap();
 
-    assert_loaded!("test_kprobe", true);
+    assert_loaded("test_kprobe");
     prog.unload().unwrap();
 
-    assert_loaded!("test_kprobe", false);
+    assert_unloaded("test_kprobe");
 }
 
 #[test]
@@ -167,25 +173,25 @@ fn basic_tracepoint() {
         .unwrap();
 
     prog.load().unwrap();
-    assert_loaded!("test_tracepoint", true);
+    assert_loaded("test_tracepoint");
     let link = prog.attach("syscalls", "sys_enter_kill").unwrap();
 
     {
         let _link_owned = prog.take_link(link).unwrap();
         prog.unload().unwrap();
-        assert_loaded_and_linked!("test_tracepoint", true);
+        assert_loaded_and_linked("test_tracepoint");
     };
 
-    assert_loaded!("test_tracepoint", false);
+    assert_unloaded("test_tracepoint");
     prog.load().unwrap();
 
-    assert_loaded!("test_tracepoint", true);
+    assert_loaded("test_tracepoint");
     prog.attach("syscalls", "sys_enter_kill").unwrap();
 
-    assert_loaded!("test_tracepoint", true);
+    assert_loaded("test_tracepoint");
     prog.unload().unwrap();
 
-    assert_loaded!("test_tracepoint", false);
+    assert_unloaded("test_tracepoint");
 }
 
 #[test]
@@ -194,25 +200,25 @@ fn basic_uprobe() {
     let prog: &mut UProbe = bpf.program_mut("test_uprobe").unwrap().try_into().unwrap();
 
     prog.load().unwrap();
-    assert_loaded!("test_uprobe", true);
+    assert_loaded("test_uprobe");
     let link = prog.attach(Some("sleep"), 0, "libc", None).unwrap();
 
     {
         let _link_owned = prog.take_link(link).unwrap();
         prog.unload().unwrap();
-        assert_loaded_and_linked!("test_uprobe", true);
+        assert_loaded_and_linked("test_uprobe");
     };
 
-    assert_loaded!("test_uprobe", false);
+    assert_unloaded("test_uprobe");
     prog.load().unwrap();
 
-    assert_loaded!("test_uprobe", true);
+    assert_loaded("test_uprobe");
     prog.attach(Some("sleep"), 0, "libc", None).unwrap();
 
-    assert_loaded!("test_uprobe", true);
+    assert_loaded("test_uprobe");
     prog.unload().unwrap();
 
-    assert_loaded!("test_uprobe", false);
+    assert_unloaded("test_uprobe");
 }
 
 #[test]
@@ -228,22 +234,22 @@ fn pin_link() {
     prog.load().unwrap();
     let link_id = prog.attach("lo", XdpFlags::default()).unwrap();
     let link = prog.take_link(link_id).unwrap();
-    assert_loaded!("pass", true);
+    assert_loaded("pass");
 
     let fd_link: FdLink = link.try_into().unwrap();
     let pinned = fd_link.pin("/sys/fs/bpf/aya-xdp-test-lo").unwrap();
 
     // because of the pin, the program is still attached
     prog.unload().unwrap();
-    assert_loaded!("pass", true);
+    assert_loaded("pass");
 
     // delete the pin, but the program is still attached
     let new_link = pinned.unpin().unwrap();
-    assert_loaded!("pass", true);
+    assert_loaded("pass");
 
     // finally when new_link is dropped we're detached
     drop(new_link);
-    assert_loaded!("pass", false);
+    assert_unloaded("pass");
 }
 
 #[test]
@@ -263,7 +269,7 @@ fn pin_lifecycle() {
     }
 
     // should still be loaded since prog was pinned
-    assert_loaded!("pass", true);
+    assert_loaded("pass");
 
     // 2. Load program from bpffs but don't attach it
     {
@@ -271,7 +277,7 @@ fn pin_lifecycle() {
     }
 
     // should still be loaded since prog was pinned
-    assert_loaded!("pass", true);
+    assert_loaded("pass");
 
     // 3. Load program from bpffs and attach
     {
@@ -286,7 +292,7 @@ fn pin_lifecycle() {
     }
 
     // should still be loaded since link was pinned
-    assert_loaded_and_linked!("pass", true);
+    assert_loaded_and_linked("pass");
 
     // 4. Load a new version of the program, unpin link, and atomically replace old program
     {
@@ -299,11 +305,11 @@ fn pin_lifecycle() {
             .unpin()
             .unwrap();
         prog.attach_to_link(link.try_into().unwrap()).unwrap();
-        assert_loaded!("pass", true);
+        assert_loaded("pass");
     }
 
     // program should be unloaded
-    assert_loaded!("pass", false);
+    assert_unloaded("pass");
 }
 
 #[test]
@@ -321,7 +327,7 @@ fn pin_lifecycle_tracepoint() {
     }
 
     // should still be loaded since prog was pinned
-    assert_loaded!("test_tracepoint", true);
+    assert_loaded("test_tracepoint");
 
     // 2. Load program from bpffs but don't attach it
     {
@@ -329,7 +335,7 @@ fn pin_lifecycle_tracepoint() {
     }
 
     // should still be loaded since prog was pinned
-    assert_loaded!("test_tracepoint", true);
+    assert_loaded("test_tracepoint");
 
     // 3. Load program from bpffs and attach
     {
@@ -346,7 +352,7 @@ fn pin_lifecycle_tracepoint() {
     }
 
     // should still be loaded since link was pinned
-    assert_loaded_and_linked!("test_tracepoint", true);
+    assert_loaded_and_linked("test_tracepoint");
 
     // 4. unpin link, and make sure everything is unloaded
     {
@@ -357,7 +363,7 @@ fn pin_lifecycle_tracepoint() {
     }
 
     // program should be unloaded
-    assert_loaded!("test_tracepoint", false);
+    assert_unloaded("test_tracepoint");
 }
 
 #[test]
@@ -371,7 +377,7 @@ fn pin_lifecycle_kprobe() {
     }
 
     // should still be loaded since prog was pinned
-    assert_loaded!("test_kprobe", true);
+    assert_loaded("test_kprobe");
 
     // 2. Load program from bpffs but don't attach it
     {
@@ -383,7 +389,7 @@ fn pin_lifecycle_kprobe() {
     }
 
     // should still be loaded since prog was pinned
-    assert_loaded!("test_kprobe", true);
+    assert_loaded("test_kprobe");
 
     // 3. Load program from bpffs and attach
     {
@@ -404,7 +410,7 @@ fn pin_lifecycle_kprobe() {
     }
 
     // should still be loaded since link was pinned
-    assert_loaded_and_linked!("test_kprobe", true);
+    assert_loaded_and_linked("test_kprobe");
 
     // 4. unpin link, and make sure everything is unloaded
     {
@@ -415,7 +421,7 @@ fn pin_lifecycle_kprobe() {
     }
 
     // program should be unloaded
-    assert_loaded!("test_kprobe", false);
+    assert_unloaded("test_kprobe");
 }
 
 #[test]
@@ -429,7 +435,7 @@ fn pin_lifecycle_uprobe() {
     }
 
     // should still be loaded since prog was pinned
-    assert_loaded!("test_uprobe", true);
+    assert_loaded("test_uprobe");
 
     // 2. Load program from bpffs but don't attach it
     {
@@ -441,7 +447,7 @@ fn pin_lifecycle_uprobe() {
     }
 
     // should still be loaded since prog was pinned
-    assert_loaded!("test_uprobe", true);
+    assert_loaded("test_uprobe");
 
     // 3. Load program from bpffs and attach
     {
@@ -462,7 +468,7 @@ fn pin_lifecycle_uprobe() {
     }
 
     // should still be loaded since link was pinned
-    assert_loaded_and_linked!("test_uprobe", true);
+    assert_loaded_and_linked("test_uprobe");
 
     // 4. unpin link, and make sure everything is unloaded
     {
@@ -473,5 +479,5 @@ fn pin_lifecycle_uprobe() {
     }
 
     // program should be unloaded
-    assert_loaded!("test_uprobe", false);
+    assert_unloaded("test_uprobe");
 }

+ 0 - 2
test/run.sh

@@ -190,8 +190,6 @@ EOF
     echo "Enabling testing repositories"
     exec_vm sudo dnf config-manager --set-enabled updates-testing
     exec_vm sudo dnf config-manager --set-enabled updates-testing-modular
-    echo "Installing dependencies"
-    exec_vm sudo dnf install -qy bpftool
 }
 
 scp_vm() {

+ 6 - 44
xtask/public-api/aya.txt

@@ -996,9 +996,7 @@ pub aya::maps::MapError::PinError
 pub aya::maps::MapError::PinError::error: aya::pin::PinError
 pub aya::maps::MapError::PinError::name: core::option::Option<alloc::string::String>
 pub aya::maps::MapError::ProgramNotLoaded
-pub aya::maps::MapError::SyscallError
-pub aya::maps::MapError::SyscallError::call: &'static str
-pub aya::maps::MapError::SyscallError::io_error: std::io::error::Error
+pub aya::maps::MapError::SyscallError(crate::sys::SyscallError)
 pub aya::maps::MapError::Unsupported
 pub aya::maps::MapError::Unsupported::map_type: u32
 impl core::convert::From<aya::maps::MapError> for aya::BpfError
@@ -1764,9 +1762,7 @@ pub aya::pin::PinError::InvalidPinPath
 pub aya::pin::PinError::InvalidPinPath::error: alloc::string::String
 pub aya::pin::PinError::NoFd
 pub aya::pin::PinError::NoFd::name: alloc::string::String
-pub aya::pin::PinError::SyscallError
-pub aya::pin::PinError::SyscallError::io_error: std::io::error::Error
-pub aya::pin::PinError::SyscallError::name: &'static str
+pub aya::pin::PinError::SyscallError(crate::sys::SyscallError)
 impl core::error::Error for aya::pin::PinError
 pub fn aya::pin::PinError::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)>
 impl core::fmt::Display for aya::pin::PinError
@@ -3008,10 +3004,7 @@ pub fn aya::programs::kprobe::KProbeLinkId::from(t: T) -> T
 pub mod aya::programs::links
 pub enum aya::programs::links::LinkError
 pub aya::programs::links::LinkError::InvalidLink
-pub aya::programs::links::LinkError::SyscallError
-pub aya::programs::links::LinkError::SyscallError::call: &'static str
-pub aya::programs::links::LinkError::SyscallError::code: libc::unix::linux_like::linux::gnu::b64::x86_64::not_x32::c_long
-pub aya::programs::links::LinkError::SyscallError::io_error: std::io::error::Error
+pub aya::programs::links::LinkError::SyscallError(crate::sys::SyscallError)
 impl core::error::Error for aya::programs::links::LinkError
 pub fn aya::programs::links::LinkError::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)>
 impl core::fmt::Display for aya::programs::links::LinkError
@@ -5174,9 +5167,7 @@ pub aya::programs::ProgramError::MapError(aya::maps::MapError)
 pub aya::programs::ProgramError::NotAttached
 pub aya::programs::ProgramError::NotLoaded
 pub aya::programs::ProgramError::SocketFilterError(aya::programs::SocketFilterError)
-pub aya::programs::ProgramError::SyscallError
-pub aya::programs::ProgramError::SyscallError::call: &'static str
-pub aya::programs::ProgramError::SyscallError::io_error: std::io::error::Error
+pub aya::programs::ProgramError::SyscallError(crate::sys::SyscallError)
 pub aya::programs::ProgramError::TcError(aya::programs::tc::TcError)
 pub aya::programs::ProgramError::TracePointError(aya::programs::trace_point::TracePointError)
 pub aya::programs::ProgramError::UProbeError(aya::programs::uprobe::UProbeError)
@@ -6237,35 +6228,6 @@ impl<T> core::borrow::BorrowMut<T> for aya::programs::ProgramInfo where T: core:
 pub fn aya::programs::ProgramInfo::borrow_mut(&mut self) -> &mut T
 impl<T> core::convert::From<T> for aya::programs::ProgramInfo
 pub fn aya::programs::ProgramInfo::from(t: T) -> T
-pub struct aya::programs::ProgramsIter
-impl core::iter::traits::iterator::Iterator for aya::programs::ProgramsIter
-pub type aya::programs::ProgramsIter::Item = core::result::Result<aya::programs::ProgramInfo, aya::programs::ProgramError>
-pub fn aya::programs::ProgramsIter::next(&mut self) -> core::option::Option<Self::Item>
-impl core::marker::Send for aya::programs::ProgramsIter
-impl core::marker::Sync for aya::programs::ProgramsIter
-impl core::marker::Unpin for aya::programs::ProgramsIter
-impl core::panic::unwind_safe::RefUnwindSafe for aya::programs::ProgramsIter
-impl core::panic::unwind_safe::UnwindSafe for aya::programs::ProgramsIter
-impl<I> core::iter::traits::collect::IntoIterator for aya::programs::ProgramsIter where I: core::iter::traits::iterator::Iterator
-pub type aya::programs::ProgramsIter::IntoIter = I
-pub type aya::programs::ProgramsIter::Item = <I as core::iter::traits::iterator::Iterator>::Item
-pub fn aya::programs::ProgramsIter::into_iter(self) -> I
-impl<T, U> core::convert::Into<U> for aya::programs::ProgramsIter where U: core::convert::From<T>
-pub fn aya::programs::ProgramsIter::into(self) -> U
-impl<T, U> core::convert::TryFrom<U> for aya::programs::ProgramsIter where U: core::convert::Into<T>
-pub type aya::programs::ProgramsIter::Error = core::convert::Infallible
-pub fn aya::programs::ProgramsIter::try_from(value: U) -> core::result::Result<T, <T as core::convert::TryFrom<U>>::Error>
-impl<T, U> core::convert::TryInto<U> for aya::programs::ProgramsIter where U: core::convert::TryFrom<T>
-pub type aya::programs::ProgramsIter::Error = <U as core::convert::TryFrom<T>>::Error
-pub fn aya::programs::ProgramsIter::try_into(self) -> core::result::Result<U, <U as core::convert::TryFrom<T>>::Error>
-impl<T> core::any::Any for aya::programs::ProgramsIter where T: 'static + core::marker::Sized
-pub fn aya::programs::ProgramsIter::type_id(&self) -> core::any::TypeId
-impl<T> core::borrow::Borrow<T> for aya::programs::ProgramsIter where T: core::marker::Sized
-pub fn aya::programs::ProgramsIter::borrow(&self) -> &T
-impl<T> core::borrow::BorrowMut<T> for aya::programs::ProgramsIter where T: core::marker::Sized
-pub fn aya::programs::ProgramsIter::borrow_mut(&mut self) -> &mut T
-impl<T> core::convert::From<T> for aya::programs::ProgramsIter
-pub fn aya::programs::ProgramsIter::from(t: T) -> T
 pub struct aya::programs::RawTracePoint
 impl aya::programs::RawTracePoint
 pub fn aya::programs::RawTracePoint::attach(&mut self, tp_name: &str) -> core::result::Result<RawTracePointLinkId, aya::programs::ProgramError>
@@ -6928,7 +6890,7 @@ impl aya::programs::links::Link for aya::programs::xdp::XdpLink
 pub type aya::programs::xdp::XdpLink::Id = aya::programs::xdp::XdpLinkId
 pub fn aya::programs::xdp::XdpLink::detach(self) -> core::result::Result<(), aya::programs::ProgramError>
 pub fn aya::programs::xdp::XdpLink::id(&self) -> Self::Id
-pub fn aya::programs::loaded_programs() -> aya::programs::ProgramsIter
+pub fn aya::programs::loaded_programs() -> impl core::iter::traits::iterator::Iterator<Item = core::result::Result<aya::programs::ProgramInfo, aya::programs::ProgramError>>
 pub mod aya::util
 pub struct aya::util::KernelVersion
 impl aya::util::KernelVersion
@@ -7259,4 +7221,4 @@ impl aya::Pod for u8
 impl<K: aya::Pod> aya::Pod for aya::maps::lpm_trie::Key<K>
 impl<T: aya::Pod, const N: usize> aya::Pod for [T; N]
 pub fn aya::features() -> &'static aya_obj::obj::Features
-pub fn aya::loaded_programs() -> aya::programs::ProgramsIter
+pub fn aya::loaded_programs() -> impl core::iter::traits::iterator::Iterator<Item = core::result::Result<aya::programs::ProgramInfo, aya::programs::ProgramError>>