1use crate::{
11 filesystem::FsInfo,
12 raw::{
13 BTRFS_DEV_STATS_RESET, BTRFS_DEVICE_SPEC_BY_ID,
14 btrfs_dev_stat_values_BTRFS_DEV_STAT_CORRUPTION_ERRS,
15 btrfs_dev_stat_values_BTRFS_DEV_STAT_FLUSH_ERRS,
16 btrfs_dev_stat_values_BTRFS_DEV_STAT_GENERATION_ERRS,
17 btrfs_dev_stat_values_BTRFS_DEV_STAT_READ_ERRS,
18 btrfs_dev_stat_values_BTRFS_DEV_STAT_VALUES_MAX,
19 btrfs_dev_stat_values_BTRFS_DEV_STAT_WRITE_ERRS, btrfs_ioc_add_dev, btrfs_ioc_dev_info,
20 btrfs_ioc_devices_ready, btrfs_ioc_forget_dev, btrfs_ioc_get_dev_stats, btrfs_ioc_rm_dev,
21 btrfs_ioc_rm_dev_v2, btrfs_ioc_scan_dev, btrfs_ioctl_dev_info_args,
22 btrfs_ioctl_get_dev_stats, btrfs_ioctl_vol_args, btrfs_ioctl_vol_args_v2,
23 },
24};
25use nix::{errno::Errno, libc::c_char};
26use std::{
27 ffi::CStr,
28 fs::OpenOptions,
29 mem,
30 os::{fd::AsRawFd, unix::io::BorrowedFd},
31};
32use uuid::Uuid;
33
34#[derive(Debug, Clone)]
37pub struct DevInfo {
38 pub devid: u64,
40 pub uuid: Uuid,
42 pub bytes_used: u64,
44 pub total_bytes: u64,
46 pub path: String,
48}
49
50#[derive(Debug, Clone)]
52pub enum DeviceSpec<'a> {
53 Path(&'a CStr),
56 Id(u64),
58}
59
60#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
62pub struct DevStats {
63 pub devid: u64,
65 pub write_errs: u64,
67 pub read_errs: u64,
69 pub flush_errs: u64,
71 pub corruption_errs: u64,
73 pub generation_errs: u64,
75}
76
77impl DevStats {
78 pub fn total_errs(&self) -> u64 {
80 self.write_errs
81 + self.read_errs
82 + self.flush_errs
83 + self.corruption_errs
84 + self.generation_errs
85 }
86
87 pub fn is_clean(&self) -> bool {
89 self.total_errs() == 0
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn dev_stats_default_is_clean() {
99 let stats = DevStats::default();
100 assert!(stats.is_clean());
101 assert_eq!(stats.total_errs(), 0);
102 }
103
104 #[test]
105 fn dev_stats_total_errs() {
106 let stats = DevStats {
107 devid: 1,
108 write_errs: 1,
109 read_errs: 2,
110 flush_errs: 3,
111 corruption_errs: 4,
112 generation_errs: 5,
113 };
114 assert_eq!(stats.total_errs(), 15);
115 assert!(!stats.is_clean());
116 }
117
118 #[test]
119 fn dev_stats_single_error_not_clean() {
120 let stats = DevStats {
121 corruption_errs: 1,
122 ..DevStats::default()
123 };
124 assert!(!stats.is_clean());
125 assert_eq!(stats.total_errs(), 1);
126 }
127}
128
129fn copy_path_to_name(name: &mut [c_char], path: &CStr) -> nix::Result<()> {
133 let bytes = path.to_bytes(); if bytes.len() >= name.len() {
135 return Err(Errno::ENAMETOOLONG);
136 }
137 for (i, &b) in bytes.iter().enumerate() {
138 name[i] = b as c_char;
139 }
140 Ok(())
142}
143
144fn open_control() -> nix::Result<std::fs::File> {
147 OpenOptions::new()
148 .read(true)
149 .write(true)
150 .open("/dev/btrfs-control")
151 .map_err(|e| Errno::from_raw(e.raw_os_error().unwrap_or(nix::libc::ENODEV)))
152}
153
154pub fn device_info(fd: BorrowedFd, devid: u64) -> nix::Result<Option<DevInfo>> {
159 let mut raw: btrfs_ioctl_dev_info_args = unsafe { mem::zeroed() };
160 raw.devid = devid;
161
162 match unsafe { btrfs_ioc_dev_info(fd.as_raw_fd(), &mut raw) } {
163 Err(Errno::ENODEV) => return Ok(None),
164 Err(e) => return Err(e),
165 Ok(_) => {}
166 }
167
168 let path = unsafe { CStr::from_ptr(raw.path.as_ptr() as *const _) }
169 .to_string_lossy()
170 .into_owned();
171
172 Ok(Some(DevInfo {
173 devid: raw.devid,
174 uuid: Uuid::from_bytes(raw.uuid),
175 bytes_used: raw.bytes_used,
176 total_bytes: raw.total_bytes,
177 path,
178 }))
179}
180
181pub fn device_info_all(fd: BorrowedFd, fs_info: &FsInfo) -> nix::Result<Vec<DevInfo>> {
187 let mut devices = Vec::with_capacity(fs_info.num_devices as usize);
188 for devid in 1..=fs_info.max_id {
189 if let Some(info) = device_info(fd, devid)? {
190 devices.push(info);
191 }
192 }
193 Ok(devices)
194}
195
196pub fn device_add(fd: BorrowedFd, path: &CStr) -> nix::Result<()> {
201 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
202 copy_path_to_name(&mut raw.name, path)?;
203 unsafe { btrfs_ioc_add_dev(fd.as_raw_fd(), &raw) }?;
204 Ok(())
205}
206
207pub fn device_remove(fd: BorrowedFd, spec: DeviceSpec) -> nix::Result<()> {
214 let mut args: btrfs_ioctl_vol_args_v2 = unsafe { mem::zeroed() };
215
216 match spec {
217 DeviceSpec::Id(devid) => {
218 args.flags = BTRFS_DEVICE_SPEC_BY_ID as u64;
219 args.__bindgen_anon_2.devid = devid;
221 unsafe { btrfs_ioc_rm_dev_v2(fd.as_raw_fd(), &args) }?;
222 }
223 DeviceSpec::Path(path) => {
224 unsafe { copy_path_to_name(&mut args.__bindgen_anon_2.name, path) }?;
226 match unsafe { btrfs_ioc_rm_dev_v2(fd.as_raw_fd(), &args) } {
227 Ok(_) => {}
228 Err(Errno::ENOTTY) | Err(Errno::EOPNOTSUPP) => {
231 let mut old: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
232 copy_path_to_name(&mut old.name, path)?;
233 unsafe { btrfs_ioc_rm_dev(fd.as_raw_fd(), &old) }?;
234 }
235 Err(e) => return Err(e),
236 }
237 }
238 }
239
240 Ok(())
241}
242
243pub fn device_scan(path: &CStr) -> nix::Result<()> {
249 let ctl = open_control()?;
250 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
251 copy_path_to_name(&mut raw.name, path)?;
252 unsafe { btrfs_ioc_scan_dev(ctl.as_raw_fd(), &raw) }?;
253 Ok(())
254}
255
256pub fn device_forget(path: Option<&CStr>) -> nix::Result<()> {
264 let ctl = open_control()?;
265 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
266 if let Some(p) = path {
267 copy_path_to_name(&mut raw.name, p)?;
268 }
269 unsafe { btrfs_ioc_forget_dev(ctl.as_raw_fd(), &raw) }?;
270 Ok(())
271}
272
273pub fn device_ready(path: &CStr) -> nix::Result<()> {
281 let ctl = open_control()?;
282 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
285 copy_path_to_name(&mut raw.name, path)?;
286 unsafe { btrfs_ioc_devices_ready(ctl.as_raw_fd(), &mut raw) }?;
287 Ok(())
288}
289
290pub fn device_stats(fd: BorrowedFd, devid: u64, reset: bool) -> nix::Result<DevStats> {
296 let mut raw: btrfs_ioctl_get_dev_stats = unsafe { mem::zeroed() };
297 raw.devid = devid;
298 raw.nr_items = btrfs_dev_stat_values_BTRFS_DEV_STAT_VALUES_MAX as u64;
299 if reset {
300 raw.flags = BTRFS_DEV_STATS_RESET as u64;
301 }
302
303 unsafe { btrfs_ioc_get_dev_stats(fd.as_raw_fd(), &mut raw) }?;
304
305 Ok(DevStats {
306 devid,
307 write_errs: raw.values[btrfs_dev_stat_values_BTRFS_DEV_STAT_WRITE_ERRS as usize],
308 read_errs: raw.values[btrfs_dev_stat_values_BTRFS_DEV_STAT_READ_ERRS as usize],
309 flush_errs: raw.values[btrfs_dev_stat_values_BTRFS_DEV_STAT_FLUSH_ERRS as usize],
310 corruption_errs: raw.values[btrfs_dev_stat_values_BTRFS_DEV_STAT_CORRUPTION_ERRS as usize],
311 generation_errs: raw.values[btrfs_dev_stat_values_BTRFS_DEV_STAT_GENERATION_ERRS as usize],
312 })
313}