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 =
183 unsafe { (buf_base.add(off) as *const btrfs_ioctl_search_header).read_unaligned() };
184 let hdr = SearchHeader {
185 transid: raw_hdr.transid,
186 objectid: raw_hdr.objectid,
187 offset: raw_hdr.offset,
188 item_type: raw_hdr.type_,
189 len: raw_hdr.len,
190 };
191 off += SEARCH_HEADER_SIZE;
192
193 let data_len = hdr.len as usize;
194 if off + data_len > buf_cap {
195 return Err(nix::errno::Errno::EOVERFLOW);
196 }
197 let data: &[u8] = unsafe { std::slice::from_raw_parts(buf_base.add(off), data_len) };
198 off += data_len;
199
200 f(&hdr, data)?;
201 last = hdr;
202 }
203
204 if !advance_cursor(&mut args.key, &last) {
205 break;
206 }
207 }
208
209 Ok(())
210}
211
212fn fill_search_key(sk: &mut btrfs_ioctl_search_key, key: &SearchKey) {
213 sk.tree_id = key.tree_id;
214 sk.min_objectid = key.min_objectid;
215 sk.max_objectid = key.max_objectid;
216 sk.min_type = key.min_type;
217 sk.max_type = key.max_type;
218 sk.min_offset = key.min_offset;
219 sk.max_offset = key.max_offset;
220 sk.min_transid = key.min_transid;
221 sk.max_transid = key.max_transid;
222}
223
224fn advance_cursor(sk: &mut btrfs_ioctl_search_key, last: &SearchHeader) -> bool {
237 let (new_offset, offset_overflow) = last.offset.overflowing_add(1);
238 if !offset_overflow {
239 sk.min_objectid = last.objectid;
240 sk.min_type = last.item_type;
241 sk.min_offset = new_offset;
242 return true;
243 }
244
245 sk.min_offset = 0;
246 let (new_type, type_overflow) = last.item_type.overflowing_add(1);
247 if !type_overflow {
248 sk.min_objectid = last.objectid;
249 sk.min_type = new_type;
250 return true;
251 }
252
253 sk.min_type = 0;
254 let (new_oid, oid_overflow) = last.objectid.overflowing_add(1);
255 if oid_overflow {
256 return false;
257 }
258 sk.min_objectid = new_oid;
259 true
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 fn header(objectid: u64, item_type: u32, offset: u64) -> SearchHeader {
267 SearchHeader {
268 transid: 0,
269 objectid,
270 offset,
271 item_type,
272 len: 0,
273 }
274 }
275
276 fn zeroed_search_key() -> btrfs_ioctl_search_key {
277 unsafe { mem::zeroed() }
278 }
279
280 #[test]
283 fn for_type_covers_all_objectids_and_offsets() {
284 let sk = SearchKey::for_type(5, 132);
285 assert_eq!(sk.tree_id, 5);
286 assert_eq!(sk.min_objectid, 0);
287 assert_eq!(sk.max_objectid, u64::MAX);
288 assert_eq!(sk.min_type, 132);
289 assert_eq!(sk.max_type, 132);
290 assert_eq!(sk.min_offset, 0);
291 assert_eq!(sk.max_offset, u64::MAX);
292 assert_eq!(sk.min_transid, 0);
293 assert_eq!(sk.max_transid, u64::MAX);
294 }
295
296 #[test]
297 fn for_objectid_range_restricts_objectids() {
298 let sk = SearchKey::for_objectid_range(1, 84, 100, 200);
299 assert_eq!(sk.tree_id, 1);
300 assert_eq!(sk.min_objectid, 100);
301 assert_eq!(sk.max_objectid, 200);
302 assert_eq!(sk.min_type, 84);
303 assert_eq!(sk.max_type, 84);
304 assert_eq!(sk.min_offset, 0);
305 assert_eq!(sk.max_offset, u64::MAX);
306 }
307
308 #[test]
311 fn fill_search_key_copies_all_fields() {
312 let key = SearchKey {
313 tree_id: 1,
314 min_objectid: 10,
315 max_objectid: 20,
316 min_type: 30,
317 max_type: 40,
318 min_offset: 50,
319 max_offset: 60,
320 min_transid: 70,
321 max_transid: 80,
322 };
323 let mut sk = zeroed_search_key();
324 fill_search_key(&mut sk, &key);
325 assert_eq!(sk.tree_id, 1);
326 assert_eq!(sk.min_objectid, 10);
327 assert_eq!(sk.max_objectid, 20);
328 assert_eq!(sk.min_type, 30);
329 assert_eq!(sk.max_type, 40);
330 assert_eq!(sk.min_offset, 50);
331 assert_eq!(sk.max_offset, 60);
332 assert_eq!(sk.min_transid, 70);
333 assert_eq!(sk.max_transid, 80);
334 }
335
336 #[test]
339 fn advance_increments_offset() {
340 let mut sk = zeroed_search_key();
341 let last = header(256, 132, 100);
342 assert!(advance_cursor(&mut sk, &last));
343 assert_eq!(sk.min_objectid, 256);
344 assert_eq!(sk.min_type, 132);
345 assert_eq!(sk.min_offset, 101);
346 }
347
348 #[test]
349 fn advance_tracks_objectid_from_last_item() {
350 let mut sk = zeroed_search_key();
353 sk.min_objectid = 100; let last = header(300, 132, 50); assert!(advance_cursor(&mut sk, &last));
356 assert_eq!(sk.min_objectid, 300, "must track last item's objectid");
357 assert_eq!(sk.min_type, 132);
358 assert_eq!(sk.min_offset, 51);
359 }
360
361 #[test]
362 fn advance_tracks_type_from_last_item() {
363 let mut sk = zeroed_search_key();
364 let last = header(256, 180, 42);
365 assert!(advance_cursor(&mut sk, &last));
366 assert_eq!(sk.min_objectid, 256);
367 assert_eq!(sk.min_type, 180);
368 assert_eq!(sk.min_offset, 43);
369 }
370
371 #[test]
374 fn advance_offset_overflow_bumps_type() {
375 let mut sk = zeroed_search_key();
376 let last = header(256, 132, u64::MAX);
377 assert!(advance_cursor(&mut sk, &last));
378 assert_eq!(sk.min_objectid, 256);
379 assert_eq!(sk.min_type, 133);
380 assert_eq!(sk.min_offset, 0);
381 }
382
383 #[test]
386 fn advance_type_overflow_bumps_objectid() {
387 let mut sk = zeroed_search_key();
388 let last = header(256, u32::MAX, u64::MAX);
389 assert!(advance_cursor(&mut sk, &last));
390 assert_eq!(sk.min_objectid, 257);
391 assert_eq!(sk.min_type, 0);
392 assert_eq!(sk.min_offset, 0);
393 }
394
395 #[test]
398 fn advance_all_overflow_returns_false() {
399 let mut sk = zeroed_search_key();
400 let last = header(u64::MAX, u32::MAX, u64::MAX);
401 assert!(!advance_cursor(&mut sk, &last));
402 }
403
404 #[test]
407 fn advance_zero_key() {
408 let mut sk = zeroed_search_key();
409 let last = header(0, 0, 0);
410 assert!(advance_cursor(&mut sk, &last));
411 assert_eq!(sk.min_objectid, 0);
412 assert_eq!(sk.min_type, 0);
413 assert_eq!(sk.min_offset, 1);
414 }
415
416 #[test]
417 fn advance_objectid_max_type_zero_offset_max() {
418 let mut sk = zeroed_search_key();
420 let last = header(u64::MAX, 0, u64::MAX);
421 assert!(advance_cursor(&mut sk, &last));
422 assert_eq!(sk.min_objectid, u64::MAX);
423 assert_eq!(sk.min_type, 1);
424 assert_eq!(sk.min_offset, 0);
425 }
426
427 #[test]
428 fn advance_preserves_unrelated_search_key_fields() {
429 let mut sk = zeroed_search_key();
430 sk.max_objectid = 999;
431 sk.max_type = 888;
432 sk.max_offset = 777;
433 sk.max_transid = 666;
434 let last = header(10, 20, 30);
435 advance_cursor(&mut sk, &last);
436 assert_eq!(sk.max_objectid, 999);
437 assert_eq!(sk.max_type, 888);
438 assert_eq!(sk.max_offset, 777);
439 assert_eq!(sk.max_transid, 666);
440 }
441}