1use crate::raw::{
10 BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL, BTRFS_IOCTL_DEV_REPLACE_CMD_START,
11 BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS,
12 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS,
13 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID,
14 BTRFS_IOCTL_DEV_REPLACE_RESULT_ALREADY_STARTED, BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR,
15 BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED, BTRFS_IOCTL_DEV_REPLACE_RESULT_SCRUB_INPROGRESS,
16 BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED, BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED,
17 BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED, BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED,
18 BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED, btrfs_ioc_dev_replace, btrfs_ioctl_dev_replace_args,
19};
20use nix::errno::Errno;
21use std::{
22 ffi::CStr,
23 mem,
24 os::{fd::AsRawFd, unix::io::BorrowedFd},
25 time::{Duration, SystemTime, UNIX_EPOCH},
26};
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum ReplaceState {
31 NeverStarted,
32 Started,
33 Finished,
34 Canceled,
35 Suspended,
36}
37
38impl ReplaceState {
39 fn from_raw(val: u64) -> Option<ReplaceState> {
40 match val {
41 x if x == BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED as u64 => {
42 Some(ReplaceState::NeverStarted)
43 }
44 x if x == BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED as u64 => Some(ReplaceState::Started),
45 x if x == BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED as u64 => Some(ReplaceState::Finished),
46 x if x == BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED as u64 => Some(ReplaceState::Canceled),
47 x if x == BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED as u64 => {
48 Some(ReplaceState::Suspended)
49 }
50 _ => None,
51 }
52 }
53}
54
55#[derive(Debug, Clone)]
57pub struct ReplaceStatus {
58 pub state: ReplaceState,
60 pub progress_1000: u64,
62 pub time_started: Option<SystemTime>,
64 pub time_stopped: Option<SystemTime>,
66 pub num_write_errors: u64,
68 pub num_uncorrectable_read_errors: u64,
70}
71
72fn epoch_to_systemtime(secs: u64) -> Option<SystemTime> {
73 if secs == 0 {
74 None
75 } else {
76 Some(UNIX_EPOCH + Duration::from_secs(secs))
77 }
78}
79
80pub enum ReplaceSource<'a> {
82 DevId(u64),
84 Path(&'a CStr),
86}
87
88pub fn replace_status(fd: BorrowedFd) -> nix::Result<ReplaceStatus> {
91 let mut args: btrfs_ioctl_dev_replace_args = unsafe { mem::zeroed() };
92 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS as u64;
93
94 unsafe { btrfs_ioc_dev_replace(fd.as_raw_fd(), &mut args) }?;
95
96 let status = unsafe { &args.__bindgen_anon_1.status };
98 let state = ReplaceState::from_raw(status.replace_state).ok_or(Errno::EINVAL)?;
99
100 Ok(ReplaceStatus {
101 state,
102 progress_1000: status.progress_1000,
103 time_started: epoch_to_systemtime(status.time_started),
104 time_stopped: epoch_to_systemtime(status.time_stopped),
105 num_write_errors: status.num_write_errors,
106 num_uncorrectable_read_errors: status.num_uncorrectable_read_errors,
107 })
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum ReplaceStartError {
115 AlreadyStarted,
117 ScrubInProgress,
119}
120
121impl std::fmt::Display for ReplaceStartError {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 match self {
124 ReplaceStartError::AlreadyStarted => {
125 write!(f, "a device replace operation is already in progress")
126 }
127 ReplaceStartError::ScrubInProgress => {
128 write!(f, "a scrub is in progress; cancel it first")
129 }
130 }
131 }
132}
133
134impl std::error::Error for ReplaceStartError {}
135
136pub fn replace_start(
146 fd: BorrowedFd,
147 source: ReplaceSource,
148 tgtdev_path: &CStr,
149 avoid_srcdev: bool,
150) -> nix::Result<Result<(), ReplaceStartError>> {
151 let mut args: btrfs_ioctl_dev_replace_args = unsafe { mem::zeroed() };
152 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START as u64;
153
154 let start = unsafe { &mut args.__bindgen_anon_1.start };
156
157 match source {
158 ReplaceSource::DevId(devid) => {
159 start.srcdevid = devid;
160 }
161 ReplaceSource::Path(path) => {
162 start.srcdevid = 0;
163 let bytes = path.to_bytes();
164 if bytes.len() >= start.srcdev_name.len() {
165 return Err(Errno::ENAMETOOLONG);
166 }
167 start.srcdev_name[..bytes.len()].copy_from_slice(bytes);
168 }
169 }
170
171 let tgt_bytes = tgtdev_path.to_bytes();
172 if tgt_bytes.len() >= start.tgtdev_name.len() {
173 return Err(Errno::ENAMETOOLONG);
174 }
175 start.tgtdev_name[..tgt_bytes.len()].copy_from_slice(tgt_bytes);
176
177 start.cont_reading_from_srcdev_mode = if avoid_srcdev {
178 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID as u64
179 } else {
180 BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS as u64
181 };
182
183 unsafe { btrfs_ioc_dev_replace(fd.as_raw_fd(), &mut args) }?;
184
185 match args.result {
186 x if x == BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR as u64 => Ok(Ok(())),
187 x if x == BTRFS_IOCTL_DEV_REPLACE_RESULT_ALREADY_STARTED as u64 => {
188 Ok(Err(ReplaceStartError::AlreadyStarted))
189 }
190 x if x == BTRFS_IOCTL_DEV_REPLACE_RESULT_SCRUB_INPROGRESS as u64 => {
191 Ok(Err(ReplaceStartError::ScrubInProgress))
192 }
193 _ => Err(Errno::EINVAL),
194 }
195}
196
197pub fn replace_cancel(fd: BorrowedFd) -> nix::Result<bool> {
203 let mut args: btrfs_ioctl_dev_replace_args = unsafe { mem::zeroed() };
204 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL as u64;
205
206 unsafe { btrfs_ioc_dev_replace(fd.as_raw_fd(), &mut args) }?;
207
208 match args.result {
209 x if x == BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR as u64 => Ok(true),
210 x if x == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED as u64 => Ok(false),
211 _ => Err(Errno::EINVAL),
212 }
213}