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
93fn copy_path_to_name(name: &mut [c_char], path: &CStr) -> nix::Result<()> {
97 let bytes = path.to_bytes(); if bytes.len() >= name.len() {
99 return Err(Errno::ENAMETOOLONG);
100 }
101 for (i, &b) in bytes.iter().enumerate() {
102 name[i] = b as c_char;
103 }
104 Ok(())
106}
107
108fn open_control() -> nix::Result<std::fs::File> {
111 OpenOptions::new()
112 .read(true)
113 .write(true)
114 .open("/dev/btrfs-control")
115 .map_err(|e| Errno::from_raw(e.raw_os_error().unwrap_or(nix::libc::ENODEV)))
116}
117
118pub fn device_info(fd: BorrowedFd, devid: u64) -> nix::Result<Option<DevInfo>> {
123 let mut raw: btrfs_ioctl_dev_info_args = unsafe { mem::zeroed() };
124 raw.devid = devid;
125
126 match unsafe { btrfs_ioc_dev_info(fd.as_raw_fd(), &mut raw) } {
127 Err(Errno::ENODEV) => return Ok(None),
128 Err(e) => return Err(e),
129 Ok(_) => {}
130 }
131
132 let path = unsafe { CStr::from_ptr(raw.path.as_ptr() as *const _) }
133 .to_string_lossy()
134 .into_owned();
135
136 Ok(Some(DevInfo {
137 devid: raw.devid,
138 uuid: Uuid::from_bytes(raw.uuid),
139 bytes_used: raw.bytes_used,
140 total_bytes: raw.total_bytes,
141 path,
142 }))
143}
144
145pub fn device_info_all(fd: BorrowedFd, fs_info: &FsInfo) -> nix::Result<Vec<DevInfo>> {
151 let mut devices = Vec::with_capacity(fs_info.num_devices as usize);
152 for devid in 1..=fs_info.max_id {
153 if let Some(info) = device_info(fd, devid)? {
154 devices.push(info);
155 }
156 }
157 Ok(devices)
158}
159
160pub fn device_add(fd: BorrowedFd, path: &CStr) -> nix::Result<()> {
165 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
166 copy_path_to_name(&mut raw.name, path)?;
167 unsafe { btrfs_ioc_add_dev(fd.as_raw_fd(), &raw) }?;
168 Ok(())
169}
170
171pub fn device_remove(fd: BorrowedFd, spec: DeviceSpec) -> nix::Result<()> {
178 let mut args: btrfs_ioctl_vol_args_v2 = unsafe { mem::zeroed() };
179
180 match spec {
181 DeviceSpec::Id(devid) => {
182 args.flags = BTRFS_DEVICE_SPEC_BY_ID as u64;
183 args.__bindgen_anon_2.devid = devid;
185 unsafe { btrfs_ioc_rm_dev_v2(fd.as_raw_fd(), &args) }?;
186 }
187 DeviceSpec::Path(path) => {
188 unsafe { copy_path_to_name(&mut args.__bindgen_anon_2.name, path) }?;
190 match unsafe { btrfs_ioc_rm_dev_v2(fd.as_raw_fd(), &args) } {
191 Ok(_) => {}
192 Err(Errno::ENOTTY) | Err(Errno::EOPNOTSUPP) => {
195 let mut old: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
196 copy_path_to_name(&mut old.name, path)?;
197 unsafe { btrfs_ioc_rm_dev(fd.as_raw_fd(), &old) }?;
198 }
199 Err(e) => return Err(e),
200 }
201 }
202 }
203
204 Ok(())
205}
206
207pub fn device_scan(path: &CStr) -> nix::Result<()> {
213 let ctl = open_control()?;
214 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
215 copy_path_to_name(&mut raw.name, path)?;
216 unsafe { btrfs_ioc_scan_dev(ctl.as_raw_fd(), &raw) }?;
217 Ok(())
218}
219
220pub fn device_forget(path: Option<&CStr>) -> nix::Result<()> {
228 let ctl = open_control()?;
229 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
230 if let Some(p) = path {
231 copy_path_to_name(&mut raw.name, p)?;
232 }
233 unsafe { btrfs_ioc_forget_dev(ctl.as_raw_fd(), &raw) }?;
234 Ok(())
235}
236
237pub fn device_ready(path: &CStr) -> nix::Result<()> {
245 let ctl = open_control()?;
246 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
249 copy_path_to_name(&mut raw.name, path)?;
250 unsafe { btrfs_ioc_devices_ready(ctl.as_raw_fd(), &mut raw) }?;
251 Ok(())
252}
253
254pub fn device_stats(fd: BorrowedFd, devid: u64, reset: bool) -> nix::Result<DevStats> {
260 let mut raw: btrfs_ioctl_get_dev_stats = unsafe { mem::zeroed() };
261 raw.devid = devid;
262 raw.nr_items = btrfs_dev_stat_values_BTRFS_DEV_STAT_VALUES_MAX as u64;
263 if reset {
264 raw.flags = BTRFS_DEV_STATS_RESET as u64;
265 }
266
267 unsafe { btrfs_ioc_get_dev_stats(fd.as_raw_fd(), &mut raw) }?;
268
269 Ok(DevStats {
270 devid,
271 write_errs: raw.values[btrfs_dev_stat_values_BTRFS_DEV_STAT_WRITE_ERRS as usize],
272 read_errs: raw.values[btrfs_dev_stat_values_BTRFS_DEV_STAT_READ_ERRS as usize],
273 flush_errs: raw.values[btrfs_dev_stat_values_BTRFS_DEV_STAT_FLUSH_ERRS as usize],
274 corruption_errs: raw.values[btrfs_dev_stat_values_BTRFS_DEV_STAT_CORRUPTION_ERRS as usize],
275 generation_errs: raw.values[btrfs_dev_stat_values_BTRFS_DEV_STAT_GENERATION_ERRS as usize],
276 })
277}