libbtrfs 0.0.20

Rust library for working with the btrfs filesystem
Documentation
//! Btrfs filesystem operations.
//!
use crate::{
    bindings::{
        btrfs_ioctl_fs_info_args, btrfs_ioctl_space_args, btrfs_ioctl_space_info,
        BTRFS_FS_INFO_FLAG_CSUM_INFO, BTRFS_FS_INFO_FLAG_GENERATION,
        BTRFS_FS_INFO_FLAG_METADATA_UUID, BTRFS_IOC_FS_INFO, BTRFS_IOC_SPACE_INFO,
    },
    util::btrfs_ioctl,
    Opt,
};
use std::{
    alloc::{alloc, dealloc, Layout},
    ffi::CString,
    fs::File,
    io,
    mem::{align_of, size_of, MaybeUninit as Uninit},
    os::unix::{io::AsRawFd, prelude::OsStrExt},
    path::Path,
    ptr,
};
use uuid::Uuid;

pub mod block_group;

/// Information about a btrfs filesystem
///
/// This structure is returned by the [`info`] function
pub struct FsInfo(btrfs_ioctl_fs_info_args);

impl FsInfo {
    /// Maximum device id for the filesystem
    pub fn max_id(&self) -> u64 {
        self.0.max_id
    }

    /// Number of devices associated with the filesystem
    pub fn num_devices(&self) -> u64 {
        self.0.num_devices
    }

    /// Filesystem UUID
    pub fn fsid(&self) -> Uuid {
        Uuid::from_bytes(self.0.fsid)
    }

    /// Treeblock size in which btrfs stores metadata
    pub fn nodesize(&self) -> u32 {
        self.0.nodesize
    }

    /// Minimum data block allocation unit
    pub fn sectorsize(&self) -> u32 {
        self.0.sectorsize
    }

    pub fn clone_alignment(&self) -> u32 {
        self.0.clone_alignment
    }

    /// Checksum type
    ///
    /// # Panics
    ///
    /// Will panic if [`Opt::CSUM_INFO`] flag was not provided
    ///
    pub fn csum_type(&self) -> u16 {
        (self.0.flags & BTRFS_FS_INFO_FLAG_CSUM_INFO != 0)
            .then_some(self.0.csum_type)
            .expect("CSUM_INFO Flag not provided")
    }

    /// Checksum size
    ///
    /// # Panics
    ///
    /// Will panic if [`Opt::CSUM_INFO`] flag was not provided
    ///
    pub fn csum_size(&self) -> u16 {
        (self.0.flags & BTRFS_FS_INFO_FLAG_CSUM_INFO != 0)
            .then_some(self.0.csum_size)
            .expect("CSUM_INFO Flag not provided")
    }

    /// Filesystem generation
    ///
    /// # Panics
    ///
    /// Will panic if [`Opt::GENERATION`] flag was not provided
    ///
    pub fn generation(&self) -> u64 {
        (self.0.flags & BTRFS_FS_INFO_FLAG_GENERATION != 0)
            .then_some(self.0.generation)
            .expect("GENERATION Flag not provided")
    }

    /// Filesystem metadata uuid
    ///
    /// # Panics
    ///
    /// Will panic if [`Opt::METADATA_UUID`] flag was not provided
    ///
    pub fn metadata_uuid(&self) -> Uuid {
        (self.0.flags & BTRFS_FS_INFO_FLAG_METADATA_UUID != 0)
            .then_some(Uuid::from_bytes(self.0.metadata_uuid))
            .expect("METADATA_UUID Flag not provided")
    }
}

/// Information about a chunk allocated to a particular block group
///
/// Returned by the [`Spaces`] iterator
pub struct SpaceInfo(btrfs_ioctl_space_info);

impl SpaceInfo {
    /// Block type for this chunk
    pub fn block_type(&self) -> block_group::BlockType {
        self.0.flags.into()
    }

    /// Raid profile for this chunk
    pub fn raid_profile(&self) -> io::Result<block_group::RaidProfile> {
        self.0.flags.try_into()
    }

    /// Raw flags for this chunk
    pub fn flags(&self) -> u64 {
        self.0.flags
    }

    /// Used bytes for this chunk
    pub fn used_bytes(&self) -> u64 {
        self.0.used_bytes
    }

    /// Total bytes for this chunk
    pub fn total_bytes(&self) -> u64 {
        self.0.total_bytes
    }
}

/// Iterator over space allocation for a block groups in a btrfs filesystem
///
/// This iterator is returned from the [`get_spaces`] function and will yield instances of
/// <code>[io::Result]<[SpaceInfo]></code>
///
pub struct Spaces(Vec<SpaceInfo>);

impl IntoIterator for Spaces {
    type Item = SpaceInfo;
    type IntoIter = std::vec::IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.into_iter()
    }
}

/// Check if a path references a btrfs filesystem
///
/// This function returns `Ok(true)` if `fs` can be determined to reference a btrfs filesystem.
pub fn is_btrfs<P: AsRef<Path>>(fs: P) -> io::Result<bool> {
    let cstring = CString::new(fs.as_ref().as_os_str().as_bytes()).unwrap();
    let mut sfs = Uninit::<libc::statfs>::uninit();
    unsafe {
        syscall!(statfs(cstring.as_ptr(), sfs.as_mut_ptr()))?;

        Ok(sfs.assume_init().f_type == libc::BTRFS_SUPER_MAGIC)
    }
}

/// Returns an [`FsInfo`] struct
///
/// The returned [`FsInfo`] provieds information about the btrfs filesystem referenced by `fs`.
/// The returned [`FsInfo`] can be custumized with `flags`
///
/// # Flags
///
/// Full list is available flags:
///
/// * [`Opt::CSUM_INFO `]
///
/// Request information about checksum type and size
///
/// * [`Opt::GENERATION `]
///
/// Request information about filesystem generation
///
/// * [`Opt::METADATA_UUID `]
///
/// Request information about filesystem metadata UUID
///
/// # Examples
///
/// ```no_run
/// use libbtrfs::{fs, Opt};
///
/// let fs_info = fs::info("/", Opt::CSUM_INFO)?;
///
/// // will not panic since Opt::CSUM_INFO flag was provieded
/// let csum_type = fs_info.csum_type();
///
/// # Ok::<(), std::io::Error>(())
/// ```
///
pub fn info<P: AsRef<Path>>(fs: P, flags: Opt) -> io::Result<FsInfo> {
    let fd = File::open(fs.as_ref())?;

    fd::info(fd.as_raw_fd(), flags)
}

/// Returns the [`Spaces`] iterator
///
/// Returns an iterator yeilding instances of <code>[io::Result]<[SpaceInfo]></code> for a btrfs
/// filesystem referenced by `fs`
///
/// # Errors
/// todo
///
pub fn get_spaces<P: AsRef<Path>>(fs: P) -> io::Result<Spaces> {
    let fd = File::open(fs.as_ref())?;

    fd::get_spaces(fd.as_raw_fd())
}

pub mod fd {
    use super::*;
    use std::os::unix::io::RawFd;

    /// See [super::is_btrfs()]
    pub fn is_btrfs(fd: RawFd) -> io::Result<bool> {
        let mut sfs = Uninit::<libc::statfs>::uninit();
        unsafe {
            syscall!(fstatfs(fd, sfs.as_mut_ptr()))?;

            Ok(sfs.assume_init().f_type == libc::BTRFS_SUPER_MAGIC)
        }
    }

    /// See [super::info()]
    pub fn info(fd: RawFd, flags: Opt) -> io::Result<FsInfo> {
        let mut args = btrfs_ioctl_fs_info_args {
            flags: flags.fs_info_flags(),
            ..Default::default()
        };
        btrfs_ioctl(fd, BTRFS_IOC_FS_INFO, &mut args)?;

        Ok(FsInfo(args))
    }

    /// See [super::get_spaces()]
    pub fn get_spaces(fd: RawFd) -> io::Result<Spaces> {
        let mut args = btrfs_ioctl_space_args::default();

        btrfs_ioctl(fd, BTRFS_IOC_SPACE_INFO, &mut args)?;
        let total_spaces = args.total_spaces as usize;
        let size = size_of::<btrfs_ioctl_space_args>()
            + (size_of::<btrfs_ioctl_space_info>() * total_spaces);
        let layout = Layout::from_size_align(size, align_of::<btrfs_ioctl_space_args>()).unwrap();
        unsafe {
            let argp = alloc(layout).cast::<btrfs_ioctl_space_args>();
            if argp.is_null() {
                error!(OutOfMemory)
            }
            (*argp).space_slots = args.total_spaces;
            (*argp).total_spaces = 0;

            btrfs_ioctl(fd, BTRFS_IOC_SPACE_INFO, argp)?;
            let ret = (*argp)
                .spaces
                .as_slice(total_spaces)
                .iter()
                .map(|info| SpaceInfo(ptr::read(info)))
                .collect::<Vec<SpaceInfo>>();

            dealloc(argp.cast(), layout);
            Ok(Spaces(ret))
        }
    }
}