1use crate::{
12 field_size,
13 filesystem::FilesystemInfo,
14 raw::{
15 BTRFS_DEV_EXTENT_KEY, BTRFS_DEV_STATS_RESET, BTRFS_DEV_TREE_OBJECTID,
16 BTRFS_DEVICE_SPEC_BY_ID, btrfs_dev_extent,
17 btrfs_dev_stat_values_BTRFS_DEV_STAT_CORRUPTION_ERRS,
18 btrfs_dev_stat_values_BTRFS_DEV_STAT_FLUSH_ERRS,
19 btrfs_dev_stat_values_BTRFS_DEV_STAT_GENERATION_ERRS,
20 btrfs_dev_stat_values_BTRFS_DEV_STAT_READ_ERRS,
21 btrfs_dev_stat_values_BTRFS_DEV_STAT_VALUES_MAX,
22 btrfs_dev_stat_values_BTRFS_DEV_STAT_WRITE_ERRS, btrfs_ioc_add_dev,
23 btrfs_ioc_dev_info, btrfs_ioc_devices_ready, btrfs_ioc_forget_dev,
24 btrfs_ioc_get_dev_stats, btrfs_ioc_rm_dev, btrfs_ioc_rm_dev_v2,
25 btrfs_ioc_scan_dev, btrfs_ioctl_dev_info_args,
26 btrfs_ioctl_get_dev_stats, btrfs_ioctl_vol_args,
27 btrfs_ioctl_vol_args_v2,
28 },
29 tree_search::{SearchKey, tree_search},
30};
31use nix::{errno::Errno, libc::c_char};
32use std::{
33 ffi::CStr,
34 fs::OpenOptions,
35 mem,
36 os::{fd::AsRawFd, unix::io::BorrowedFd},
37};
38use uuid::Uuid;
39
40#[derive(Debug, Clone)]
43pub struct DeviceInfo {
44 pub devid: u64,
46 pub uuid: Uuid,
48 pub bytes_used: u64,
50 pub total_bytes: u64,
52 pub path: String,
54}
55
56#[derive(Debug, Clone)]
58pub enum DeviceSpec<'a> {
59 Path(&'a CStr),
62 Id(u64),
64}
65
66#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
68pub struct DeviceStats {
69 pub devid: u64,
71 pub write_errs: u64,
73 pub read_errs: u64,
75 pub flush_errs: u64,
77 pub corruption_errs: u64,
79 pub generation_errs: u64,
81}
82
83impl DeviceStats {
84 pub fn total_errs(&self) -> u64 {
86 self.write_errs
87 + self.read_errs
88 + self.flush_errs
89 + self.corruption_errs
90 + self.generation_errs
91 }
92
93 pub fn is_clean(&self) -> bool {
95 self.total_errs() == 0
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn dev_stats_default_is_clean() {
105 let stats = DeviceStats::default();
106 assert!(stats.is_clean());
107 assert_eq!(stats.total_errs(), 0);
108 }
109
110 #[test]
111 fn dev_stats_total_errs() {
112 let stats = DeviceStats {
113 devid: 1,
114 write_errs: 1,
115 read_errs: 2,
116 flush_errs: 3,
117 corruption_errs: 4,
118 generation_errs: 5,
119 };
120 assert_eq!(stats.total_errs(), 15);
121 assert!(!stats.is_clean());
122 }
123
124 #[test]
125 fn dev_stats_single_error_not_clean() {
126 let stats = DeviceStats {
127 corruption_errs: 1,
128 ..DeviceStats::default()
129 };
130 assert!(!stats.is_clean());
131 assert_eq!(stats.total_errs(), 1);
132 }
133}
134
135fn copy_path_to_name(name: &mut [c_char], path: &CStr) -> nix::Result<()> {
139 let bytes = path.to_bytes(); if bytes.len() >= name.len() {
141 return Err(Errno::ENAMETOOLONG);
142 }
143 for (i, &b) in bytes.iter().enumerate() {
144 name[i] = b as c_char;
145 }
146 Ok(())
148}
149
150fn open_control() -> nix::Result<std::fs::File> {
153 OpenOptions::new()
154 .read(true)
155 .write(true)
156 .open("/dev/btrfs-control")
157 .map_err(|e| {
158 Errno::from_raw(e.raw_os_error().unwrap_or(nix::libc::ENODEV))
159 })
160}
161
162pub fn device_info(
167 fd: BorrowedFd,
168 devid: u64,
169) -> nix::Result<Option<DeviceInfo>> {
170 let mut raw: btrfs_ioctl_dev_info_args = unsafe { mem::zeroed() };
171 raw.devid = devid;
172
173 match unsafe { btrfs_ioc_dev_info(fd.as_raw_fd(), &mut raw) } {
174 Err(Errno::ENODEV) => return Ok(None),
175 Err(e) => return Err(e),
176 Ok(_) => {}
177 }
178
179 let path = unsafe { CStr::from_ptr(raw.path.as_ptr() as *const _) }
180 .to_string_lossy()
181 .into_owned();
182
183 Ok(Some(DeviceInfo {
184 devid: raw.devid,
185 uuid: Uuid::from_bytes(raw.uuid),
186 bytes_used: raw.bytes_used,
187 total_bytes: raw.total_bytes,
188 path,
189 }))
190}
191
192pub fn device_info_all(
198 fd: BorrowedFd,
199 fs_info: &FilesystemInfo,
200) -> nix::Result<Vec<DeviceInfo>> {
201 let mut devices = Vec::with_capacity(fs_info.num_devices as usize);
202 for devid in 1..=fs_info.max_id {
203 if let Some(info) = device_info(fd, devid)? {
204 devices.push(info);
205 }
206 }
207 Ok(devices)
208}
209
210pub fn device_add(fd: BorrowedFd, path: &CStr) -> nix::Result<()> {
215 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
216 copy_path_to_name(&mut raw.name, path)?;
217 unsafe { btrfs_ioc_add_dev(fd.as_raw_fd(), &raw) }?;
218 Ok(())
219}
220
221pub fn device_remove(fd: BorrowedFd, spec: DeviceSpec) -> nix::Result<()> {
228 let mut args: btrfs_ioctl_vol_args_v2 = unsafe { mem::zeroed() };
229
230 match spec {
231 DeviceSpec::Id(devid) => {
232 args.flags = BTRFS_DEVICE_SPEC_BY_ID as u64;
233 args.__bindgen_anon_2.devid = devid;
235 unsafe { btrfs_ioc_rm_dev_v2(fd.as_raw_fd(), &args) }?;
236 }
237 DeviceSpec::Path(path) => {
238 unsafe {
240 copy_path_to_name(&mut args.__bindgen_anon_2.name, path)
241 }?;
242 match unsafe { btrfs_ioc_rm_dev_v2(fd.as_raw_fd(), &args) } {
243 Ok(_) => {}
244 Err(Errno::ENOTTY) | Err(Errno::EOPNOTSUPP) => {
247 let mut old: btrfs_ioctl_vol_args =
248 unsafe { mem::zeroed() };
249 copy_path_to_name(&mut old.name, path)?;
250 unsafe { btrfs_ioc_rm_dev(fd.as_raw_fd(), &old) }?;
251 }
252 Err(e) => return Err(e),
253 }
254 }
255 }
256
257 Ok(())
258}
259
260pub fn device_scan(path: &CStr) -> nix::Result<()> {
266 let ctl = open_control()?;
267 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
268 copy_path_to_name(&mut raw.name, path)?;
269 unsafe { btrfs_ioc_scan_dev(ctl.as_raw_fd(), &raw) }?;
270 Ok(())
271}
272
273pub fn device_forget(path: Option<&CStr>) -> nix::Result<()> {
281 let ctl = open_control()?;
282 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
283 if let Some(p) = path {
284 copy_path_to_name(&mut raw.name, p)?;
285 }
286 unsafe { btrfs_ioc_forget_dev(ctl.as_raw_fd(), &raw) }?;
287 Ok(())
288}
289
290pub fn device_ready(path: &CStr) -> nix::Result<()> {
298 let ctl = open_control()?;
299 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
302 copy_path_to_name(&mut raw.name, path)?;
303 unsafe { btrfs_ioc_devices_ready(ctl.as_raw_fd(), &mut raw) }?;
304 Ok(())
305}
306
307pub fn device_stats(
313 fd: BorrowedFd,
314 devid: u64,
315 reset: bool,
316) -> nix::Result<DeviceStats> {
317 let mut raw: btrfs_ioctl_get_dev_stats = unsafe { mem::zeroed() };
318 raw.devid = devid;
319 raw.nr_items = btrfs_dev_stat_values_BTRFS_DEV_STAT_VALUES_MAX as u64;
320 if reset {
321 raw.flags = BTRFS_DEV_STATS_RESET as u64;
322 }
323
324 unsafe { btrfs_ioc_get_dev_stats(fd.as_raw_fd(), &mut raw) }?;
325
326 Ok(DeviceStats {
327 devid,
328 write_errs: raw.values
329 [btrfs_dev_stat_values_BTRFS_DEV_STAT_WRITE_ERRS as usize],
330 read_errs: raw.values
331 [btrfs_dev_stat_values_BTRFS_DEV_STAT_READ_ERRS as usize],
332 flush_errs: raw.values
333 [btrfs_dev_stat_values_BTRFS_DEV_STAT_FLUSH_ERRS as usize],
334 corruption_errs: raw.values
335 [btrfs_dev_stat_values_BTRFS_DEV_STAT_CORRUPTION_ERRS as usize],
336 generation_errs: raw.values
337 [btrfs_dev_stat_values_BTRFS_DEV_STAT_GENERATION_ERRS as usize],
338 })
339}
340
341const DEV_EXTENT_LENGTH_OFF: usize =
342 std::mem::offset_of!(btrfs_dev_extent, length);
343
344const SZ_1M: u64 = 1024 * 1024;
345const SZ_32M: u64 = 32 * 1024 * 1024;
346
347const BTRFS_SUPER_MIRROR_MAX: usize = 3;
349
350fn sb_offset(i: usize) -> u64 {
354 match i {
355 0 => 64 * 1024,
356 _ => 1u64 << (20 + 10 * (i as u64)),
357 }
358}
359
360#[derive(Debug, Clone, Copy)]
362struct Extent {
363 start: u64,
364 end: u64,
366}
367
368pub fn device_min_size(fd: BorrowedFd, devid: u64) -> nix::Result<u64> {
378 let mut min_size: u64 = SZ_1M;
379 let mut extents: Vec<Extent> = Vec::new();
380 let mut holes: Vec<Extent> = Vec::new();
381 let mut last_pos: Option<u64> = None;
382
383 tree_search(
384 fd,
385 SearchKey::for_objectid_range(
386 BTRFS_DEV_TREE_OBJECTID as u64,
387 BTRFS_DEV_EXTENT_KEY,
388 devid,
389 devid,
390 ),
391 |hdr, data| {
392 if data.len()
393 < DEV_EXTENT_LENGTH_OFF + field_size!(btrfs_dev_extent, length)
394 {
395 return Ok(());
396 }
397 let phys_start = hdr.offset;
398 let len = read_le_u64(data, DEV_EXTENT_LENGTH_OFF);
399
400 min_size += len;
401
402 extents.push(Extent {
405 start: phys_start,
406 end: phys_start + len - 1,
407 });
408
409 if let Some(prev_end) = last_pos {
410 if prev_end != phys_start {
411 holes.push(Extent {
412 start: prev_end,
413 end: phys_start - 1,
414 });
415 }
416 }
417
418 last_pos = Some(phys_start + len);
419 Ok(())
420 },
421 )?;
422
423 extents.sort_by(|a, b| b.end.cmp(&a.end));
425
426 adjust_min_size(&mut extents, &mut holes, &mut min_size);
427
428 Ok(min_size)
429}
430
431fn hole_includes_sb_mirror(start: u64, end: u64) -> bool {
433 (0..BTRFS_SUPER_MIRROR_MAX).any(|i| {
434 let bytenr = sb_offset(i);
435 bytenr >= start && bytenr <= end
436 })
437}
438
439fn adjust_min_size(
449 extents: &mut Vec<Extent>,
450 holes: &mut Vec<Extent>,
451 min_size: &mut u64,
452) {
453 let mut scratch_space: u64 = 0;
454
455 while let Some(&ext) = extents.first() {
456 if ext.end < *min_size {
457 break;
458 }
459
460 let extent_len = ext.end - ext.start + 1;
461
462 let hole_idx = holes.iter().position(|h| {
464 let hole_len = h.end - h.start + 1;
465 hole_len >= extent_len
466 });
467
468 let Some(idx) = hole_idx else {
469 *min_size = ext.end + 1;
470 break;
471 };
472
473 if hole_includes_sb_mirror(
476 holes[idx].start,
477 holes[idx].start + extent_len - 1,
478 ) {
479 *min_size += extent_len;
480 }
481
482 let hole_len = holes[idx].end - holes[idx].start + 1;
484 if hole_len > extent_len {
485 holes[idx].start += extent_len;
486 } else {
487 holes.remove(idx);
488 }
489
490 extents.remove(0);
491
492 if extent_len > scratch_space {
493 scratch_space = extent_len;
494 }
495 }
496
497 if scratch_space > 0 {
498 *min_size += scratch_space;
499 *min_size += SZ_32M;
501 }
502}
503
504fn read_le_u64(buf: &[u8], off: usize) -> u64 {
505 u64::from_le_bytes(buf[off..off + 8].try_into().unwrap())
506}