1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum CompressType {
22 Zlib = 1,
23 Lzo = 2,
24 Zstd = 3,
25}
26
27impl std::fmt::Display for CompressType {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 match self {
30 Self::Zlib => f.write_str("zlib"),
31 Self::Lzo => f.write_str("lzo"),
32 Self::Zstd => f.write_str("zstd"),
33 }
34 }
35}
36
37impl std::str::FromStr for CompressType {
38 type Err = String;
39
40 fn from_str(s: &str) -> Result<Self, Self::Err> {
41 match s.to_ascii_lowercase().as_str() {
42 "zlib" => Ok(Self::Zlib),
43 "lzo" => Ok(Self::Lzo),
44 "zstd" => Ok(Self::Zstd),
45 _ => Err(format!(
46 "unknown compress type '{s}'; expected zlib, lzo, or zstd"
47 )),
48 }
49 }
50}
51
52#[derive(Debug, Clone)]
57pub struct DefragRangeArgs {
58 pub start: u64,
60 pub len: u64,
62 pub flush: bool,
64 pub extent_thresh: u32,
68 pub compress: Option<CompressSpec>,
71 pub nocomp: bool,
74}
75
76#[derive(Debug, Clone, Copy)]
78pub struct CompressSpec {
79 pub compress_type: CompressType,
81 pub level: Option<i8>,
86}
87
88impl DefragRangeArgs {
89 pub fn new() -> Self {
92 Self {
93 start: 0,
94 len: u64::MAX,
95 flush: false,
96 extent_thresh: 0,
97 compress: None,
98 nocomp: false,
99 }
100 }
101
102 pub fn start(mut self, start: u64) -> Self {
104 self.start = start;
105 self
106 }
107
108 pub fn len(mut self, len: u64) -> Self {
110 self.len = len;
111 self
112 }
113
114 pub fn flush(mut self) -> Self {
116 self.flush = true;
117 self
118 }
119
120 pub fn extent_thresh(mut self, thresh: u32) -> Self {
123 self.extent_thresh = thresh;
124 self
125 }
126
127 pub fn compress(mut self, spec: CompressSpec) -> Self {
129 self.compress = Some(spec);
130 self.nocomp = false;
131 self
132 }
133
134 pub fn nocomp(mut self) -> Self {
136 self.nocomp = true;
137 self.compress = None;
138 self
139 }
140}
141
142impl Default for DefragRangeArgs {
143 fn default() -> Self {
144 Self::new()
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
155 fn compress_type_display() {
156 assert_eq!(format!("{}", CompressType::Zlib), "zlib");
157 assert_eq!(format!("{}", CompressType::Lzo), "lzo");
158 assert_eq!(format!("{}", CompressType::Zstd), "zstd");
159 }
160
161 #[test]
164 fn compress_type_from_str() {
165 assert_eq!("zlib".parse::<CompressType>().unwrap(), CompressType::Zlib);
166 assert_eq!("lzo".parse::<CompressType>().unwrap(), CompressType::Lzo);
167 assert_eq!("zstd".parse::<CompressType>().unwrap(), CompressType::Zstd);
168 }
169
170 #[test]
171 fn compress_type_from_str_case_insensitive() {
172 assert_eq!("ZLIB".parse::<CompressType>().unwrap(), CompressType::Zlib);
173 assert_eq!("Zstd".parse::<CompressType>().unwrap(), CompressType::Zstd);
174 }
175
176 #[test]
177 fn compress_type_from_str_invalid() {
178 assert!("lz4".parse::<CompressType>().is_err());
179 assert!("".parse::<CompressType>().is_err());
180 }
181
182 #[test]
185 fn defrag_args_defaults() {
186 let args = DefragRangeArgs::new();
187 assert_eq!(args.start, 0);
188 assert_eq!(args.len, u64::MAX);
189 assert!(!args.flush);
190 assert_eq!(args.extent_thresh, 0);
191 assert!(args.compress.is_none());
192 assert!(!args.nocomp);
193 }
194
195 #[test]
196 fn defrag_args_builder_chain() {
197 let args = DefragRangeArgs::new()
198 .start(4096)
199 .len(1024 * 1024)
200 .flush()
201 .extent_thresh(256 * 1024);
202 assert_eq!(args.start, 4096);
203 assert_eq!(args.len, 1024 * 1024);
204 assert!(args.flush);
205 assert_eq!(args.extent_thresh, 256 * 1024);
206 }
207
208 #[test]
209 fn defrag_args_compress_clears_nocomp() {
210 let args = DefragRangeArgs::new().nocomp().compress(CompressSpec {
211 compress_type: CompressType::Zstd,
212 level: None,
213 });
214 assert!(args.compress.is_some());
215 assert!(!args.nocomp);
216 }
217
218 #[test]
219 fn defrag_args_nocomp_clears_compress() {
220 let args = DefragRangeArgs::new()
221 .compress(CompressSpec {
222 compress_type: CompressType::Zlib,
223 level: Some(3),
224 })
225 .nocomp();
226 assert!(args.compress.is_none());
227 assert!(args.nocomp);
228 }
229
230 #[test]
231 fn defrag_args_default_trait() {
232 let a = DefragRangeArgs::default();
233 let b = DefragRangeArgs::new();
234 assert_eq!(a.start, b.start);
235 assert_eq!(a.len, b.len);
236 }
237}
238
239pub fn defrag_range(fd: BorrowedFd, args: &DefragRangeArgs) -> nix::Result<()> {
245 let mut raw: btrfs_ioctl_defrag_range_args = unsafe { mem::zeroed() };
246
247 raw.start = args.start;
248 raw.len = args.len;
249 raw.extent_thresh = args.extent_thresh;
250
251 if args.flush {
252 raw.flags |= BTRFS_DEFRAG_RANGE_START_IO as u64;
253 }
254
255 if args.nocomp {
256 raw.flags |= BTRFS_DEFRAG_RANGE_NOCOMPRESS as u64;
257 } else if let Some(spec) = args.compress {
258 raw.flags |= BTRFS_DEFRAG_RANGE_COMPRESS as u64;
259 match spec.level {
260 None => {
261 raw.__bindgen_anon_1.compress_type = spec.compress_type as u32;
262 }
263 Some(level) => {
264 raw.flags |= BTRFS_DEFRAG_RANGE_COMPRESS_LEVEL as u64;
265 raw.__bindgen_anon_1.compress.type_ = spec.compress_type as u8;
266 raw.__bindgen_anon_1.compress.level = level;
267 }
268 }
269 }
270
271 unsafe { btrfs_ioc_defrag_range(fd.as_raw_fd(), &mut raw) }?;
272 Ok(())
273}