Skip to main content

disk_core/unix_common/
validate.rs

1//! Shared validation helpers for Unix-like filesystems.
2//!
3//! Provides small utility functions for checking magic bytes, value ranges,
4//! and collecting validation results with warnings and errors.
5
6use anyhow::{bail, Result};
7
8/// Check that magic bytes at a given offset match the expected value.
9pub fn validate_magic(data: &[u8], offset: usize, expected: &[u8], fs_name: &str) -> Result<()> {
10    if data.len() < offset + expected.len() {
11        bail!("{fs_name}: data too short for magic check at offset {offset}");
12    }
13    if &data[offset..offset + expected.len()] != expected {
14        bail!("{fs_name}: invalid magic at offset {offset}");
15    }
16    Ok(())
17}
18
19/// Check that a value is a power of two.
20pub fn validate_power_of_two(value: u64, name: &str) -> Result<()> {
21    if value == 0 || (value & (value - 1)) != 0 {
22        bail!("{name}: {value} is not a power of two");
23    }
24    Ok(())
25}
26
27/// Check that a value falls within an expected range (inclusive).
28pub fn validate_range(value: u64, min: u64, max: u64, name: &str) -> Result<()> {
29    if value < min || value > max {
30        bail!("{name}: {value} not in range [{min}, {max}]");
31    }
32    Ok(())
33}
34
35/// Accumulator for validation results (non-fatal warnings + fatal errors).
36#[derive(Default)]
37pub struct ValidationResult {
38    pub warnings: Vec<String>,
39    pub errors: Vec<String>,
40}
41
42impl ValidationResult {
43    pub fn warn(&mut self, msg: impl Into<String>) {
44        self.warnings.push(msg.into());
45    }
46
47    pub fn error(&mut self, msg: impl Into<String>) {
48        self.errors.push(msg.into());
49    }
50
51    pub fn has_errors(&self) -> bool {
52        !self.errors.is_empty()
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn test_validate_magic_ok() {
62        let data = [0x53, 0xEF, 0x00, 0x00]; // ext2 magic at offset 0
63        assert!(validate_magic(&data, 0, &[0x53, 0xEF], "ext").is_ok());
64    }
65
66    #[test]
67    fn test_validate_magic_at_offset() {
68        let data = [0x00, 0x00, 0x53, 0xEF];
69        assert!(validate_magic(&data, 2, &[0x53, 0xEF], "ext").is_ok());
70    }
71
72    #[test]
73    fn test_validate_magic_mismatch() {
74        let data = [0x00, 0x00, 0x00, 0x00];
75        let err = validate_magic(&data, 0, &[0x53, 0xEF], "ext").unwrap_err();
76        assert!(err.to_string().contains("invalid magic"));
77    }
78
79    #[test]
80    fn test_validate_magic_too_short() {
81        let data = [0x53];
82        let err = validate_magic(&data, 0, &[0x53, 0xEF], "ext").unwrap_err();
83        assert!(err.to_string().contains("too short"));
84    }
85
86    #[test]
87    fn test_validate_power_of_two_ok() {
88        assert!(validate_power_of_two(1, "block_size").is_ok());
89        assert!(validate_power_of_two(512, "block_size").is_ok());
90        assert!(validate_power_of_two(1024, "block_size").is_ok());
91        assert!(validate_power_of_two(4096, "block_size").is_ok());
92    }
93
94    #[test]
95    fn test_validate_power_of_two_zero() {
96        assert!(validate_power_of_two(0, "block_size").is_err());
97    }
98
99    #[test]
100    fn test_validate_power_of_two_non_power() {
101        let err = validate_power_of_two(3, "block_size").unwrap_err();
102        assert!(err.to_string().contains("not a power of two"));
103    }
104
105    #[test]
106    fn test_validate_range_ok() {
107        assert!(validate_range(512, 512, 4096, "block_size").is_ok());
108        assert!(validate_range(4096, 512, 4096, "block_size").is_ok());
109        assert!(validate_range(2000, 512, 4096, "block_size").is_ok());
110    }
111
112    #[test]
113    fn test_validate_range_below() {
114        let err = validate_range(256, 512, 4096, "block_size").unwrap_err();
115        assert!(err.to_string().contains("not in range"));
116    }
117
118    #[test]
119    fn test_validate_range_above() {
120        let err = validate_range(8192, 512, 4096, "block_size").unwrap_err();
121        assert!(err.to_string().contains("not in range"));
122    }
123
124    #[test]
125    fn test_validation_result() {
126        let mut vr = ValidationResult::default();
127        assert!(!vr.has_errors());
128
129        vr.warn("minor issue");
130        assert!(!vr.has_errors());
131        assert_eq!(vr.warnings.len(), 1);
132
133        vr.error("fatal problem");
134        assert!(vr.has_errors());
135        assert_eq!(vr.errors.len(), 1);
136    }
137}