libbtrfs 0.0.20

Rust library for working with the btrfs filesystem
Documentation
#![allow(clippy::len_without_is_empty, clippy::new_ret_no_self)]

//! Btrfs tree searches.
//!
//!
use crate::{
    bindings::{
        btrfs_ioctl_search_args, btrfs_ioctl_search_header, btrfs_ioctl_search_key,
        BTRFS_IOC_TREE_SEARCH,
    },
    util::{btrfs_ioctl, OptionFd},
};
use std::{
    fs::File,
    io,
    mem::{size_of, MaybeUninit as Uninit},
    os::fd::{AsRawFd, RawFd},
    path::Path,
    ptr::addr_of_mut,
    slice, str,
};

/// Btrfs search item that contains name information.
///
/// Btrfs types than implement this trait can call the [`SearchItem::name_as_ibytes()`],
/// [`SearchItem::name_as_bytes()`], and [`SearchItem::name_as_str()`] methods.
///
/// # Safety
///
/// Structs that implement this trait are given a additional bytes at the end of the buffer
/// returned by a tree search wich is designated for the name of that type.
pub unsafe trait SearchName: Sized {
    /// Structs that implement this trait have a field `name_len` that denotes the length of the
    /// name located at the offset of the struct, which does not end in a null byte.
    fn name_len(&self) -> usize;
}

macro_rules! impl_tree_search_name {
    ($( $btrfs_search_item:ident ),+) => [$(

        unsafe impl SearchName for $crate::bindings::$btrfs_search_item {
            #[inline]
            fn name_len(&self) -> usize {
                u16::from_le(self.name_len) as usize
            }
        }

    )+]
}

impl_tree_search_name![
    btrfs_dir_item,
    btrfs_inode_ref,
    btrfs_root_ref,
    btrfs_inode_extref
];

/// Returned by [`SearchBuf::next`]
pub struct SearchItem<'a>(&'a [i8], &'a [i8]);

impl<'a> SearchItem<'a> {
    /// Gets the underlying search header as a raw pointer
    #[inline]
    const fn hdr(&self) -> *const btrfs_ioctl_search_header {
        self.0.as_ptr().cast()
    }

    /// Gets the transaction id for this item
    #[inline]
    pub const fn transid(&self) -> u64 {
        unsafe { self.hdr().read_unaligned().transid }
    }

    /// Gets the object id for this item
    #[inline]
    pub const fn objectid(&self) -> u64 {
        unsafe { self.hdr().read_unaligned().objectid }
    }

    /// Gets the offset for this item
    #[inline]
    pub const fn offset(&self) -> u64 {
        unsafe { self.hdr().read_unaligned().offset }
    }

    /// Gets the key type for this item
    #[inline]
    pub const fn key(&self) -> u32 {
        unsafe { self.hdr().read_unaligned().type_ }
    }

    /// Gets the data length for this item
    #[inline]
    pub const fn len(&self) -> usize {
        unsafe { self.hdr().read_unaligned().len as usize }
    }

    /// Gets the data item from the search buffer
    #[inline]
    pub fn get<T: From<&'a [i8]>>(&self) -> T {
        self.1.into()
    }

    /// Get the name for a type that implement the [`SearchName`] trait as a signed byte slice
    ///
    /// #Panics
    ///
    /// function will panic if the offset of `item` exceeds the length of the search buffer
    ///
    #[inline]
    pub fn name_as_ibytes<T: SearchName>(&self, item: &T) -> &'a [i8] {
        let buf = &self.1[size_of::<T>()..];
        &buf[..item.name_len()]
    }

    /// Get the name for a type that implement the [`SearchName`] trait as a byte slice
    ///
    /// #Panics
    ///
    /// See [`SearchItem::name_as_ibytes`]
    ///
    #[inline]
    pub fn name_as_bytes<T: SearchName>(&self, item: &T) -> &'a [u8] {
        let ibytes = self.name_as_ibytes(item);
        unsafe { slice::from_raw_parts(ibytes.as_ptr().cast(), ibytes.len()) }
    }

    /// Get the name for a type that implement the [`SearchName`] trait as a str slice
    ///
    /// #Panics
    ///
    /// See [`SearchItem::name_as_ibytes`]
    ///
    #[inline]
    pub fn name_as_str<T: SearchName>(&self, item: &T) -> Result<&str, str::Utf8Error> {
        str::from_utf8(self.name_as_bytes(item))
    }
}

type Callback = Box<dyn Fn(&mut btrfs_ioctl_search_key, &SearchItem)>;

struct SearchCallback<'a> {
    f: Callback,
    key: &'a mut btrfs_ioctl_search_key,
}

/// Iterator over items of a btrfs tree search
///
/// Returned by the [`TreeSearch::search`] method, where calls to next yeild instances a [`SearchItem`]
pub struct SearchBuf<'a> {
    /// Number if items this search contains
    pub nr_items: u32,
    buf: &'a [i8],
    positon: u32,
    offset: usize,

    callback: Option<SearchCallback<'a>>,
}

impl<'a> Iterator for SearchBuf<'a> {
    type Item = SearchItem<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.nr_items == self.positon {
            return None;
        }
        let (hdr, buf) = &self.buf[self.offset..].split_at(size_of::<btrfs_ioctl_search_header>());
        let item = SearchItem(hdr, buf);

        if let Some(cb) = self.callback.as_mut() {
            (cb.f)(cb.key, &item)
        }

        self.offset += size_of::<btrfs_ioctl_search_header>() + item.len();
        self.positon += 1;
        Some(item)
    }
}

/// Provides functionality to preform btrfs tree searches
///
/// This struct contains the [`TreeSearch::search`] method that preforms a btrfs tree search using
/// the search key provided by the [`TreeSearch::try_new`] method which constructs a new
/// `TreeSearch::search` instance
///
pub struct TreeSearch {
    fd: OptionFd,
    sargs: Uninit<btrfs_ioctl_search_args>,
    nr_items: u32,
}

impl TreeSearch {
    /// Does a btrfs tree search
    ///
    /// This function is simmilar to the [`TreeSearch::search`] method but call `f` on each time
    /// the next method is called on the returned [`SearchBuf`]. This provides a possibly nicer way
    /// to update the search key for the next search.
    ///
    /// # Examples
    ///
    /// ```
    /// TODO
    ///
    /// ```
    pub fn search_with<F>(&mut self, f: F) -> io::Result<Option<SearchBuf>>
    where
        F: Fn(&mut btrfs_ioctl_search_key, &SearchItem) + 'static,
    {
        self.nr_items(self.nr_items);
        btrfs_ioctl(
            self.fd.as_raw_fd(),
            BTRFS_IOC_TREE_SEARCH,
            self.sargs.as_mut_ptr(),
        )?;

        // SAFTEY: tree search ioctl initialized the search buf and the search key was initialized by the constructor
        let args = unsafe { self.sargs.assume_init_mut() };
        Ok(if args.key.nr_items == 0 {
            None
        } else {
            Some(SearchBuf {
                buf: &args.buf,
                nr_items: args.key.nr_items,
                positon: 0,
                offset: 0,

                callback: Some(SearchCallback {
                    f: Box::new(f),
                    key: &mut args.key,
                }),
            })
        })
    }

    /// Does a btrfs tree search
    ///
    /// Returns an [`Option`] contaning the [`Items`] iterator if the search found > 0 items.
    /// If no items were found, [`None`] is returned.
    ///
    pub fn search(&mut self) -> io::Result<Option<SearchBuf>> {
        self.nr_items(self.nr_items);
        btrfs_ioctl(
            self.fd.as_raw_fd(),
            BTRFS_IOC_TREE_SEARCH,
            self.sargs.as_mut_ptr(),
        )?;

        // SAFTEY: tree search ioctl initialized the search buf and the search key was initialized by the constructor
        let args = unsafe { self.sargs.assume_init_ref() };
        Ok(if args.key.nr_items == 0 {
            None
        } else {
            Some(SearchBuf {
                buf: &args.buf,
                nr_items: args.key.nr_items,
                positon: 0,
                offset: 0,

                callback: None,
            })
        })
    }

    /// Constructs a new `TreeSearch`
    ///
    /// Fails only if a file cannot be opened at `fs`
    ///
    /// For an infallable version see [`fd::TreeSearch::new`]
    pub fn try_new<F, P: AsRef<Path>>(fs: P, f: F) -> io::Result<Self>
    where
        F: FnOnce(&mut btrfs_ioctl_search_key),
    {
        let mut key = btrfs_ioctl_search_key::default();
        f(&mut key);

        let mut sargs = Uninit::<btrfs_ioctl_search_args>::uninit();
        unsafe {
            addr_of_mut!((*sargs.as_mut_ptr()).key).write(key);
        }
        Ok(TreeSearch {
            sargs,
            nr_items: key.nr_items,
            fd: OptionFd::File(File::open(fs)?),
        })
    }

    // functions for updated the search key.
    // Cant use Fn trait like constructor or search_with() because the search_args which holds the
    // key is uninitialized.

    #[inline]
    pub fn nr_items(&mut self, nr_items: u32) -> &mut Self {
        self.nr_items = nr_items;
        unsafe { addr_of_mut!((*self.sargs.as_mut_ptr()).key.nr_items).write(self.nr_items) }
        self
    }

    #[inline]
    pub fn tree_id(&mut self, tree_id: u64) -> &mut Self {
        unsafe { addr_of_mut!((*self.sargs.as_mut_ptr()).key.tree_id).write(tree_id) }
        self
    }

    #[inline]
    pub fn min_offset(&mut self, offset: u64) -> &mut Self {
        unsafe { addr_of_mut!((*self.sargs.as_mut_ptr()).key.min_offset).write(offset) }
        self
    }

    #[inline]
    pub fn max_offset(&mut self, offset: u64) -> &mut Self {
        unsafe { addr_of_mut!((*self.sargs.as_mut_ptr()).key.max_offset).write(offset) }
        self
    }

    #[inline]
    pub fn min_objectid(&mut self, objectid: u64) -> &mut Self {
        unsafe { addr_of_mut!((*self.sargs.as_mut_ptr()).key.min_objectid).write(objectid) }
        self
    }

    #[inline]
    pub fn max_objectid(&mut self, objectid: u64) -> &mut Self {
        unsafe { addr_of_mut!((*self.sargs.as_mut_ptr()).key.max_objectid).write(objectid) }
        self
    }

    #[inline]
    pub fn min_type(&mut self, ty: u32) -> &mut Self {
        unsafe { addr_of_mut!((*self.sargs.as_mut_ptr()).key.min_type).write(ty) }
        self
    }

    #[inline]
    pub fn max_type(&mut self, ty: u32) -> &mut Self {
        unsafe { addr_of_mut!((*self.sargs.as_mut_ptr()).key.max_type).write(ty) }
        self
    }
}

pub mod fd {
    use super::*;

    /// See [super::TreeSearch]
    pub struct TreeSearch;

    impl TreeSearch {
        pub fn new<F>(fd: RawFd, f: F) -> super::TreeSearch
        where
            F: FnOnce(&mut btrfs_ioctl_search_key),
        {
            let mut key = btrfs_ioctl_search_key::default();
            f(&mut key);

            let mut sargs = Uninit::<btrfs_ioctl_search_args>::uninit();
            unsafe {
                addr_of_mut!((*sargs.as_mut_ptr()).key).write(key);
            }
            super::TreeSearch {
                sargs,
                nr_items: key.nr_items,
                fd: OptionFd::Raw(fd),
            }
        }
    }
}