use crate::{
bindings::{
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,
},
util::{IoError, IoResult, OptionFd, btrfs_ioctl},
};
use std::{
alloc::{Layout, alloc, dealloc, handle_alloc_error},
ffi::c_char,
fs::File,
marker::PhantomData,
mem::{ManuallyDrop, MaybeUninit},
ops::{Bound, RangeBounds},
os::fd::{AsFd, BorrowedFd},
path::Path,
ptr::{read_unaligned, write},
};
pub mod tree_item;
use tree_item::TreeItem;
pub type ObjectId = u64;
pub type Ty = u32;
pub type Offset = u64;
pub type DiskKey = (ObjectId, Ty, Offset);
pub trait FromDiskKey
{
#[doc(hidden)]
fn as_disk_key(self) -> Option<DiskKey>;
}
impl FromDiskKey for Option<()>
{
fn as_disk_key(self) -> Option<DiskKey>
{
None
}
}
impl FromDiskKey for ()
{
fn as_disk_key(self) -> Option<DiskKey>
{
None
}
}
impl FromDiskKey for u64
{
fn as_disk_key(self) -> Option<DiskKey>
{
todo!()
}
}
impl FromDiskKey for DiskKey
{
fn as_disk_key(self) -> Option<DiskKey>
{
Some(self)
}
}
pub fn next_objectid(key: DiskKey) -> DiskKey
{
u64::checked_add(key.0, 1).map_or(key, |obj| (obj, 0, 0))
}
pub fn next_type(key: DiskKey) -> DiskKey
{
u8::checked_add(key.1 as u8, 1).map_or_else(|| next_type(key), |ty| (key.0, ty as u32, 0))
}
pub fn next_offset(key: DiskKey) -> DiskKey
{
u64::checked_add(key.2, 1).map_or_else(|| next_type(key), |off| (key.0, key.1, off))
}
struct FnOnDrop<'buf, F, K>
where
K: FromDiskKey,
F: FnOnce(DiskKey) -> K,
{
f: ManuallyDrop<F>,
key: &'buf mut btrfs_ioctl_search_key,
}
struct SearchIter<'buf, F, K>
where
K: FromDiskKey,
F: FnOnce(DiskKey) -> K,
{
buffer: *const c_char,
index: usize,
curr_offset: usize,
prev_offset: usize,
on_drop: FnOnDrop<'buf, F, K>,
_phantom: PhantomData<&'buf c_char>,
}
impl<F, K> Drop for SearchIter<'_, F, K>
where
K: FromDiskKey,
F: FnOnce(DiskKey) -> K,
{
fn drop(&mut self)
{
let hdr = unsafe {
self.buffer
.add(self.prev_offset)
.cast::<btrfs_ioctl_search_header>()
.read_unaligned()
};
let callback = unsafe { ManuallyDrop::take(&mut self.on_drop.f) };
if let Some((obj, ty, off)) = callback((hdr.objectid, hdr.type_, hdr.offset)).as_disk_key()
{
self.on_drop.key.min_objectid = obj;
self.on_drop.key.min_type = ty;
self.on_drop.key.min_offset = off;
}
}
}
impl<'buf, F, K> ExactSizeIterator for SearchIter<'buf, F, K>
where
K: FromDiskKey,
F: FnOnce(DiskKey) -> K,
{
}
impl<'buf, F, K> Iterator for SearchIter<'buf, F, K>
where
K: FromDiskKey,
F: FnOnce(DiskKey) -> K,
{
type Item = SearchItem<'buf>;
fn next(&mut self) -> Option<Self::Item>
{
if self.index == 0 {
return None;
}
self.index -= 1;
let item = SearchItem {
header: unsafe { self.buffer.add(self.curr_offset).cast() },
_phantom: PhantomData,
};
self.prev_offset = self.curr_offset;
self.curr_offset += item.total_len();
Some(item)
}
fn size_hint(&self) -> (usize, Option<usize>)
{
(self.index, Some(self.index))
}
}
pub struct SearchItem<'buf>
{
header: *const btrfs_ioctl_search_header,
_phantom: PhantomData<&'buf c_char>,
}
impl<'buf> SearchItem<'buf>
{
#[inline]
pub const fn objectid(&self) -> u64
{
unsafe { read_unaligned(&raw const (*self.header).objectid) }
}
#[inline]
pub const fn ty(&self) -> u32
{
unsafe { read_unaligned(&raw const (*self.header).type_) }
}
#[inline]
pub const fn offset(&self) -> u64
{
unsafe { read_unaligned(&raw const (*self.header).offset) }
}
pub const fn key(&self) -> DiskKey
{
let hdr = unsafe { read_unaligned(&raw const (*self.header)) };
(hdr.objectid, hdr.type_, hdr.offset)
}
#[inline]
pub const fn transid(&self) -> u64
{
unsafe { read_unaligned(&raw const (*self.header).transid) }
}
#[inline]
pub const fn len(&self) -> u32
{
unsafe { read_unaligned(&raw const (*self.header).len) }
}
#[inline]
pub unsafe fn get_unchecked<T: TreeItem<'buf>>(&self) -> T
{
TreeItem::from_search_unckeched(unsafe { self.header.add(1).cast::<T>() })
}
#[inline]
pub fn get<T: TreeItem<'buf>>(&self) -> Option<T>
{
TreeItem::from_search(unsafe { self.header.add(1).cast::<T>() }, self.ty())
}
#[inline]
const fn total_len(&self) -> usize
{
size_of::<btrfs_ioctl_search_header>() + self.len() as usize
}
}
pub struct TreeSearch<'r>
{
fd: OptionFd<'r>,
args: MaybeUninit<btrfs_ioctl_search_args>,
}
impl<'a> seal::SearchKeyBuilderExt for &'a mut TreeSearch<'_>
{
#[inline(always)]
fn get_key(&mut self) -> &mut self::btrfs_ioctl_search_key
{
unsafe { &mut (*self.args.as_mut_ptr()).key }
}
}
impl<'a> SearchKeyBuilder for &'a mut TreeSearch<'_> {}
impl TreeSearch<'_>
{
pub fn search<'buf, F, K>(
&'buf mut self,
on_drop: F,
) -> IoResult<impl Iterator<Item = SearchItem<'buf>> + ExactSizeIterator>
where
K: FromDiskKey,
F: FnOnce(DiskKey) -> K,
{
let fd = self.fd.as_fd();
let args = self.args.as_mut_ptr();
let key = unsafe { &mut (*args).key };
let nr_items_in = key.nr_items;
btrfs_ioctl(fd, BTRFS_IOC_TREE_SEARCH, args)?;
let index = key.nr_items as usize;
key.nr_items = nr_items_in;
Ok(SearchIter {
index,
buffer: unsafe { &raw const (*args).buf }.cast(),
on_drop: FnOnDrop { f: ManuallyDrop::new(on_drop), key },
curr_offset: 0,
prev_offset: 0,
_phantom: PhantomData,
})
}
}
pub struct BoxedTreeSearch<'r>
{
fd: OptionFd<'r>,
args: *mut btrfs_ioctl_search_args_v2,
}
impl<'a> seal::SearchKeyBuilderExt for &'a mut BoxedTreeSearch<'_>
{
#[inline(always)]
fn get_key(&mut self) -> &mut self::btrfs_ioctl_search_key
{
unsafe { &mut (*self.args).key }
}
}
impl<'a> SearchKeyBuilder for &'a mut BoxedTreeSearch<'_> {}
impl Drop for BoxedTreeSearch<'_>
{
fn drop(&mut self)
{
unsafe {
let size = (*self.args).buf_size as usize + size_of::<btrfs_ioctl_search_args_v2>();
let align = align_of::<btrfs_ioctl_search_args_v2>();
let layout = Layout::from_size_align(size, align).unwrap();
dealloc(self.args.cast(), layout);
}
}
}
impl BoxedTreeSearch<'_>
{
pub fn search<'buf, F, K>(
&'buf mut self,
on_drop: F,
) -> IoResult<impl Iterator<Item = SearchItem<'buf>> + ExactSizeIterator>
where
K: FromDiskKey,
F: FnOnce(DiskKey) -> K,
{
let fd = self.fd.as_fd();
let args = self.args;
let key = unsafe { &mut (*args).key };
let nr_items_in = key.nr_items;
btrfs_ioctl(fd, BTRFS_IOC_TREE_SEARCH_V2, args)?;
let index = key.nr_items as usize;
key.nr_items = nr_items_in;
Ok(SearchIter {
index,
buffer: unsafe { &raw const (*args).buf }.cast(),
on_drop: FnOnDrop { f: ManuallyDrop::new(on_drop), key },
curr_offset: 0,
prev_offset: 0,
_phantom: PhantomData,
})
}
}
pub struct SearchBuilder<'r>
{
key: btrfs_ioctl_search_key,
fd: OptionFd<'r>,
}
impl seal::SearchKeyBuilderExt for SearchBuilder<'_>
{
#[inline(always)]
fn get_key(&mut self) -> &mut self::btrfs_ioctl_search_key
{
&mut self.key
}
}
impl SearchKeyBuilder for SearchBuilder<'_> {}
impl<'r> From<BorrowedFd<'r>> for SearchBuilder<'r>
{
fn from(value: BorrowedFd<'r>) -> Self
{
Self {
fd: OptionFd::Borrowed(value),
key: Default::default(),
}
}
}
impl TryFrom<&Path> for SearchBuilder<'_>
{
type Error = IoError;
fn try_from(value: &Path) -> Result<Self, Self::Error>
{
File::open(value).map(|f| Self {
fd: OptionFd::Owned(f.into()),
key: Default::default(),
})
}
}
impl TryFrom<&str> for SearchBuilder<'_>
{
type Error = IoError;
fn try_from(value: &str) -> Result<Self, Self::Error>
{
Self::try_from(Path::new(value))
}
}
impl<'r> From<OptionFd<'r>> for SearchBuilder<'r>
{
fn from(value: OptionFd<'r>) -> Self
{
Self { fd: value, key: Default::default() }
}
}
impl<'fd> SearchBuilder<'fd>
{
pub fn new(self) -> TreeSearch<'fd>
{
let mut args = MaybeUninit::<btrfs_ioctl_search_args>::uninit();
let argp = args.as_mut_ptr();
unsafe {
write(&raw mut (*argp).key, self.key);
}
TreeSearch { args, fd: self.fd }
}
pub fn new_boxed(self, buf_size: u64) -> BoxedTreeSearch<'fd>
{
let size = buf_size as usize + size_of::<btrfs_ioctl_search_args_v2>();
let align = align_of::<btrfs_ioctl_search_args_v2>();
let layout = Layout::from_size_align(size, align).unwrap();
let args = unsafe {
let args = alloc(layout).cast::<btrfs_ioctl_search_args_v2>();
if args.is_null() {
handle_alloc_error(layout)
}
write(&raw mut (*args).key, self.key);
write(&raw mut (*args).buf_size, buf_size);
args
};
BoxedTreeSearch { args, fd: self.fd }
}
}
macro_rules! tree_search_set_key_by_range {
($arg:ident; $__self:ident . $get_key_fn:ident -> $min:ident | $max:ident) => {{
match $arg.start_bound() {
Bound::Unbounded => {
if let Bound::Included(&b) = $arg.end_bound() {
$__self.$get_key_fn().$min = b;
$__self.$get_key_fn().$max = b;
return $__self;
}
}
Bound::Excluded(&b) | Bound::Included(&b) => {
$__self.$get_key_fn().$min = b;
}
}
if let Bound::Included(&b) | Bound::Excluded(&b) = $arg.end_bound() {
$__self.$get_key_fn().$max = b;
};
$__self
}};
}
#[repr(u64)]
#[derive(Clone, Copy)]
pub enum TreeId
{
RootTree = crate::bindings::BTRFS_ROOT_TREE_OBJECTID,
ExtentTree = crate::bindings::BTRFS_EXTENT_TREE_OBJECTID,
ChunkTree = crate::bindings::BTRFS_CHUNK_TREE_OBJECTID,
DevTree = crate::bindings::BTRFS_DEV_TREE_OBJECTID,
FsTree = crate::bindings::BTRFS_FS_TREE_OBJECTID,
RootTreeDir = crate::bindings::BTRFS_ROOT_TREE_DIR_OBJECTID,
CsumTree = crate::bindings::BTRFS_CSUM_TREE_OBJECTID,
QuotaTree = crate::bindings::BTRFS_QUOTA_TREE_OBJECTID,
UuidTree = crate::bindings::BTRFS_UUID_TREE_OBJECTID,
FreeSpaceTree = crate::bindings::BTRFS_FREE_SPACE_TREE_OBJECTID,
BlockGroupTree = crate::bindings::BTRFS_BLOCK_GROUP_TREE_OBJECTID,
}
impl PartialEq<u64> for TreeId
{
fn eq(&self, other: &u64) -> bool
{
(*self as u64).eq(other)
}
}
impl PartialOrd<u64> for TreeId
{
fn partial_cmp(&self, other: &u64) -> Option<std::cmp::Ordering>
{
(*self as u64).partial_cmp(other)
}
}
impl PartialEq<TreeId> for u64
{
fn eq(&self, other: &TreeId) -> bool
{
(*other as u64).eq(self)
}
}
impl PartialOrd<TreeId> for u64
{
fn partial_cmp(&self, other: &TreeId) -> Option<std::cmp::Ordering>
{
(*other as u64).partial_cmp(self)
}
}
mod seal
{
pub trait SearchKeyBuilderExt
{
fn get_key(&mut self) -> &mut super::btrfs_ioctl_search_key;
}
}
pub trait SearchKeyBuilder: seal::SearchKeyBuilderExt + Sized
{
fn tree(mut self, tree_id: TreeId) -> Self
{
self.get_key().tree_id = tree_id as u64;
self
}
fn item_limit(mut self, limit: u32) -> Self
{
self.get_key().nr_items = limit;
self
}
fn offset(mut self, offset: impl RangeBounds<u64>) -> Self
{
tree_search_set_key_by_range!(offset; self.get_key -> min_offset | max_offset)
}
fn objectid(mut self, objectid: impl RangeBounds<u64>) -> Self
{
tree_search_set_key_by_range!(objectid; self.get_key -> min_objectid | max_objectid)
}
fn transid(mut self, transid: impl RangeBounds<u64>) -> Self
{
tree_search_set_key_by_range!(transid; self.get_key -> min_transid | max_transid)
}
fn item_type(mut self, item_type: impl RangeBounds<u32>) -> Self
{
tree_search_set_key_by_range!(item_type; self.get_key -> min_type | max_type)
}
}