Skip to main content

btrfs_uapi/
defrag.rs

1//! # File defragmentation: rewriting fragmented extents into contiguous runs
2//!
3//! Defragmenting a file rewrites its extents contiguously on disk, which can
4//! improve sequential read performance.  Optionally applies or removes
5//! transparent compression at the same time.
6
7use crate::raw::{
8    BTRFS_DEFRAG_RANGE_COMPRESS, BTRFS_DEFRAG_RANGE_COMPRESS_LEVEL, BTRFS_DEFRAG_RANGE_NOCOMPRESS,
9    BTRFS_DEFRAG_RANGE_START_IO, btrfs_ioc_defrag_range, btrfs_ioctl_defrag_range_args,
10};
11use std::{
12    mem,
13    os::{fd::AsRawFd, unix::io::BorrowedFd},
14};
15
16/// Compression algorithm to use when defragmenting.
17///
18/// Corresponds to the `BTRFS_COMPRESS_*` values from `compression.h`.
19/// The numeric values are part of the on-disk/ioctl ABI.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum CompressType {
22    Zlib = 1,
23    Lzo = 2,
24    Zstd = 3,
25}
26
27impl CompressType {
28    /// Parse a compress type from a string name, as accepted by the
29    /// `btrfs filesystem defrag -c` option.
30    pub fn from_str(s: &str) -> Option<Self> {
31        match s.to_ascii_lowercase().as_str() {
32            "zlib" => Some(Self::Zlib),
33            "lzo" => Some(Self::Lzo),
34            "zstd" => Some(Self::Zstd),
35            _ => None,
36        }
37    }
38}
39
40/// Arguments for a defragmentation operation.
41///
42/// Construct with [`DefragRangeArgs::new`] and use the builder methods to set
43/// options. All options are optional; the defaults match the kernel's defaults.
44#[derive(Debug, Clone)]
45pub struct DefragRangeArgs {
46    /// Start offset in bytes. Defaults to `0`.
47    pub start: u64,
48    /// Number of bytes to defragment. Defaults to `u64::MAX` (the entire file).
49    pub len: u64,
50    /// Flush dirty pages to disk immediately after defragmenting.
51    pub flush: bool,
52    /// Extents larger than this threshold are considered already defragmented
53    /// and will not be rewritten. `0` uses the kernel default (32 MiB as of
54    /// recent kernels). `1` forces every extent to be rewritten.
55    pub extent_thresh: u32,
56    /// Compress the file while defragmenting. `None` leaves the file's
57    /// existing compression attribute unchanged.
58    pub compress: Option<CompressSpec>,
59    /// Explicitly disable compression during defragmentation (uncompress if
60    /// necessary). Mutually exclusive with `compress`.
61    pub nocomp: bool,
62}
63
64/// Compression specification for [`DefragRangeArgs`].
65#[derive(Debug, Clone, Copy)]
66pub struct CompressSpec {
67    /// Compression algorithm to use.
68    pub compress_type: CompressType,
69    /// Optional compression level. When `None`, the kernel default for the
70    /// chosen algorithm is used. When `Some`, the
71    /// `BTRFS_DEFRAG_RANGE_COMPRESS_LEVEL` flag is set and the level is
72    /// passed via the `compress.level` union member.
73    pub level: Option<i8>,
74}
75
76impl DefragRangeArgs {
77    /// Create a new `DefragRangeArgs` with all defaults: defragment the
78    /// entire file, no compression change, no flush.
79    pub fn new() -> Self {
80        Self {
81            start: 0,
82            len: u64::MAX,
83            flush: false,
84            extent_thresh: 0,
85            compress: None,
86            nocomp: false,
87        }
88    }
89
90    /// Set the start offset in bytes.
91    pub fn start(mut self, start: u64) -> Self {
92        self.start = start;
93        self
94    }
95
96    /// Set the number of bytes to defragment.
97    pub fn len(mut self, len: u64) -> Self {
98        self.len = len;
99        self
100    }
101
102    /// Flush dirty data to disk after defragmenting.
103    pub fn flush(mut self) -> Self {
104        self.flush = true;
105        self
106    }
107
108    /// Set the extent size threshold. Extents larger than this will not be
109    /// rewritten.
110    pub fn extent_thresh(mut self, thresh: u32) -> Self {
111        self.extent_thresh = thresh;
112        self
113    }
114
115    /// Compress the file using the given algorithm while defragmenting.
116    pub fn compress(mut self, spec: CompressSpec) -> Self {
117        self.compress = Some(spec);
118        self.nocomp = false;
119        self
120    }
121
122    /// Disable compression while defragmenting (decompresses existing data).
123    pub fn nocomp(mut self) -> Self {
124        self.nocomp = true;
125        self.compress = None;
126        self
127    }
128}
129
130impl Default for DefragRangeArgs {
131    fn default() -> Self {
132        Self::new()
133    }
134}
135
136/// Defragment a byte range of the file referred to by `fd`.
137///
138/// `fd` must be an open file descriptor to a regular file on a btrfs
139/// filesystem. Pass `&DefragRangeArgs::new()` to defragment the entire file
140/// with default settings.
141pub fn defrag_range(fd: BorrowedFd, args: &DefragRangeArgs) -> nix::Result<()> {
142    let mut raw: btrfs_ioctl_defrag_range_args = unsafe { mem::zeroed() };
143
144    raw.start = args.start;
145    raw.len = args.len;
146    raw.extent_thresh = args.extent_thresh;
147
148    if args.flush {
149        raw.flags |= BTRFS_DEFRAG_RANGE_START_IO as u64;
150    }
151
152    if args.nocomp {
153        raw.flags |= BTRFS_DEFRAG_RANGE_NOCOMPRESS as u64;
154    } else if let Some(spec) = args.compress {
155        raw.flags |= BTRFS_DEFRAG_RANGE_COMPRESS as u64;
156        match spec.level {
157            None => {
158                raw.__bindgen_anon_1.compress_type = spec.compress_type as u32;
159            }
160            Some(level) => {
161                raw.flags |= BTRFS_DEFRAG_RANGE_COMPRESS_LEVEL as u64;
162                raw.__bindgen_anon_1.compress.type_ = spec.compress_type as u8;
163                raw.__bindgen_anon_1.compress.level = level;
164            }
165        }
166    }
167
168    unsafe { btrfs_ioc_defrag_range(fd.as_raw_fd(), &mut raw) }?;
169    Ok(())
170}