1use crate::raw::{
23 btrfs_ioc_tree_search, btrfs_ioctl_search_args, btrfs_ioctl_search_header,
24 btrfs_ioctl_search_key,
25};
26use std::{
27 mem,
28 os::{fd::AsRawFd, unix::io::BorrowedFd},
29};
30
31#[derive(Debug, Clone)]
45pub struct SearchKey {
46 pub tree_id: u64,
48 pub min_objectid: u64,
49 pub max_objectid: u64,
50 pub min_type: u32,
52 pub max_type: u32,
53 pub min_offset: u64,
54 pub max_offset: u64,
55 pub min_transid: u64,
58 pub max_transid: u64,
59}
60
61impl SearchKey {
62 pub fn for_type(tree_id: u64, item_type: u32) -> Self {
65 Self {
66 tree_id,
67 min_objectid: 0,
68 max_objectid: u64::MAX,
69 min_type: item_type,
70 max_type: item_type,
71 min_offset: 0,
72 max_offset: u64::MAX,
73 min_transid: 0,
74 max_transid: u64::MAX,
75 }
76 }
77
78 pub fn for_objectid_range(
81 tree_id: u64,
82 item_type: u32,
83 min_objectid: u64,
84 max_objectid: u64,
85 ) -> Self {
86 Self {
87 min_objectid,
88 max_objectid,
89 ..Self::for_type(tree_id, item_type)
90 }
91 }
92}
93
94#[derive(Debug, Clone, Copy)]
100pub struct SearchHeader {
101 pub transid: u64,
102 pub objectid: u64,
103 pub offset: u64,
104 pub item_type: u32,
106 pub len: u32,
108}
109
110const ITEMS_PER_BATCH: u32 = 4096;
112
113const SEARCH_HEADER_SIZE: usize = mem::size_of::<btrfs_ioctl_search_header>();
115
116pub fn tree_search(
135 fd: BorrowedFd,
136 key: SearchKey,
137 mut f: impl FnMut(&SearchHeader, &[u8]) -> nix::Result<()>,
138) -> nix::Result<()> {
139 let mut args: btrfs_ioctl_search_args = unsafe { mem::zeroed() };
140
141 fill_search_key(&mut args.key, &key);
142
143 loop {
144 args.key.nr_items = ITEMS_PER_BATCH;
145
146 unsafe { btrfs_ioc_tree_search(fd.as_raw_fd(), &mut args) }?;
147
148 let nr = args.key.nr_items;
149 if nr == 0 {
150 break;
151 }
152
153 let buf_base: *const u8 = args.buf.as_ptr().cast();
167 let buf_cap = args.buf.len();
168
169 let mut off = 0usize;
170 let mut last = SearchHeader {
171 transid: 0,
172 objectid: 0,
173 offset: 0,
174 item_type: 0,
175 len: 0,
176 };
177
178 for _ in 0..nr {
179 if off + SEARCH_HEADER_SIZE > buf_cap {
180 return Err(nix::errno::Errno::EOVERFLOW);
181 }
182 let raw_hdr: btrfs_ioctl_search_header = unsafe {
183 (buf_base.add(off) as *const btrfs_ioctl_search_header)
184 .read_unaligned()
185 };
186 let hdr = SearchHeader {
187 transid: raw_hdr.transid,
188 objectid: raw_hdr.objectid,
189 offset: raw_hdr.offset,
190 item_type: raw_hdr.type_,
191 len: raw_hdr.len,
192 };
193 off += SEARCH_HEADER_SIZE;
194
195 let data_len = hdr.len as usize;
196 if off + data_len > buf_cap {
197 return Err(nix::errno::Errno::EOVERFLOW);
198 }
199 let data: &[u8] = unsafe {
200 std::slice::from_raw_parts(buf_base.add(off), data_len)
201 };
202 off += data_len;
203
204 f(&hdr, data)?;
205 last = hdr;
206 }
207
208 if !advance_cursor(&mut args.key, &last) {
209 break;
210 }
211 }
212
213 Ok(())
214}
215
216fn fill_search_key(sk: &mut btrfs_ioctl_search_key, key: &SearchKey) {
217 sk.tree_id = key.tree_id;
218 sk.min_objectid = key.min_objectid;
219 sk.max_objectid = key.max_objectid;
220 sk.min_type = key.min_type;
221 sk.max_type = key.max_type;
222 sk.min_offset = key.min_offset;
223 sk.max_offset = key.max_offset;
224 sk.min_transid = key.min_transid;
225 sk.max_transid = key.max_transid;
226}
227
228fn advance_cursor(
241 sk: &mut btrfs_ioctl_search_key,
242 last: &SearchHeader,
243) -> bool {
244 let (new_offset, offset_overflow) = last.offset.overflowing_add(1);
245 if !offset_overflow {
246 sk.min_objectid = last.objectid;
247 sk.min_type = last.item_type;
248 sk.min_offset = new_offset;
249 return true;
250 }
251
252 sk.min_offset = 0;
253 let (new_type, type_overflow) = last.item_type.overflowing_add(1);
254 if !type_overflow {
255 sk.min_objectid = last.objectid;
256 sk.min_type = new_type;
257 return true;
258 }
259
260 sk.min_type = 0;
261 let (new_oid, oid_overflow) = last.objectid.overflowing_add(1);
262 if oid_overflow {
263 return false;
264 }
265 sk.min_objectid = new_oid;
266 true
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 fn header(objectid: u64, item_type: u32, offset: u64) -> SearchHeader {
274 SearchHeader {
275 transid: 0,
276 objectid,
277 offset,
278 item_type,
279 len: 0,
280 }
281 }
282
283 fn zeroed_search_key() -> btrfs_ioctl_search_key {
284 unsafe { mem::zeroed() }
285 }
286
287 #[test]
290 fn for_type_covers_all_objectids_and_offsets() {
291 let sk = SearchKey::for_type(5, 132);
292 assert_eq!(sk.tree_id, 5);
293 assert_eq!(sk.min_objectid, 0);
294 assert_eq!(sk.max_objectid, u64::MAX);
295 assert_eq!(sk.min_type, 132);
296 assert_eq!(sk.max_type, 132);
297 assert_eq!(sk.min_offset, 0);
298 assert_eq!(sk.max_offset, u64::MAX);
299 assert_eq!(sk.min_transid, 0);
300 assert_eq!(sk.max_transid, u64::MAX);
301 }
302
303 #[test]
304 fn for_objectid_range_restricts_objectids() {
305 let sk = SearchKey::for_objectid_range(1, 84, 100, 200);
306 assert_eq!(sk.tree_id, 1);
307 assert_eq!(sk.min_objectid, 100);
308 assert_eq!(sk.max_objectid, 200);
309 assert_eq!(sk.min_type, 84);
310 assert_eq!(sk.max_type, 84);
311 assert_eq!(sk.min_offset, 0);
312 assert_eq!(sk.max_offset, u64::MAX);
313 }
314
315 #[test]
318 fn fill_search_key_copies_all_fields() {
319 let key = SearchKey {
320 tree_id: 1,
321 min_objectid: 10,
322 max_objectid: 20,
323 min_type: 30,
324 max_type: 40,
325 min_offset: 50,
326 max_offset: 60,
327 min_transid: 70,
328 max_transid: 80,
329 };
330 let mut sk = zeroed_search_key();
331 fill_search_key(&mut sk, &key);
332 assert_eq!(sk.tree_id, 1);
333 assert_eq!(sk.min_objectid, 10);
334 assert_eq!(sk.max_objectid, 20);
335 assert_eq!(sk.min_type, 30);
336 assert_eq!(sk.max_type, 40);
337 assert_eq!(sk.min_offset, 50);
338 assert_eq!(sk.max_offset, 60);
339 assert_eq!(sk.min_transid, 70);
340 assert_eq!(sk.max_transid, 80);
341 }
342
343 #[test]
346 fn advance_increments_offset() {
347 let mut sk = zeroed_search_key();
348 let last = header(256, 132, 100);
349 assert!(advance_cursor(&mut sk, &last));
350 assert_eq!(sk.min_objectid, 256);
351 assert_eq!(sk.min_type, 132);
352 assert_eq!(sk.min_offset, 101);
353 }
354
355 #[test]
356 fn advance_tracks_objectid_from_last_item() {
357 let mut sk = zeroed_search_key();
360 sk.min_objectid = 100; let last = header(300, 132, 50); assert!(advance_cursor(&mut sk, &last));
363 assert_eq!(sk.min_objectid, 300, "must track last item's objectid");
364 assert_eq!(sk.min_type, 132);
365 assert_eq!(sk.min_offset, 51);
366 }
367
368 #[test]
369 fn advance_tracks_type_from_last_item() {
370 let mut sk = zeroed_search_key();
371 let last = header(256, 180, 42);
372 assert!(advance_cursor(&mut sk, &last));
373 assert_eq!(sk.min_objectid, 256);
374 assert_eq!(sk.min_type, 180);
375 assert_eq!(sk.min_offset, 43);
376 }
377
378 #[test]
381 fn advance_offset_overflow_bumps_type() {
382 let mut sk = zeroed_search_key();
383 let last = header(256, 132, u64::MAX);
384 assert!(advance_cursor(&mut sk, &last));
385 assert_eq!(sk.min_objectid, 256);
386 assert_eq!(sk.min_type, 133);
387 assert_eq!(sk.min_offset, 0);
388 }
389
390 #[test]
393 fn advance_type_overflow_bumps_objectid() {
394 let mut sk = zeroed_search_key();
395 let last = header(256, u32::MAX, u64::MAX);
396 assert!(advance_cursor(&mut sk, &last));
397 assert_eq!(sk.min_objectid, 257);
398 assert_eq!(sk.min_type, 0);
399 assert_eq!(sk.min_offset, 0);
400 }
401
402 #[test]
405 fn advance_all_overflow_returns_false() {
406 let mut sk = zeroed_search_key();
407 let last = header(u64::MAX, u32::MAX, u64::MAX);
408 assert!(!advance_cursor(&mut sk, &last));
409 }
410
411 #[test]
414 fn advance_zero_key() {
415 let mut sk = zeroed_search_key();
416 let last = header(0, 0, 0);
417 assert!(advance_cursor(&mut sk, &last));
418 assert_eq!(sk.min_objectid, 0);
419 assert_eq!(sk.min_type, 0);
420 assert_eq!(sk.min_offset, 1);
421 }
422
423 #[test]
424 fn advance_objectid_max_type_zero_offset_max() {
425 let mut sk = zeroed_search_key();
427 let last = header(u64::MAX, 0, u64::MAX);
428 assert!(advance_cursor(&mut sk, &last));
429 assert_eq!(sk.min_objectid, u64::MAX);
430 assert_eq!(sk.min_type, 1);
431 assert_eq!(sk.min_offset, 0);
432 }
433
434 #[test]
435 fn advance_preserves_unrelated_search_key_fields() {
436 let mut sk = zeroed_search_key();
437 sk.max_objectid = 999;
438 sk.max_type = 888;
439 sk.max_offset = 777;
440 sk.max_transid = 666;
441 let last = header(10, 20, 30);
442 advance_cursor(&mut sk, &last);
443 assert_eq!(sk.max_objectid, 999);
444 assert_eq!(sk.max_type, 888);
445 assert_eq!(sk.max_offset, 777);
446 assert_eq!(sk.max_transid, 666);
447 }
448}