use crate::raw::{
BTRFS_DEFRAG_RANGE_COMPRESS, BTRFS_DEFRAG_RANGE_COMPRESS_LEVEL,
BTRFS_DEFRAG_RANGE_NOCOMPRESS, BTRFS_DEFRAG_RANGE_START_IO,
btrfs_ioc_defrag_range, btrfs_ioctl_defrag_range_args,
};
use std::{
mem,
os::{fd::AsRawFd, unix::io::BorrowedFd},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompressType {
Zlib = 1,
Lzo = 2,
Zstd = 3,
}
impl std::fmt::Display for CompressType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Zlib => f.write_str("zlib"),
Self::Lzo => f.write_str("lzo"),
Self::Zstd => f.write_str("zstd"),
}
}
}
impl std::str::FromStr for CompressType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"zlib" => Ok(Self::Zlib),
"lzo" => Ok(Self::Lzo),
"zstd" => Ok(Self::Zstd),
_ => Err(format!(
"unknown compress type '{s}'; expected zlib, lzo, or zstd"
)),
}
}
}
#[derive(Debug, Clone)]
pub struct DefragRangeArgs {
pub start: u64,
pub len: u64,
pub flush: bool,
pub extent_thresh: u32,
pub compress: Option<CompressSpec>,
pub nocomp: bool,
}
#[derive(Debug, Clone, Copy)]
pub struct CompressSpec {
pub compress_type: CompressType,
pub level: Option<i8>,
}
impl DefragRangeArgs {
#[must_use]
pub fn new() -> Self {
Self {
start: 0,
len: u64::MAX,
flush: false,
extent_thresh: 0,
compress: None,
nocomp: false,
}
}
#[must_use]
pub fn start(mut self, start: u64) -> Self {
self.start = start;
self
}
#[must_use]
pub fn len(mut self, len: u64) -> Self {
self.len = len;
self
}
#[must_use]
pub fn flush(mut self) -> Self {
self.flush = true;
self
}
#[must_use]
pub fn extent_thresh(mut self, thresh: u32) -> Self {
self.extent_thresh = thresh;
self
}
#[must_use]
pub fn compress(mut self, spec: CompressSpec) -> Self {
self.compress = Some(spec);
self.nocomp = false;
self
}
#[must_use]
pub fn nocomp(mut self) -> Self {
self.nocomp = true;
self.compress = None;
self
}
}
impl Default for DefragRangeArgs {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compress_type_display() {
assert_eq!(format!("{}", CompressType::Zlib), "zlib");
assert_eq!(format!("{}", CompressType::Lzo), "lzo");
assert_eq!(format!("{}", CompressType::Zstd), "zstd");
}
#[test]
fn compress_type_from_str() {
assert_eq!("zlib".parse::<CompressType>().unwrap(), CompressType::Zlib);
assert_eq!("lzo".parse::<CompressType>().unwrap(), CompressType::Lzo);
assert_eq!("zstd".parse::<CompressType>().unwrap(), CompressType::Zstd);
}
#[test]
fn compress_type_from_str_case_insensitive() {
assert_eq!("ZLIB".parse::<CompressType>().unwrap(), CompressType::Zlib);
assert_eq!("Zstd".parse::<CompressType>().unwrap(), CompressType::Zstd);
}
#[test]
fn compress_type_from_str_invalid() {
assert!("lz4".parse::<CompressType>().is_err());
assert!("".parse::<CompressType>().is_err());
}
#[test]
fn defrag_args_defaults() {
let args = DefragRangeArgs::new();
assert_eq!(args.start, 0);
assert_eq!(args.len, u64::MAX);
assert!(!args.flush);
assert_eq!(args.extent_thresh, 0);
assert!(args.compress.is_none());
assert!(!args.nocomp);
}
#[test]
fn defrag_args_builder_chain() {
let args = DefragRangeArgs::new()
.start(4096)
.len(1024 * 1024)
.flush()
.extent_thresh(256 * 1024);
assert_eq!(args.start, 4096);
assert_eq!(args.len, 1024 * 1024);
assert!(args.flush);
assert_eq!(args.extent_thresh, 256 * 1024);
}
#[test]
fn defrag_args_compress_clears_nocomp() {
let args = DefragRangeArgs::new().nocomp().compress(CompressSpec {
compress_type: CompressType::Zstd,
level: None,
});
assert!(args.compress.is_some());
assert!(!args.nocomp);
}
#[test]
fn defrag_args_nocomp_clears_compress() {
let args = DefragRangeArgs::new()
.compress(CompressSpec {
compress_type: CompressType::Zlib,
level: Some(3),
})
.nocomp();
assert!(args.compress.is_none());
assert!(args.nocomp);
}
#[test]
fn defrag_args_default_trait() {
let a = DefragRangeArgs::default();
let b = DefragRangeArgs::new();
assert_eq!(a.start, b.start);
assert_eq!(a.len, b.len);
}
}
pub fn defrag_range(fd: BorrowedFd, args: &DefragRangeArgs) -> nix::Result<()> {
let mut raw: btrfs_ioctl_defrag_range_args = unsafe { mem::zeroed() };
raw.start = args.start;
raw.len = args.len;
raw.extent_thresh = args.extent_thresh;
if args.flush {
raw.flags |= u64::from(BTRFS_DEFRAG_RANGE_START_IO);
}
if args.nocomp {
raw.flags |= u64::from(BTRFS_DEFRAG_RANGE_NOCOMPRESS);
} else if let Some(spec) = args.compress {
raw.flags |= u64::from(BTRFS_DEFRAG_RANGE_COMPRESS);
match spec.level {
None => {
raw.__bindgen_anon_1.compress_type = spec.compress_type as u32;
}
Some(level) => {
raw.flags |= u64::from(BTRFS_DEFRAG_RANGE_COMPRESS_LEVEL);
raw.__bindgen_anon_1.compress.type_ = spec.compress_type as u8;
raw.__bindgen_anon_1.compress.level = level;
}
}
}
unsafe { btrfs_ioc_defrag_range(fd.as_raw_fd(), &raw const raw) }?;
Ok(())
}