use crate::raw::{
BTRFS_FS_INFO_FLAG_GENERATION, BTRFS_LABEL_SIZE, btrfs_ioc_fs_info,
btrfs_ioc_get_fslabel, btrfs_ioc_resize, btrfs_ioc_set_fslabel,
btrfs_ioc_start_sync, btrfs_ioc_sync, btrfs_ioc_wait_sync,
btrfs_ioctl_fs_info_args, btrfs_ioctl_vol_args,
};
use nix::libc::c_char;
use std::{
ffi::{CStr, CString},
mem,
os::{fd::AsRawFd, unix::io::BorrowedFd},
};
use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct FilesystemInfo {
pub uuid: Uuid,
pub num_devices: u64,
pub max_id: u64,
pub nodesize: u32,
pub sectorsize: u32,
pub generation: u64,
}
pub fn filesystem_info(fd: BorrowedFd) -> nix::Result<FilesystemInfo> {
let mut raw: btrfs_ioctl_fs_info_args = unsafe { mem::zeroed() };
raw.flags = u64::from(BTRFS_FS_INFO_FLAG_GENERATION);
unsafe { btrfs_ioc_fs_info(fd.as_raw_fd(), &raw mut raw) }?;
Ok(FilesystemInfo {
uuid: Uuid::from_bytes(raw.fsid),
num_devices: raw.num_devices,
max_id: raw.max_id,
nodesize: raw.nodesize,
sectorsize: raw.sectorsize,
generation: raw.generation,
})
}
pub fn sync(fd: BorrowedFd) -> nix::Result<()> {
unsafe { btrfs_ioc_sync(fd.as_raw_fd()) }?;
Ok(())
}
pub fn start_sync(fd: BorrowedFd) -> nix::Result<u64> {
let mut transid: u64 = 0;
unsafe { btrfs_ioc_start_sync(fd.as_raw_fd(), &raw mut transid) }?;
Ok(transid)
}
pub fn wait_sync(fd: BorrowedFd, transid: u64) -> nix::Result<()> {
unsafe { btrfs_ioc_wait_sync(fd.as_raw_fd(), &raw const transid) }?;
Ok(())
}
pub fn label_get(fd: BorrowedFd) -> nix::Result<CString> {
let mut buf = [0i8; BTRFS_LABEL_SIZE as usize];
unsafe { btrfs_ioc_get_fslabel(fd.as_raw_fd(), &raw mut buf) }?;
let cstr = unsafe { CStr::from_ptr(buf.as_ptr()) };
Ok(cstr.to_owned())
}
#[allow(clippy::cast_possible_wrap)] pub fn label_set(fd: BorrowedFd, label: &CStr) -> nix::Result<()> {
let bytes = label.to_bytes();
if bytes.len() >= BTRFS_LABEL_SIZE as usize {
return Err(nix::errno::Errno::EINVAL);
}
let mut buf = [0i8; BTRFS_LABEL_SIZE as usize];
for (i, &b) in bytes.iter().enumerate() {
buf[i] = b as c_char;
}
unsafe { btrfs_ioc_set_fslabel(fd.as_raw_fd(), &raw const buf) }?;
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResizeAmount {
Cancel,
Max,
Set(u64),
Add(u64),
Sub(u64),
}
impl std::fmt::Display for ResizeAmount {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Cancel => f.write_str("cancel"),
Self::Max => f.write_str("max"),
Self::Set(n) => write!(f, "{n}"),
Self::Add(n) => write!(f, "+{n}"),
Self::Sub(n) => write!(f, "-{n}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResizeArgs {
pub devid: Option<u64>,
pub amount: ResizeAmount,
}
impl ResizeArgs {
#[must_use]
pub fn new(amount: ResizeAmount) -> Self {
Self {
devid: None,
amount,
}
}
#[must_use]
pub fn with_devid(mut self, devid: u64) -> Self {
self.devid = Some(devid);
self
}
fn format_name(&self) -> String {
let amount = self.amount.to_string();
match self.devid {
Some(devid) => format!("{devid}:{amount}"),
None => amount,
}
}
}
#[allow(clippy::cast_possible_wrap)] pub fn resize(fd: BorrowedFd, args: ResizeArgs) -> nix::Result<()> {
let name = args.format_name();
let name_bytes = name.as_bytes();
if name_bytes.len() >= 4088 {
return Err(nix::errno::Errno::EINVAL);
}
let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
for (i, &b) in name_bytes.iter().enumerate() {
raw.name[i] = b as c_char;
}
unsafe { btrfs_ioc_resize(fd.as_raw_fd(), &raw const raw) }?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resize_amount_cancel() {
assert_eq!(ResizeAmount::Cancel.to_string(), "cancel");
}
#[test]
fn resize_amount_max() {
assert_eq!(ResizeAmount::Max.to_string(), "max");
}
#[test]
fn resize_amount_set() {
assert_eq!(ResizeAmount::Set(1073741824).to_string(), "1073741824");
}
#[test]
fn resize_amount_add() {
assert_eq!(ResizeAmount::Add(512000000).to_string(), "+512000000");
}
#[test]
fn resize_amount_sub() {
assert_eq!(ResizeAmount::Sub(256000000).to_string(), "-256000000");
}
#[test]
fn resize_args_no_devid() {
let args = ResizeArgs::new(ResizeAmount::Max);
assert!(args.devid.is_none());
assert_eq!(args.format_name(), "max");
}
#[test]
fn resize_args_with_devid() {
let args = ResizeArgs::new(ResizeAmount::Add(1024)).with_devid(2);
assert_eq!(args.devid, Some(2));
assert_eq!(args.format_name(), "2:+1024");
}
#[test]
fn resize_args_set_with_devid() {
let args = ResizeArgs::new(ResizeAmount::Set(999)).with_devid(1);
assert_eq!(args.format_name(), "1:999");
}
}
pub fn is_mounted(device: &std::path::Path) -> std::io::Result<bool> {
let canonical = std::fs::canonicalize(device)?;
let contents = std::fs::read_to_string("/proc/mounts")?;
Ok(contents.lines().any(|line| {
line.split_whitespace()
.next()
.and_then(|src| std::fs::canonicalize(src).ok())
.is_some_and(|src_canon| src_canon == canonical)
}))
}