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 util::read_le_u64,
31};
32use nix::{errno::Errno, libc::c_char};
33use std::{
34 ffi::CStr,
35 fs::OpenOptions,
36 mem,
37 os::{fd::AsRawFd, unix::io::BorrowedFd},
38};
39use uuid::Uuid;
40
41#[derive(Debug, Clone)]
44pub struct DeviceInfo {
45 pub devid: u64,
47 pub uuid: Uuid,
49 pub bytes_used: u64,
51 pub total_bytes: u64,
53 pub path: String,
55}
56
57#[derive(Debug, Clone)]
59pub enum DeviceSpec<'a> {
60 Path(&'a CStr),
63 Id(u64),
65}
66
67#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
69pub struct DeviceStats {
70 pub devid: u64,
72 pub write_errs: u64,
74 pub read_errs: u64,
76 pub flush_errs: u64,
78 pub corruption_errs: u64,
80 pub generation_errs: u64,
82}
83
84impl DeviceStats {
85 pub fn total_errs(&self) -> u64 {
87 self.write_errs
88 + self.read_errs
89 + self.flush_errs
90 + self.corruption_errs
91 + self.generation_errs
92 }
93
94 pub fn is_clean(&self) -> bool {
96 self.total_errs() == 0
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn dev_stats_default_is_clean() {
106 let stats = DeviceStats::default();
107 assert!(stats.is_clean());
108 assert_eq!(stats.total_errs(), 0);
109 }
110
111 #[test]
112 fn dev_stats_total_errs() {
113 let stats = DeviceStats {
114 devid: 1,
115 write_errs: 1,
116 read_errs: 2,
117 flush_errs: 3,
118 corruption_errs: 4,
119 generation_errs: 5,
120 };
121 assert_eq!(stats.total_errs(), 15);
122 assert!(!stats.is_clean());
123 }
124
125 #[test]
126 fn dev_stats_single_error_not_clean() {
127 let stats = DeviceStats {
128 corruption_errs: 1,
129 ..DeviceStats::default()
130 };
131 assert!(!stats.is_clean());
132 assert_eq!(stats.total_errs(), 1);
133 }
134}
135
136fn copy_path_to_name(name: &mut [c_char], path: &CStr) -> nix::Result<()> {
140 let bytes = path.to_bytes(); if bytes.len() >= name.len() {
142 return Err(Errno::ENAMETOOLONG);
143 }
144 for (i, &b) in bytes.iter().enumerate() {
145 name[i] = b as c_char;
146 }
147 Ok(())
149}
150
151fn open_control() -> nix::Result<std::fs::File> {
154 OpenOptions::new()
155 .read(true)
156 .write(true)
157 .open("/dev/btrfs-control")
158 .map_err(|e| {
159 Errno::from_raw(e.raw_os_error().unwrap_or(nix::libc::ENODEV))
160 })
161}
162
163pub fn device_info(
168 fd: BorrowedFd,
169 devid: u64,
170) -> nix::Result<Option<DeviceInfo>> {
171 let mut raw: btrfs_ioctl_dev_info_args = unsafe { mem::zeroed() };
172 raw.devid = devid;
173
174 match unsafe { btrfs_ioc_dev_info(fd.as_raw_fd(), &mut raw) } {
175 Err(Errno::ENODEV) => return Ok(None),
176 Err(e) => return Err(e),
177 Ok(_) => {}
178 }
179
180 let path = unsafe { CStr::from_ptr(raw.path.as_ptr() as *const _) }
181 .to_string_lossy()
182 .into_owned();
183
184 Ok(Some(DeviceInfo {
185 devid: raw.devid,
186 uuid: Uuid::from_bytes(raw.uuid),
187 bytes_used: raw.bytes_used,
188 total_bytes: raw.total_bytes,
189 path,
190 }))
191}
192
193pub fn device_info_all(
199 fd: BorrowedFd,
200 fs_info: &FilesystemInfo,
201) -> nix::Result<Vec<DeviceInfo>> {
202 let mut devices = Vec::with_capacity(fs_info.num_devices as usize);
203 for devid in 1..=fs_info.max_id {
204 if let Some(info) = device_info(fd, devid)? {
205 devices.push(info);
206 }
207 }
208 Ok(devices)
209}
210
211pub fn device_add(fd: BorrowedFd, path: &CStr) -> nix::Result<()> {
216 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
217 copy_path_to_name(&mut raw.name, path)?;
218 unsafe { btrfs_ioc_add_dev(fd.as_raw_fd(), &raw) }?;
219 Ok(())
220}
221
222pub fn device_remove(fd: BorrowedFd, spec: DeviceSpec) -> nix::Result<()> {
234 let mut args: btrfs_ioctl_vol_args_v2 = unsafe { mem::zeroed() };
235
236 match spec {
237 DeviceSpec::Id(devid) => {
238 args.flags = BTRFS_DEVICE_SPEC_BY_ID as u64;
239 args.__bindgen_anon_2.devid = devid;
241 unsafe { btrfs_ioc_rm_dev_v2(fd.as_raw_fd(), &args) }?;
242 }
243 DeviceSpec::Path(path) => {
244 unsafe {
246 copy_path_to_name(&mut args.__bindgen_anon_2.name, path)
247 }?;
248 match unsafe { btrfs_ioc_rm_dev_v2(fd.as_raw_fd(), &args) } {
249 Ok(_) => {}
250 Err(Errno::ENOTTY) | Err(Errno::EOPNOTSUPP) => {
253 let mut old: btrfs_ioctl_vol_args =
254 unsafe { mem::zeroed() };
255 copy_path_to_name(&mut old.name, path)?;
256 unsafe { btrfs_ioc_rm_dev(fd.as_raw_fd(), &old) }?;
257 }
258 Err(e) => return Err(e),
259 }
260 }
261 }
262
263 Ok(())
264}
265
266pub fn device_scan(path: &CStr) -> nix::Result<()> {
272 let ctl = open_control()?;
273 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
274 copy_path_to_name(&mut raw.name, path)?;
275 unsafe { btrfs_ioc_scan_dev(ctl.as_raw_fd(), &raw) }?;
276 Ok(())
277}
278
279pub fn device_forget(path: Option<&CStr>) -> nix::Result<()> {
287 let ctl = open_control()?;
288 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
289 if let Some(p) = path {
290 copy_path_to_name(&mut raw.name, p)?;
291 }
292 unsafe { btrfs_ioc_forget_dev(ctl.as_raw_fd(), &raw) }?;
293 Ok(())
294}
295
296pub fn device_ready(path: &CStr) -> nix::Result<()> {
304 let ctl = open_control()?;
305 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
308 copy_path_to_name(&mut raw.name, path)?;
309 unsafe { btrfs_ioc_devices_ready(ctl.as_raw_fd(), &mut raw) }?;
310 Ok(())
311}
312
313pub fn device_stats(
319 fd: BorrowedFd,
320 devid: u64,
321 reset: bool,
322) -> nix::Result<DeviceStats> {
323 let mut raw: btrfs_ioctl_get_dev_stats = unsafe { mem::zeroed() };
324 raw.devid = devid;
325 raw.nr_items = btrfs_dev_stat_values_BTRFS_DEV_STAT_VALUES_MAX as u64;
326 if reset {
327 raw.flags = BTRFS_DEV_STATS_RESET as u64;
328 }
329
330 unsafe { btrfs_ioc_get_dev_stats(fd.as_raw_fd(), &mut raw) }?;
331
332 Ok(DeviceStats {
333 devid,
334 write_errs: raw.values
335 [btrfs_dev_stat_values_BTRFS_DEV_STAT_WRITE_ERRS as usize],
336 read_errs: raw.values
337 [btrfs_dev_stat_values_BTRFS_DEV_STAT_READ_ERRS as usize],
338 flush_errs: raw.values
339 [btrfs_dev_stat_values_BTRFS_DEV_STAT_FLUSH_ERRS as usize],
340 corruption_errs: raw.values
341 [btrfs_dev_stat_values_BTRFS_DEV_STAT_CORRUPTION_ERRS as usize],
342 generation_errs: raw.values
343 [btrfs_dev_stat_values_BTRFS_DEV_STAT_GENERATION_ERRS as usize],
344 })
345}
346
347const DEV_EXTENT_LENGTH_OFF: usize =
348 std::mem::offset_of!(btrfs_dev_extent, length);
349
350const SZ_1M: u64 = 1024 * 1024;
351const SZ_32M: u64 = 32 * 1024 * 1024;
352
353const BTRFS_SUPER_MIRROR_MAX: usize = 3;
355
356fn sb_offset(i: usize) -> u64 {
360 match i {
361 0 => 64 * 1024,
362 _ => 1u64 << (20 + 10 * (i as u64)),
363 }
364}
365
366#[derive(Debug, Clone, Copy)]
368struct Extent {
369 start: u64,
370 end: u64,
372}
373
374pub fn device_min_size(fd: BorrowedFd, devid: u64) -> nix::Result<u64> {
384 let mut min_size: u64 = SZ_1M;
385 let mut extents: Vec<Extent> = Vec::new();
386 let mut holes: Vec<Extent> = Vec::new();
387 let mut last_pos: Option<u64> = None;
388
389 tree_search(
390 fd,
391 SearchKey::for_objectid_range(
392 BTRFS_DEV_TREE_OBJECTID as u64,
393 BTRFS_DEV_EXTENT_KEY,
394 devid,
395 devid,
396 ),
397 |hdr, data| {
398 if data.len()
399 < DEV_EXTENT_LENGTH_OFF + field_size!(btrfs_dev_extent, length)
400 {
401 return Ok(());
402 }
403 let phys_start = hdr.offset;
404 let len = read_le_u64(data, DEV_EXTENT_LENGTH_OFF);
405
406 min_size += len;
407
408 extents.push(Extent {
411 start: phys_start,
412 end: phys_start + len - 1,
413 });
414
415 if let Some(prev_end) = last_pos
416 && prev_end != phys_start
417 {
418 holes.push(Extent {
419 start: prev_end,
420 end: phys_start - 1,
421 });
422 }
423
424 last_pos = Some(phys_start + len);
425 Ok(())
426 },
427 )?;
428
429 extents.sort_by(|a, b| b.end.cmp(&a.end));
431
432 adjust_min_size(&mut extents, &mut holes, &mut min_size);
433
434 Ok(min_size)
435}
436
437fn hole_includes_sb_mirror(start: u64, end: u64) -> bool {
439 (0..BTRFS_SUPER_MIRROR_MAX).any(|i| {
440 let bytenr = sb_offset(i);
441 bytenr >= start && bytenr <= end
442 })
443}
444
445fn adjust_min_size(
455 extents: &mut Vec<Extent>,
456 holes: &mut Vec<Extent>,
457 min_size: &mut u64,
458) {
459 let mut scratch_space: u64 = 0;
460
461 while let Some(&ext) = extents.first() {
462 if ext.end < *min_size {
463 break;
464 }
465
466 let extent_len = ext.end - ext.start + 1;
467
468 let hole_idx = holes.iter().position(|h| {
470 let hole_len = h.end - h.start + 1;
471 hole_len >= extent_len
472 });
473
474 let Some(idx) = hole_idx else {
475 *min_size = ext.end + 1;
476 break;
477 };
478
479 if hole_includes_sb_mirror(
482 holes[idx].start,
483 holes[idx].start + extent_len - 1,
484 ) {
485 *min_size += extent_len;
486 }
487
488 let hole_len = holes[idx].end - holes[idx].start + 1;
490 if hole_len > extent_len {
491 holes[idx].start += extent_len;
492 } else {
493 holes.remove(idx);
494 }
495
496 extents.remove(0);
497
498 if extent_len > scratch_space {
499 scratch_space = extent_len;
500 }
501 }
502
503 if scratch_space > 0 {
504 *min_size += scratch_space;
505 *min_size += SZ_32M;
507 }
508}