use std::ffi::CStr;
use std::ops::RangeInclusive;
#[cfg(fbcode_build)]
pub use btrfs_sys::*;
#[cfg(not(fbcode_build))]
pub mod open_source;
#[cfg(not(fbcode_build))]
pub use open_source::btrfs_sys::*;
#[cfg(test)]
mod test;
#[cfg(not(fbcode_build))]
#[cfg(test)]
mod sudotest;
mod utils;
use std::path::PathBuf;
use thiserror::Error;
pub use crate::btrfs_api::utils::*;
#[derive(Error, Debug)]
pub enum Error {
#[error("System Error: {0}")]
SysError(nix::errno::Errno),
#[error("{1:?}: {0:?}")]
IoError(PathBuf, #[source] std::io::Error),
#[error("Not btrfs filesystem: {0:?}")]
NotBtrfs(PathBuf),
}
pub type Result<T> = std::result::Result<T, Error>;
mod ioctl {
use super::*;
nix::ioctl_readwrite!(search_v2, BTRFS_IOCTL_MAGIC, 17, btrfs_ioctl_search_args_v2);
nix::ioctl_readwrite!(
ino_lookup,
BTRFS_IOCTL_MAGIC,
18,
btrfs_ioctl_ino_lookup_args
);
nix::ioctl_readwrite!(ino_paths, BTRFS_IOCTL_MAGIC, 35, btrfs_ioctl_ino_path_args);
nix::ioctl_readwrite!(
logical_ino_v2,
BTRFS_IOCTL_MAGIC,
59,
btrfs_ioctl_logical_ino_args
);
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct LogicalInoItem {
pub inum: u64,
pub offset: u64,
pub root: u64,
}
pub fn logical_ino(
fd: i32,
logical: u64,
ignoring_offset: bool,
mut cb: impl FnMut(Result<&[LogicalInoItem]>),
) {
let mut data = WithMemAfter::<btrfs_data_container, 4096>::new();
let mut args = btrfs_ioctl_logical_ino_args {
logical,
size: data.total_size() as u64,
reserved: Default::default(),
flags: if ignoring_offset {
BTRFS_LOGICAL_INO_ARGS_IGNORE_OFFSET as u64
} else {
0
},
inodes: data.as_mut_ptr() as u64,
};
unsafe {
match ioctl::logical_ino_v2(fd, &mut args) {
Ok(_) => {
let inodes = std::slice::from_raw_parts(
data.extra_ptr() as *const LogicalInoItem,
(data.elem_cnt / 3) as usize,
);
cb(Ok(inodes));
}
Err(err) => {
cb(Err(Error::SysError(err)));
}
}
}
}
pub fn ino_lookup(fd: i32, root: u64, inum: u64, mut cb: impl FnMut(Result<&CStr>)) {
let mut args = btrfs_ioctl_ino_lookup_args {
treeid: root,
objectid: inum,
name: [0; BTRFS_INO_LOOKUP_PATH_MAX as usize],
};
unsafe {
match ioctl::ino_lookup(fd, &mut args) {
Ok(_) => {
cb(Ok(CStr::from_ptr(args.name.as_ptr())));
}
Err(err) => {
cb(Err(Error::SysError(err)));
}
}
}
}
pub struct SearchKey {
pub objectid: u64,
pub typ: u8,
pub offset: u64,
}
impl SearchKey {
pub const MIN: Self = SearchKey::new(u64::MIN, u8::MIN, u64::MIN);
pub const MAX: Self = SearchKey::new(u64::MAX, u8::MAX, u64::MAX);
pub const ALL: RangeInclusive<Self> = Self::MIN..=Self::MAX;
pub const fn range_fixed_id_type(objectid: u64, typ: u8) -> RangeInclusive<Self> {
Self::new(objectid, typ, u64::MIN)..=Self::new(objectid, typ, u64::MAX)
}
pub const fn new(objectid: u64, typ: u8, offset: u64) -> Self {
Self {
objectid,
typ,
offset,
}
}
pub fn next(&self) -> Self {
let (offset, carry1) = self.offset.overflowing_add(1);
let (typ, carry2) = self.typ.overflowing_add(carry1 as u8);
let (objectid, _) = self.objectid.overflowing_add(carry2 as u64);
SearchKey {
objectid,
typ,
offset,
}
}
fn from(h: &btrfs_ioctl_search_header) -> Self {
SearchKey {
objectid: h.objectid,
typ: h.type_ as u8,
offset: h.offset,
}
}
}
pub fn tree_search_cb(
fd: i32,
tree_id: u64,
range: RangeInclusive<SearchKey>,
mut cb: impl FnMut(&btrfs_ioctl_search_header, &[u8]),
) -> Result<()> {
const BUF_SIZE: usize = 16 * 1024;
let mut args = WithMemAfter::<btrfs_ioctl_search_args_v2, BUF_SIZE>::new();
args.key = btrfs_ioctl_search_key {
tree_id,
min_objectid: range.start().objectid,
max_objectid: range.end().objectid,
min_offset: range.start().offset,
max_offset: range.end().offset,
min_transid: u64::MIN,
max_transid: u64::MAX,
min_type: range.start().typ as u32,
max_type: range.end().typ as u32,
nr_items: u32::MAX,
unused: 0,
unused1: 0,
unused2: 0,
unused3: 0,
unused4: 0,
};
args.buf_size = args.extra_size() as u64;
loop {
args.key.nr_items = u32::MAX;
unsafe {
ioctl::search_v2(fd, args.as_mut_ptr()).map_err(Error::SysError)?;
}
if args.key.nr_items == 0 {
break;
}
let mut ptr = args.buf.as_ptr() as *const u8;
let mut last_search_header: *const btrfs_ioctl_search_header = std::ptr::null();
for _ in 0..args.key.nr_items {
let search_header =
unsafe { get_and_move_typed::<btrfs_ioctl_search_header>(&mut ptr) };
let data = unsafe {
std::slice::from_raw_parts(
get_and_move(&mut ptr, (*search_header).len as usize),
(*search_header).len as usize,
)
};
last_search_header = search_header;
unsafe {
cb(&*search_header, data);
}
}
let min_key = unsafe { SearchKey::from(&*last_search_header).next() };
args.key.min_objectid = min_key.objectid;
args.key.min_type = min_key.typ as u32;
args.key.min_offset = min_key.offset;
}
Ok(())
}
pub fn find_root_backref(fd: i32, root_id: u64) -> Result<Option<(String, u64)>> {
let mut res: Option<(String, u64)> = None;
tree_search_cb(
fd,
BTRFS_ROOT_TREE_OBJECTID as u64,
SearchKey::range_fixed_id_type(root_id, BTRFS_ROOT_BACKREF_KEY as u8),
|sh, data| {
match sh.type_ {
BTRFS_ROOT_BACKREF_KEY => {
let mut data_ptr = data.as_ptr();
let root_ref = unsafe { get_and_move_typed::<btrfs_root_ref>(&mut data_ptr) };
let name = unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(
data_ptr,
(*root_ref).name_len as usize,
))
};
res = Some((name.to_owned(), sh.offset));
}
_ => {}
};
},
)?;
Ok(res)
}