use crate::raw::{
btrfs_ioc_tree_search, btrfs_ioc_tree_search_v2, btrfs_ioctl_search_args,
btrfs_ioctl_search_args_v2, btrfs_ioctl_search_header,
btrfs_ioctl_search_key,
};
use std::{
mem,
os::{fd::AsRawFd, unix::io::BorrowedFd},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Key {
pub objectid: u64,
pub item_type: u32,
pub offset: u64,
}
impl Key {
pub const MIN: Self = Self {
objectid: 0,
item_type: 0,
offset: 0,
};
pub const MAX: Self = Self {
objectid: u64::MAX,
item_type: u32::MAX,
offset: u64::MAX,
};
}
#[derive(Debug, Clone)]
pub struct SearchFilter {
pub tree_id: u64,
pub start: Key,
pub end: Key,
pub min_transid: u64,
pub max_transid: u64,
}
impl SearchFilter {
#[must_use]
pub fn for_type(tree_id: u64, item_type: u32) -> Self {
Self {
tree_id,
start: Key {
objectid: 0,
item_type,
offset: 0,
},
end: Key {
objectid: u64::MAX,
item_type,
offset: u64::MAX,
},
min_transid: 0,
max_transid: u64::MAX,
}
}
#[must_use]
pub fn for_objectid_range(
tree_id: u64,
item_type: u32,
min_objectid: u64,
max_objectid: u64,
) -> Self {
Self {
tree_id,
start: Key {
objectid: min_objectid,
item_type,
offset: 0,
},
end: Key {
objectid: max_objectid,
item_type,
offset: u64::MAX,
},
min_transid: 0,
max_transid: u64::MAX,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct SearchHeader {
pub transid: u64,
pub objectid: u64,
pub offset: u64,
pub item_type: u32,
pub len: u32,
}
const ITEMS_PER_BATCH: u32 = 4096;
const SEARCH_HEADER_SIZE: usize = mem::size_of::<btrfs_ioctl_search_header>();
#[allow(clippy::needless_pass_by_value)] pub fn tree_search(
fd: BorrowedFd,
filter: SearchFilter,
mut f: impl FnMut(&SearchHeader, &[u8]) -> nix::Result<()>,
) -> nix::Result<()> {
let mut args: btrfs_ioctl_search_args = unsafe { mem::zeroed() };
fill_search_key(&mut args.key, &filter);
loop {
args.key.nr_items = ITEMS_PER_BATCH;
unsafe { btrfs_ioc_tree_search(fd.as_raw_fd(), &raw mut args) }?;
let nr = args.key.nr_items;
if nr == 0 {
break;
}
let buf_base: *const u8 = args.buf.as_ptr().cast();
let buf_cap = args.buf.len();
let mut off = 0usize;
let mut last = SearchHeader {
transid: 0,
objectid: 0,
offset: 0,
item_type: 0,
len: 0,
};
for _ in 0..nr {
if off + SEARCH_HEADER_SIZE > buf_cap {
return Err(nix::errno::Errno::EOVERFLOW);
}
let raw_hdr: btrfs_ioctl_search_header = unsafe {
buf_base
.add(off)
.cast::<btrfs_ioctl_search_header>()
.read_unaligned()
};
let hdr = SearchHeader {
transid: raw_hdr.transid,
objectid: raw_hdr.objectid,
offset: raw_hdr.offset,
item_type: raw_hdr.type_,
len: raw_hdr.len,
};
off += SEARCH_HEADER_SIZE;
let data_len = hdr.len as usize;
if off + data_len > buf_cap {
return Err(nix::errno::Errno::EOVERFLOW);
}
let data: &[u8] = unsafe {
std::slice::from_raw_parts(buf_base.add(off), data_len)
};
off += data_len;
f(&hdr, data)?;
last = hdr;
}
if !advance_cursor(&mut args.key, &last) {
break;
}
}
Ok(())
}
const DEFAULT_V2_BUF_SIZE: usize = 64 * 1024;
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::too_many_lines)]
pub fn tree_search_v2(
fd: BorrowedFd,
filter: SearchFilter,
buf_size: Option<usize>,
mut f: impl FnMut(&SearchHeader, &[u8]) -> nix::Result<()>,
) -> nix::Result<()> {
let base_size = mem::size_of::<btrfs_ioctl_search_args_v2>();
let mut capacity = buf_size.unwrap_or(DEFAULT_V2_BUF_SIZE);
let alloc_bytes = base_size + capacity;
let num_u64s = alloc_bytes.div_ceil(mem::size_of::<u64>());
let mut buf = vec![0u64; num_u64s];
let args_ptr = buf.as_mut_ptr().cast::<btrfs_ioctl_search_args_v2>();
unsafe {
fill_search_key(&mut (*args_ptr).key, &filter);
}
loop {
unsafe {
(*args_ptr).key.nr_items = ITEMS_PER_BATCH;
(*args_ptr).buf_size = capacity as u64;
}
match unsafe {
btrfs_ioc_tree_search_v2(fd.as_raw_fd(), &raw mut *args_ptr)
} {
Ok(_) => {}
Err(nix::errno::Errno::EOVERFLOW) => {
#[allow(clippy::cast_possible_truncation)]
let needed = unsafe { (*args_ptr).buf_size } as usize;
if needed <= capacity {
return Err(nix::errno::Errno::EOVERFLOW);
}
capacity = needed;
let alloc_bytes = base_size + capacity;
let num_u64s = alloc_bytes.div_ceil(mem::size_of::<u64>());
buf.resize(num_u64s, 0);
let args_ptr_new =
buf.as_mut_ptr().cast::<btrfs_ioctl_search_args_v2>();
unsafe {
(*args_ptr_new).key.nr_items = ITEMS_PER_BATCH;
(*args_ptr_new).buf_size = capacity as u64;
btrfs_ioc_tree_search_v2(
fd.as_raw_fd(),
&raw mut *args_ptr_new,
)?;
}
let _ = args_ptr_new;
}
Err(e) => return Err(e),
}
let args_ptr = buf.as_mut_ptr().cast::<btrfs_ioctl_search_args_v2>();
let nr = unsafe { (*args_ptr).key.nr_items };
if nr == 0 {
break;
}
let data_base: *const u8 =
unsafe { (args_ptr as *const u8).add(base_size) };
let mut off = 0usize;
let mut last = SearchHeader {
transid: 0,
objectid: 0,
offset: 0,
item_type: 0,
len: 0,
};
for _ in 0..nr {
if off + SEARCH_HEADER_SIZE > capacity {
return Err(nix::errno::Errno::EOVERFLOW);
}
let raw_hdr: btrfs_ioctl_search_header = unsafe {
data_base
.add(off)
.cast::<btrfs_ioctl_search_header>()
.read_unaligned()
};
let hdr = SearchHeader {
transid: raw_hdr.transid,
objectid: raw_hdr.objectid,
offset: raw_hdr.offset,
item_type: raw_hdr.type_,
len: raw_hdr.len,
};
off += SEARCH_HEADER_SIZE;
let data_len = hdr.len as usize;
if off + data_len > capacity {
return Err(nix::errno::Errno::EOVERFLOW);
}
let data: &[u8] = unsafe {
std::slice::from_raw_parts(data_base.add(off), data_len)
};
off += data_len;
f(&hdr, data)?;
last = hdr;
}
if !advance_cursor(unsafe { &mut (*args_ptr).key }, &last) {
break;
}
}
Ok(())
}
fn fill_search_key(sk: &mut btrfs_ioctl_search_key, filter: &SearchFilter) {
sk.tree_id = filter.tree_id;
sk.min_objectid = filter.start.objectid;
sk.max_objectid = filter.end.objectid;
sk.min_type = filter.start.item_type;
sk.max_type = filter.end.item_type;
sk.min_offset = filter.start.offset;
sk.max_offset = filter.end.offset;
sk.min_transid = filter.min_transid;
sk.max_transid = filter.max_transid;
}
fn advance_cursor(
sk: &mut btrfs_ioctl_search_key,
last: &SearchHeader,
) -> bool {
let (new_offset, offset_overflow) = last.offset.overflowing_add(1);
if !offset_overflow {
sk.min_objectid = last.objectid;
sk.min_type = last.item_type;
sk.min_offset = new_offset;
return true;
}
sk.min_offset = 0;
let (new_type, type_overflow) = last.item_type.overflowing_add(1);
if !type_overflow {
sk.min_objectid = last.objectid;
sk.min_type = new_type;
return true;
}
sk.min_type = 0;
let (new_oid, oid_overflow) = last.objectid.overflowing_add(1);
if oid_overflow {
return false;
}
sk.min_objectid = new_oid;
true
}
#[cfg(test)]
mod tests {
use super::*;
fn header(objectid: u64, item_type: u32, offset: u64) -> SearchHeader {
SearchHeader {
transid: 0,
objectid,
offset,
item_type,
len: 0,
}
}
fn zeroed_search_key() -> btrfs_ioctl_search_key {
unsafe { mem::zeroed() }
}
#[test]
fn for_type_covers_all_objectids_and_offsets() {
let f = SearchFilter::for_type(5, 132);
assert_eq!(f.tree_id, 5);
assert_eq!(f.start.objectid, 0);
assert_eq!(f.end.objectid, u64::MAX);
assert_eq!(f.start.item_type, 132);
assert_eq!(f.end.item_type, 132);
assert_eq!(f.start.offset, 0);
assert_eq!(f.end.offset, u64::MAX);
assert_eq!(f.min_transid, 0);
assert_eq!(f.max_transid, u64::MAX);
}
#[test]
fn for_objectid_range_restricts_objectids() {
let f = SearchFilter::for_objectid_range(1, 84, 100, 200);
assert_eq!(f.tree_id, 1);
assert_eq!(f.start.objectid, 100);
assert_eq!(f.end.objectid, 200);
assert_eq!(f.start.item_type, 84);
assert_eq!(f.end.item_type, 84);
assert_eq!(f.start.offset, 0);
assert_eq!(f.end.offset, u64::MAX);
}
#[test]
fn fill_search_key_copies_all_fields() {
let filter = SearchFilter {
tree_id: 1,
start: Key {
objectid: 10,
item_type: 30,
offset: 50,
},
end: Key {
objectid: 20,
item_type: 40,
offset: 60,
},
min_transid: 70,
max_transid: 80,
};
let mut sk = zeroed_search_key();
fill_search_key(&mut sk, &filter);
assert_eq!(sk.tree_id, 1);
assert_eq!(sk.min_objectid, 10);
assert_eq!(sk.max_objectid, 20);
assert_eq!(sk.min_type, 30);
assert_eq!(sk.max_type, 40);
assert_eq!(sk.min_offset, 50);
assert_eq!(sk.max_offset, 60);
assert_eq!(sk.min_transid, 70);
assert_eq!(sk.max_transid, 80);
}
#[test]
fn advance_increments_offset() {
let mut sk = zeroed_search_key();
let last = header(256, 132, 100);
assert!(advance_cursor(&mut sk, &last));
assert_eq!(sk.min_objectid, 256);
assert_eq!(sk.min_type, 132);
assert_eq!(sk.min_offset, 101);
}
#[test]
fn advance_tracks_objectid_from_last_item() {
let mut sk = zeroed_search_key();
sk.min_objectid = 100; let last = header(300, 132, 50); assert!(advance_cursor(&mut sk, &last));
assert_eq!(sk.min_objectid, 300, "must track last item's objectid");
assert_eq!(sk.min_type, 132);
assert_eq!(sk.min_offset, 51);
}
#[test]
fn advance_tracks_type_from_last_item() {
let mut sk = zeroed_search_key();
let last = header(256, 180, 42);
assert!(advance_cursor(&mut sk, &last));
assert_eq!(sk.min_objectid, 256);
assert_eq!(sk.min_type, 180);
assert_eq!(sk.min_offset, 43);
}
#[test]
fn advance_offset_overflow_bumps_type() {
let mut sk = zeroed_search_key();
let last = header(256, 132, u64::MAX);
assert!(advance_cursor(&mut sk, &last));
assert_eq!(sk.min_objectid, 256);
assert_eq!(sk.min_type, 133);
assert_eq!(sk.min_offset, 0);
}
#[test]
fn advance_type_overflow_bumps_objectid() {
let mut sk = zeroed_search_key();
let last = header(256, u32::MAX, u64::MAX);
assert!(advance_cursor(&mut sk, &last));
assert_eq!(sk.min_objectid, 257);
assert_eq!(sk.min_type, 0);
assert_eq!(sk.min_offset, 0);
}
#[test]
fn advance_all_overflow_returns_false() {
let mut sk = zeroed_search_key();
let last = header(u64::MAX, u32::MAX, u64::MAX);
assert!(!advance_cursor(&mut sk, &last));
}
#[test]
fn advance_zero_key() {
let mut sk = zeroed_search_key();
let last = header(0, 0, 0);
assert!(advance_cursor(&mut sk, &last));
assert_eq!(sk.min_objectid, 0);
assert_eq!(sk.min_type, 0);
assert_eq!(sk.min_offset, 1);
}
#[test]
fn advance_objectid_max_type_zero_offset_max() {
let mut sk = zeroed_search_key();
let last = header(u64::MAX, 0, u64::MAX);
assert!(advance_cursor(&mut sk, &last));
assert_eq!(sk.min_objectid, u64::MAX);
assert_eq!(sk.min_type, 1);
assert_eq!(sk.min_offset, 0);
}
#[test]
fn advance_preserves_unrelated_search_key_fields() {
let mut sk = zeroed_search_key();
sk.max_objectid = 999;
sk.max_type = 888;
sk.max_offset = 777;
sk.max_transid = 666;
let last = header(10, 20, 30);
advance_cursor(&mut sk, &last);
assert_eq!(sk.max_objectid, 999);
assert_eq!(sk.max_type, 888);
assert_eq!(sk.max_offset, 777);
assert_eq!(sk.max_transid, 666);
}
}