Skip to content

Commit 9934a58

Browse files
Jongyd-e-s-o
authored andcommitted
map: Add lookup_into() method
Allows looking up into a preallocated buffer.
1 parent d625dd3 commit 9934a58

File tree

3 files changed

+168
-12
lines changed

3 files changed

+168
-12
lines changed

libbpf-rs/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Unreleased
1212
- Added `Program::{stdout,stderr}` for accessing BPF stdout and stderr
1313
streams
1414
- Added `OpenProgram::autoload` getter
15+
- Added `MapCore::lookup_into` for looking up values into preallocated
16+
buffers
1517

1618

1719
0.26.0-beta.0

libbpf-rs/src/map.rs

Lines changed: 82 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,14 @@ where
311311
key.as_ptr() as *const c_void
312312
}
313313

314-
/// Internal function to return a value from a map into a buffer of the given size.
315-
fn lookup_raw<M>(map: &M, key: &[u8], flags: MapFlags, out_size: usize) -> Result<Option<Vec<u8>>>
314+
/// Internal function to perform a map lookup and write the value into raw pointer.
315+
/// Returns `Ok(true)` if the key was found, `Ok(false)` if not found, or an error.
316+
fn lookup_raw<M>(
317+
map: &M,
318+
key: &[u8],
319+
value: &mut [mem::MaybeUninit<u8>],
320+
flags: MapFlags,
321+
) -> Result<bool>
316322
where
317323
M: MapCore + ?Sized,
318324
{
@@ -322,34 +328,65 @@ where
322328
key.len(),
323329
map.key_size()
324330
)));
325-
};
331+
}
326332

327-
let mut out: Vec<u8> = Vec::with_capacity(out_size);
333+
// Make sure the internal users of this function pass the expected buffer size
334+
debug_assert_eq!(
335+
value.len(),
336+
if map.map_type().is_percpu() {
337+
percpu_buffer_size(map).unwrap()
338+
} else {
339+
map.value_size() as usize
340+
}
341+
);
328342

329343
let ret = unsafe {
330344
libbpf_sys::bpf_map_lookup_elem_flags(
331345
map.as_fd().as_raw_fd(),
332346
map_key(map, key),
333-
out.as_mut_ptr() as *mut c_void,
347+
// TODO: Use `MaybeUninit::slice_as_mut_ptr` once stable.
348+
value.as_mut_ptr().cast(),
334349
flags.bits(),
335350
)
336351
};
337352

338353
if ret == 0 {
339-
unsafe {
340-
out.set_len(out_size);
341-
}
342-
Ok(Some(out))
354+
Ok(true)
343355
} else {
344356
let err = io::Error::last_os_error();
345357
if err.kind() == io::ErrorKind::NotFound {
346-
Ok(None)
358+
Ok(false)
347359
} else {
348360
Err(Error::from(err))
349361
}
350362
}
351363
}
352364

365+
/// Internal function to return a value from a map into a buffer of the given size.
366+
fn lookup_raw_vec<M>(
367+
map: &M,
368+
key: &[u8],
369+
flags: MapFlags,
370+
out_size: usize,
371+
) -> Result<Option<Vec<u8>>>
372+
where
373+
M: MapCore + ?Sized,
374+
{
375+
// Allocate without initializing (avoiding memset)
376+
let mut out = Vec::with_capacity(out_size);
377+
378+
match lookup_raw(map, key, out.spare_capacity_mut(), flags)? {
379+
true => {
380+
// SAFETY: `lookup_raw` successfully filled the buffer
381+
unsafe {
382+
out.set_len(out_size);
383+
}
384+
Ok(Some(out))
385+
}
386+
false => Ok(None),
387+
}
388+
}
389+
353390
/// Internal function to update a map. This does not check the length of the
354391
/// supplied value.
355392
fn update_raw<M>(map: &M, key: &[u8], value: &[u8], flags: MapFlags) -> Result<()>
@@ -480,7 +517,40 @@ pub trait MapCore: Debug + AsFd + private::Sealed {
480517
fn lookup(&self, key: &[u8], flags: MapFlags) -> Result<Option<Vec<u8>>> {
481518
check_not_bloom_or_percpu(self)?;
482519
let out_size = self.value_size() as usize;
483-
lookup_raw(self, key, flags, out_size)
520+
lookup_raw_vec(self, key, flags, out_size)
521+
}
522+
523+
/// Looks up a map value into a pre-allocated buffer, avoiding allocation.
524+
///
525+
/// This method provides a zero-allocation alternative to [`Self::lookup()`].
526+
///
527+
/// `key` must have exactly [`Self::key_size()`] elements.
528+
/// `value` must have exactly [`Self::value_size()`] elements.
529+
///
530+
/// Returns `Ok(true)` if the key was found and the buffer was filled,
531+
/// `Ok(false)` if the key was not found, or an error.
532+
///
533+
/// If the map is one of the per-cpu data structures, this function cannot be used.
534+
/// If the map is of type `bloom_filter`, this function cannot be used.
535+
fn lookup_into(&self, key: &[u8], value: &mut [u8], flags: MapFlags) -> Result<bool> {
536+
check_not_bloom_or_percpu(self)?;
537+
538+
if value.len() != self.value_size() as usize {
539+
return Err(Error::with_invalid_data(format!(
540+
"value buffer size {} != {}",
541+
value.len(),
542+
self.value_size()
543+
)));
544+
}
545+
546+
// SAFETY: `u8` and `MaybeUninit<u8>` have the same in-memory representation.
547+
let value = unsafe {
548+
slice::from_raw_parts_mut::<mem::MaybeUninit<u8>>(
549+
value.as_mut_ptr().cast(),
550+
value.len(),
551+
)
552+
};
553+
lookup_raw(self, key, value, flags)
484554
}
485555

486556
/// Returns many elements in batch mode from the map.
@@ -548,7 +618,7 @@ pub trait MapCore: Debug + AsFd + private::Sealed {
548618
let aligned_val_size = percpu_aligned_value_size(self);
549619
let out_size = percpu_buffer_size(self)?;
550620

551-
let raw_res = lookup_raw(self, key, flags, out_size)?;
621+
let raw_res = lookup_raw_vec(self, key, flags, out_size)?;
552622
if let Some(raw_vals) = raw_res {
553623
let mut out = Vec::new();
554624
for chunk in raw_vals.chunks_exact(aligned_val_size) {

libbpf-rs/tests/test.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,90 @@ fn test_object_map_mutation() {
667667
.is_none());
668668
}
669669

670+
#[tag(root)]
671+
#[test]
672+
fn test_object_map_lookup_into() {
673+
let mut obj = get_test_object("runqslower.bpf.o");
674+
let start = get_map_mut(&mut obj, "start");
675+
676+
// Insert a test value
677+
start
678+
.update(&[1, 2, 3, 4], &[1, 2, 3, 4, 5, 6, 7, 8], MapFlags::empty())
679+
.expect("failed to write");
680+
681+
// Test successful lookup with pre-allocated buffer
682+
let mut value = [0u8; 8];
683+
let found = start
684+
.lookup_into(&[1, 2, 3, 4], &mut value, MapFlags::empty())
685+
.expect("failed to lookup_into");
686+
687+
assert!(found, "key should be found");
688+
assert_eq!(value, [1, 2, 3, 4, 5, 6, 7, 8]);
689+
690+
// Test lookup of non-existent key
691+
let mut value2 = [0u8; 8];
692+
let found2 = start
693+
.lookup_into(&[5, 6, 7, 8], &mut value2, MapFlags::empty())
694+
.expect("failed to lookup_into for non-existent key");
695+
696+
assert!(!found2, "key should not be found");
697+
// Buffer should remain unchanged when key is not found
698+
assert_eq!(value2, [0u8; 8]);
699+
}
700+
701+
#[tag(root)]
702+
#[test]
703+
fn test_object_map_lookup_into_wrong_size() {
704+
let mut obj = get_test_object("runqslower.bpf.o");
705+
let start = get_map_mut(&mut obj, "start");
706+
707+
// Insert a test value
708+
start
709+
.update(&[1, 2, 3, 4], &[1, 2, 3, 4, 5, 6, 7, 8], MapFlags::empty())
710+
.expect("failed to write");
711+
712+
// Test with wrong buffer size (too small)
713+
let mut value_small = [0u8; 4];
714+
let result = start.lookup_into(&[1, 2, 3, 4], &mut value_small, MapFlags::empty());
715+
assert!(result.is_err(), "should fail with wrong buffer size");
716+
717+
// Test with wrong buffer size (too large)
718+
let mut value_large = [0u8; 16];
719+
let result = start.lookup_into(&[1, 2, 3, 4], &mut value_large, MapFlags::empty());
720+
assert!(result.is_err(), "should fail with wrong buffer size");
721+
}
722+
723+
#[tag(root)]
724+
#[test]
725+
fn test_object_map_lookup_into_consistency() {
726+
let mut obj = get_test_object("runqslower.bpf.o");
727+
let start = get_map_mut(&mut obj, "start");
728+
729+
// Insert a test value
730+
let test_value = [10, 20, 30, 40, 50, 60, 70, 80];
731+
start
732+
.update(&[1, 2, 3, 4], &test_value, MapFlags::empty())
733+
.expect("failed to write");
734+
735+
// Compare results from lookup() and lookup_into()
736+
let lookup_result = start
737+
.lookup(&[1, 2, 3, 4], MapFlags::empty())
738+
.expect("failed to lookup")
739+
.expect("key not found");
740+
741+
let mut value_buffer = [0u8; 8];
742+
let found = start
743+
.lookup_into(&[1, 2, 3, 4], &mut value_buffer, MapFlags::empty())
744+
.expect("failed to lookup_into");
745+
746+
assert!(found, "key should be found");
747+
assert_eq!(
748+
lookup_result.as_slice(),
749+
&value_buffer,
750+
"lookup() and lookup_into() should return the same value"
751+
);
752+
}
753+
670754
#[tag(root)]
671755
#[test]
672756
fn test_object_map_lookup_flags() {

0 commit comments

Comments
 (0)