use crate::{
raw::{
BTRFS_FIRST_FREE_OBJECTID, btrfs_ioc_ino_lookup,
btrfs_ioc_ino_lookup_user, btrfs_ioc_ino_paths,
btrfs_ioc_logical_ino_v2, btrfs_ioctl_ino_lookup_user_args,
btrfs_root_ref,
},
tree_search::{SearchFilter, tree_search},
};
use std::os::fd::{AsRawFd, BorrowedFd};
pub fn lookup_path_rootid(fd: BorrowedFd<'_>) -> nix::Result<u64> {
let mut args = crate::raw::btrfs_ioctl_ino_lookup_args {
treeid: 0,
objectid: u64::from(BTRFS_FIRST_FREE_OBJECTID),
..unsafe { std::mem::zeroed() }
};
unsafe {
btrfs_ioc_ino_lookup(fd.as_raw_fd(), &raw mut args)?;
}
Ok(args.treeid)
}
pub fn ino_paths(fd: BorrowedFd<'_>, inum: u64) -> nix::Result<Vec<String>> {
const PATH_MAX: usize = 4096;
let mut buf = vec![0u8; PATH_MAX];
let mut args = crate::raw::btrfs_ioctl_ino_path_args {
inum,
size: PATH_MAX as u64,
reserved: [0; 4],
fspath: buf.as_mut_ptr() as u64,
};
unsafe {
btrfs_ioc_ino_paths(fd.as_raw_fd(), &raw mut args)?;
}
#[allow(clippy::cast_ptr_alignment)]
let container =
unsafe { &*buf.as_ptr().cast::<crate::raw::btrfs_data_container>() };
let mut paths = Vec::new();
for i in 0..container.elem_cnt as usize {
#[allow(clippy::cast_possible_truncation)] let val_offset = unsafe {
let val_ptr = container.val.as_ptr();
*val_ptr.add(i) as usize
};
let val_base = container.val.as_ptr() as usize;
let path_ptr = (val_base + val_offset) as *const i8;
let c_str = unsafe { std::ffi::CStr::from_ptr(path_ptr) };
if let Ok(path_str) = c_str.to_str() {
paths.push(path_str.to_string());
}
}
Ok(paths)
}
#[derive(Debug, Clone)]
pub struct LogicalInoResult {
pub inode: u64,
pub offset: u64,
pub root: u64,
}
pub fn logical_ino(
fd: BorrowedFd<'_>,
logical: u64,
ignore_offset: bool,
bufsize: Option<u64>,
) -> nix::Result<Vec<LogicalInoResult>> {
const MAX_BUFSIZE: u64 = 16 * 1024 * 1024; const DEFAULT_BUFSIZE: u64 = 64 * 1024;
let size = std::cmp::min(bufsize.unwrap_or(DEFAULT_BUFSIZE), MAX_BUFSIZE);
#[allow(clippy::cast_possible_truncation)] let mut buf = vec![0u8; size as usize];
let mut flags = 0u64;
if ignore_offset {
flags |= u64::from(crate::raw::BTRFS_LOGICAL_INO_ARGS_IGNORE_OFFSET);
}
let mut args = crate::raw::btrfs_ioctl_logical_ino_args {
logical,
size,
reserved: [0; 3],
flags,
inodes: buf.as_mut_ptr() as u64,
};
unsafe {
btrfs_ioc_logical_ino_v2(fd.as_raw_fd(), &raw mut args)?;
}
#[allow(clippy::cast_ptr_alignment)]
let container =
unsafe { &*buf.as_ptr().cast::<crate::raw::btrfs_data_container>() };
let mut results = Vec::new();
for i in (0..container.elem_cnt as usize).step_by(3) {
if i + 2 < container.elem_cnt as usize {
let val_offset_inum = unsafe {
let val_ptr = container.val.as_ptr();
*val_ptr.add(i)
};
let val_offset_offset = unsafe {
let val_ptr = container.val.as_ptr();
*val_ptr.add(i + 1)
};
let val_offset_root = unsafe {
let val_ptr = container.val.as_ptr();
*val_ptr.add(i + 2)
};
results.push(LogicalInoResult {
inode: val_offset_inum,
offset: val_offset_offset,
root: val_offset_root,
});
}
}
Ok(results)
}
pub fn subvolid_resolve(
fd: BorrowedFd<'_>,
subvol_id: u64,
) -> nix::Result<String> {
let mut path = String::new();
subvolid_resolve_sub(fd, &mut path, subvol_id)?;
Ok(path)
}
fn subvolid_resolve_sub(
fd: BorrowedFd<'_>,
path: &mut String,
subvol_id: u64,
) -> nix::Result<()> {
use crate::raw::BTRFS_FS_TREE_OBJECTID;
if subvol_id == u64::from(BTRFS_FS_TREE_OBJECTID) {
return Ok(());
}
let mut found = false;
tree_search(
fd,
SearchFilter::for_objectid_range(
u64::from(crate::raw::BTRFS_ROOT_TREE_OBJECTID),
crate::raw::BTRFS_ROOT_BACKREF_KEY,
subvol_id,
subvol_id,
),
|hdr, data| {
use std::mem::{offset_of, size_of};
found = true;
let parent_subvol_id = hdr.offset;
subvolid_resolve_sub(fd, path, parent_subvol_id)?;
let header_size = size_of::<btrfs_root_ref>();
if data.len() < header_size {
return Err(nix::errno::Errno::EOVERFLOW);
}
let dirid = u64::from_le_bytes(
data[offset_of!(btrfs_root_ref, dirid)..][..8]
.try_into()
.unwrap(),
);
let name_off = offset_of!(btrfs_root_ref, name_len);
let name_len =
u16::from_le_bytes([data[name_off], data[name_off + 1]])
as usize;
if data.len() < header_size + name_len {
return Err(nix::errno::Errno::EOVERFLOW);
}
let name_bytes = &data[header_size..header_size + name_len];
if dirid != u64::from(BTRFS_FIRST_FREE_OBJECTID) {
let mut ino_lookup_args =
crate::raw::btrfs_ioctl_ino_lookup_args {
treeid: parent_subvol_id,
objectid: dirid,
..unsafe { std::mem::zeroed() }
};
unsafe {
btrfs_ioc_ino_lookup(
fd.as_raw_fd(),
&raw mut ino_lookup_args,
)?;
}
let dir_name = unsafe {
std::ffi::CStr::from_ptr(ino_lookup_args.name.as_ptr())
}
.to_str()
.map_err(|_| nix::errno::Errno::EINVAL)?;
if !dir_name.is_empty() {
if !path.is_empty() {
path.push('/');
}
path.push_str(dir_name);
}
}
if !path.is_empty() {
path.push('/');
}
let name_str = std::str::from_utf8(name_bytes)
.map_err(|_| nix::errno::Errno::EINVAL)?;
path.push_str(name_str);
Ok(())
},
)?;
if !found {
return Err(nix::errno::Errno::ENOENT);
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct InoLookupUserResult {
pub name: String,
pub path: String,
}
pub fn ino_lookup_user(
fd: BorrowedFd<'_>,
treeid: u64,
dirid: u64,
) -> nix::Result<InoLookupUserResult> {
let mut args = btrfs_ioctl_ino_lookup_user_args {
dirid,
treeid,
..unsafe { std::mem::zeroed() }
};
unsafe {
btrfs_ioc_ino_lookup_user(fd.as_raw_fd(), &raw mut args)?;
}
let name = unsafe { std::ffi::CStr::from_ptr(args.name.as_ptr()) }
.to_str()
.map_err(|_| nix::errno::Errno::EINVAL)?
.to_owned();
let path = unsafe { std::ffi::CStr::from_ptr(args.path.as_ptr()) }
.to_str()
.map_err(|_| nix::errno::Errno::EINVAL)?
.to_owned();
Ok(InoLookupUserResult { name, path })
}