use crate::{
raw::{
self, btrfs_ioc_clone_range, btrfs_ioc_encoded_read,
btrfs_ioc_encoded_write, btrfs_ioc_send, btrfs_ioc_set_received_subvol,
btrfs_ioctl_clone_range_args, btrfs_ioctl_encoded_io_args,
btrfs_ioctl_received_subvol_args, btrfs_ioctl_send_args,
},
tree_search::{SearchFilter, tree_search},
};
use bitflags::bitflags;
use std::os::fd::{AsRawFd, BorrowedFd, RawFd};
use uuid::Uuid;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SendFlags: u64 {
const NO_FILE_DATA = raw::BTRFS_SEND_FLAG_NO_FILE_DATA as u64;
const OMIT_STREAM_HEADER = raw::BTRFS_SEND_FLAG_OMIT_STREAM_HEADER as u64;
const OMIT_END_CMD = raw::BTRFS_SEND_FLAG_OMIT_END_CMD as u64;
const VERSION = raw::BTRFS_SEND_FLAG_VERSION as u64;
const COMPRESSED = raw::BTRFS_SEND_FLAG_COMPRESSED as u64;
}
}
pub fn send(
subvol_fd: BorrowedFd<'_>,
send_fd: RawFd,
parent_root: u64,
clone_sources: &mut [u64],
flags: SendFlags,
version: u32,
) -> nix::Result<()> {
let mut args: btrfs_ioctl_send_args = unsafe { std::mem::zeroed() };
args.send_fd = i64::from(send_fd);
args.parent_root = parent_root;
args.clone_sources_count = clone_sources.len() as u64;
args.clone_sources = if clone_sources.is_empty() {
std::ptr::null_mut()
} else {
clone_sources.as_mut_ptr()
};
args.flags = flags.bits();
args.version = version;
unsafe {
btrfs_ioc_send(subvol_fd.as_raw_fd(), &raw const args)?;
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct SubvolumeSearchResult {
pub root_id: u64,
}
#[allow(clippy::cast_possible_wrap)] pub fn received_subvol_set(
fd: BorrowedFd<'_>,
uuid: &Uuid,
stransid: u64,
) -> nix::Result<u64> {
let mut args: btrfs_ioctl_received_subvol_args =
unsafe { std::mem::zeroed() };
let uuid_bytes = uuid.as_bytes();
for (i, &b) in uuid_bytes.iter().enumerate() {
args.uuid[i] = b as std::os::raw::c_char;
}
args.stransid = stransid;
unsafe {
btrfs_ioc_set_received_subvol(fd.as_raw_fd(), &raw mut args)?;
}
Ok(args.rtransid)
}
pub fn clone_range(
dest_fd: BorrowedFd<'_>,
src_fd: BorrowedFd<'_>,
src_offset: u64,
src_length: u64,
dest_offset: u64,
) -> nix::Result<()> {
let args = btrfs_ioctl_clone_range_args {
src_fd: i64::from(src_fd.as_raw_fd()),
src_offset,
src_length,
dest_offset,
};
unsafe {
btrfs_ioc_clone_range(dest_fd.as_raw_fd(), &raw const args)?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::cast_possible_wrap)] pub fn encoded_write(
fd: BorrowedFd<'_>,
data: &[u8],
offset: u64,
unencoded_file_len: u64,
unencoded_len: u64,
unencoded_offset: u64,
compression: u32,
encryption: u32,
) -> nix::Result<()> {
let iov = nix::libc::iovec {
iov_base: data.as_ptr() as *mut _,
iov_len: data.len(),
};
let mut args: btrfs_ioctl_encoded_io_args = unsafe { std::mem::zeroed() };
args.iov = std::ptr::from_ref(&iov) as *mut _;
args.iovcnt = 1;
args.offset = offset as i64;
args.len = unencoded_file_len;
args.unencoded_len = unencoded_len;
args.unencoded_offset = unencoded_offset;
args.compression = compression;
args.encryption = encryption;
unsafe {
btrfs_ioc_encoded_write(fd.as_raw_fd(), &raw const args)?;
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EncodedReadResult {
pub offset: u64,
pub unencoded_file_len: u64,
pub unencoded_len: u64,
pub unencoded_offset: u64,
pub compression: u32,
pub encryption: u32,
pub bytes_read: usize,
}
#[allow(clippy::cast_possible_wrap)] #[allow(clippy::cast_sign_loss)] pub fn encoded_read(
fd: BorrowedFd<'_>,
buf: &mut [u8],
offset: u64,
len: u64,
) -> nix::Result<EncodedReadResult> {
let iov = nix::libc::iovec {
iov_base: buf.as_mut_ptr().cast(),
iov_len: buf.len(),
};
let mut args: btrfs_ioctl_encoded_io_args = unsafe { std::mem::zeroed() };
args.iov = std::ptr::from_ref(&iov) as *mut _;
args.iovcnt = 1;
args.offset = offset as i64;
args.len = len;
let ret = unsafe { btrfs_ioc_encoded_read(fd.as_raw_fd(), &raw mut args) }?;
Ok(EncodedReadResult {
offset: args.offset as u64,
unencoded_file_len: args.len,
unencoded_len: args.unencoded_len,
unencoded_offset: args.unencoded_offset,
compression: args.compression,
encryption: args.encryption,
bytes_read: ret as usize,
})
}
pub fn subvolume_search_by_uuid(
fd: BorrowedFd<'_>,
uuid: &Uuid,
) -> nix::Result<u64> {
search_uuid_tree(fd, uuid, raw::BTRFS_UUID_KEY_SUBVOL)
}
pub fn subvolume_search_by_received_uuid(
fd: BorrowedFd<'_>,
uuid: &Uuid,
) -> nix::Result<u64> {
search_uuid_tree(fd, uuid, raw::BTRFS_UUID_KEY_RECEIVED_SUBVOL)
}
fn search_uuid_tree(
fd: BorrowedFd<'_>,
uuid: &Uuid,
item_type: u32,
) -> nix::Result<u64> {
let bytes = uuid.as_bytes();
let objectid = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
let offset = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
let mut key = SearchFilter::for_type(
u64::from(raw::BTRFS_UUID_TREE_OBJECTID),
item_type,
);
key.start.objectid = objectid;
key.end.objectid = objectid;
key.start.offset = offset;
key.end.offset = offset;
let mut result: Option<u64> = None;
tree_search(fd, key, |_hdr, data| {
if data.len() >= 8 {
result = Some(u64::from_le_bytes(data[0..8].try_into().unwrap()));
}
Ok(())
})?;
result.ok_or(nix::errno::Errno::ENOENT)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn send_flags_no_file_data() {
let flags = SendFlags::NO_FILE_DATA;
assert!(flags.contains(SendFlags::NO_FILE_DATA));
assert!(!flags.contains(SendFlags::COMPRESSED));
}
#[test]
fn send_flags_combine() {
let flags = SendFlags::NO_FILE_DATA | SendFlags::COMPRESSED;
assert!(flags.contains(SendFlags::NO_FILE_DATA));
assert!(flags.contains(SendFlags::COMPRESSED));
assert!(!flags.contains(SendFlags::VERSION));
}
#[test]
fn send_flags_empty() {
let flags = SendFlags::empty();
assert!(flags.is_empty());
assert_eq!(flags.bits(), 0);
}
#[test]
fn send_flags_debug() {
let flags = SendFlags::OMIT_STREAM_HEADER | SendFlags::OMIT_END_CMD;
let s = format!("{flags:?}");
assert!(s.contains("OMIT_STREAM_HEADER"), "debug: {s}");
assert!(s.contains("OMIT_END_CMD"), "debug: {s}");
}
#[test]
fn encoded_read_result_equality() {
let a = EncodedReadResult {
offset: 0,
unencoded_file_len: 4096,
unencoded_len: 4096,
unencoded_offset: 0,
compression: 0,
encryption: 0,
bytes_read: 4096,
};
let b = a;
assert_eq!(a, b);
}
#[test]
fn encoded_read_result_debug() {
let r = EncodedReadResult {
offset: 0,
unencoded_file_len: 4096,
unencoded_len: 8192,
unencoded_offset: 0,
compression: 3,
encryption: 0,
bytes_read: 1024,
};
let s = format!("{r:?}");
assert!(s.contains("compression: 3"), "debug: {s}");
assert!(s.contains("bytes_read: 1024"), "debug: {s}");
}
#[test]
fn subvolume_search_result_debug() {
let r = SubvolumeSearchResult { root_id: 256 };
let s = format!("{r:?}");
assert!(s.contains("256"), "debug: {s}");
}
}