Skip to main content

btrfs_uapi/
scrub.rs

1//! # Data integrity scrubbing: verifying and repairing filesystem checksums
2//!
3//! A scrub reads every data and metadata block on the filesystem, verifies it
4//! against its stored checksum, and repairs any errors it finds using redundant
5//! copies where available (e.g. RAID profiles).  Scrubbing is the primary way
6//! to proactively detect silent data corruption.
7//!
8//! Requires `CAP_SYS_ADMIN`.
9
10use crate::raw::{
11    BTRFS_SCRUB_READONLY, btrfs_ioc_scrub, btrfs_ioc_scrub_cancel,
12    btrfs_ioc_scrub_progress, btrfs_ioctl_scrub_args,
13};
14use std::{
15    mem,
16    os::{fd::AsRawFd, unix::io::BorrowedFd},
17};
18
19/// Progress counters for a scrub operation, as returned by `BTRFS_IOC_SCRUB`
20/// or `BTRFS_IOC_SCRUB_PROGRESS`.
21#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
22pub struct ScrubProgress {
23    /// Number of data extents scrubbed.
24    pub data_extents_scrubbed: u64,
25    /// Number of tree (metadata) extents scrubbed.
26    pub tree_extents_scrubbed: u64,
27    /// Number of data bytes scrubbed.
28    pub data_bytes_scrubbed: u64,
29    /// Number of tree (metadata) bytes scrubbed.
30    pub tree_bytes_scrubbed: u64,
31    /// Number of read errors encountered.
32    pub read_errors: u64,
33    /// Number of checksum errors.
34    pub csum_errors: u64,
35    /// Number of metadata verification errors.
36    pub verify_errors: u64,
37    /// Number of data blocks with no checksum.
38    pub no_csum: u64,
39    /// Number of checksums with no corresponding data extent.
40    pub csum_discards: u64,
41    /// Number of bad superblock copies encountered.
42    pub super_errors: u64,
43    /// Number of internal memory allocation errors.
44    pub malloc_errors: u64,
45    /// Number of errors that could not be corrected.
46    pub uncorrectable_errors: u64,
47    /// Number of errors that were successfully corrected.
48    pub corrected_errors: u64,
49    /// Last physical byte address scrubbed (useful for resuming).
50    pub last_physical: u64,
51    /// Number of transient read errors (re-read succeeded).
52    pub unverified_errors: u64,
53}
54
55impl ScrubProgress {
56    /// Total number of hard errors (read, super, verify, checksum).
57    #[must_use]
58    pub fn error_count(&self) -> u64 {
59        self.read_errors
60            + self.super_errors
61            + self.verify_errors
62            + self.csum_errors
63    }
64
65    /// Total bytes scrubbed (data + tree).
66    #[must_use]
67    pub fn bytes_scrubbed(&self) -> u64 {
68        self.data_bytes_scrubbed + self.tree_bytes_scrubbed
69    }
70
71    /// Returns `true` if no errors of any kind were found.
72    #[must_use]
73    pub fn is_clean(&self) -> bool {
74        self.error_count() == 0
75            && self.corrected_errors == 0
76            && self.uncorrectable_errors == 0
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn scrub_progress_default_is_clean() {
86        let p = ScrubProgress::default();
87        assert!(p.is_clean());
88        assert_eq!(p.error_count(), 0);
89        assert_eq!(p.bytes_scrubbed(), 0);
90    }
91
92    #[test]
93    fn scrub_progress_error_count() {
94        let p = ScrubProgress {
95            read_errors: 1,
96            super_errors: 2,
97            verify_errors: 3,
98            csum_errors: 4,
99            ..ScrubProgress::default()
100        };
101        assert_eq!(p.error_count(), 10);
102        assert!(!p.is_clean());
103    }
104
105    #[test]
106    fn scrub_progress_bytes_scrubbed() {
107        let p = ScrubProgress {
108            data_bytes_scrubbed: 1000,
109            tree_bytes_scrubbed: 500,
110            ..ScrubProgress::default()
111        };
112        assert_eq!(p.bytes_scrubbed(), 1500);
113    }
114
115    #[test]
116    fn scrub_progress_corrected_errors_not_clean() {
117        let p = ScrubProgress {
118            corrected_errors: 1,
119            ..ScrubProgress::default()
120        };
121        assert!(!p.is_clean());
122        assert_eq!(p.error_count(), 0); // error_count doesn't include corrected
123    }
124
125    #[test]
126    fn scrub_progress_uncorrectable_errors_not_clean() {
127        let p = ScrubProgress {
128            uncorrectable_errors: 1,
129            ..ScrubProgress::default()
130        };
131        assert!(!p.is_clean());
132    }
133}
134
135fn from_raw(raw: &btrfs_ioctl_scrub_args) -> ScrubProgress {
136    let p = &raw.progress;
137    ScrubProgress {
138        data_extents_scrubbed: p.data_extents_scrubbed,
139        tree_extents_scrubbed: p.tree_extents_scrubbed,
140        data_bytes_scrubbed: p.data_bytes_scrubbed,
141        tree_bytes_scrubbed: p.tree_bytes_scrubbed,
142        read_errors: p.read_errors,
143        csum_errors: p.csum_errors,
144        verify_errors: p.verify_errors,
145        no_csum: p.no_csum,
146        csum_discards: p.csum_discards,
147        super_errors: p.super_errors,
148        malloc_errors: p.malloc_errors,
149        uncorrectable_errors: p.uncorrectable_errors,
150        corrected_errors: p.corrected_errors,
151        last_physical: p.last_physical,
152        unverified_errors: p.unverified_errors,
153    }
154}
155
156/// Start a scrub on the device identified by `devid` within the filesystem
157/// referred to by `fd`.
158///
159/// This call **blocks** until the scrub completes or is cancelled. On
160/// completion the final [`ScrubProgress`] counters are returned.
161///
162/// Set `readonly` to `true` to check for errors without attempting repairs.
163pub fn scrub_start(
164    fd: BorrowedFd,
165    devid: u64,
166    readonly: bool,
167) -> nix::Result<ScrubProgress> {
168    let mut args: btrfs_ioctl_scrub_args = unsafe { mem::zeroed() };
169    args.devid = devid;
170    args.start = 0;
171    args.end = u64::MAX;
172    if readonly {
173        args.flags = u64::from(BTRFS_SCRUB_READONLY);
174    }
175    unsafe { btrfs_ioc_scrub(fd.as_raw_fd(), &raw mut args) }?;
176    Ok(from_raw(&args))
177}
178
179/// Cancel the scrub currently running on the filesystem referred to by `fd`.
180pub fn scrub_cancel(fd: BorrowedFd) -> nix::Result<()> {
181    unsafe { btrfs_ioc_scrub_cancel(fd.as_raw_fd()) }?;
182    Ok(())
183}
184
185/// Query the progress of the scrub currently running on the device identified
186/// by `devid` within the filesystem referred to by `fd`.
187///
188/// Returns `None` if no scrub is running on that device (`ENOTCONN`).
189pub fn scrub_progress(
190    fd: BorrowedFd,
191    devid: u64,
192) -> nix::Result<Option<ScrubProgress>> {
193    let mut args: btrfs_ioctl_scrub_args = unsafe { mem::zeroed() };
194    args.devid = devid;
195    args.start = 0;
196    args.end = u64::MAX;
197
198    match unsafe { btrfs_ioc_scrub_progress(fd.as_raw_fd(), &raw mut args) } {
199        Err(nix::errno::Errno::ENOTCONN) => Ok(None),
200        Err(e) => Err(e),
201        Ok(_) => Ok(Some(from_raw(&args))),
202    }
203}