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}