1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use byteorder::{ByteOrder, LittleEndian};
use std::io::{Read, Seek};
use thiserror::Error;

#[derive(Error, Debug)]
/// Errors returned by e2size
pub enum E2SizeError {
    /// Error reading superblock
    #[error("Can't read superblock")]
    Read(#[from] std::io::Error),

    /// Bad magic value of superblock (corrupt fs?)
    #[error("Invalid magic value (expected: 0xEF53 got: 0x{0:X})")]
    BadMagic(u16),

    /// Blocksize was outside allowed range (corrupt fs?)
    #[error("Invalid block size: {0}")]
    InvalidBlockSize(u32),

    /// Filesystem outside seekable range (corrupt fs?)
    #[error("Filesystem too Big")]
    FilesystemTooBig,
}

/// Retrieves the size of a ext2/3/4 filesystem in bytes
/// 
/// Reads the superblock (1024 bytes at 1024 byte offset) from reader
pub fn e2size<T>(mut reader: T) -> std::result::Result<u64, E2SizeError>
where
    T: Read,
    T: Seek,
{
    let mut superblock = [0u8; 1024];
    reader.seek(std::io::SeekFrom::Start(1024))?;
    reader.read_exact(&mut superblock)?;

    let magic = LittleEndian::read_u16(&superblock[0x38..]);
    if magic != 0xEF53 {
        return Err(E2SizeError::BadMagic(magic));
    }

    let block_count = LittleEndian::read_u32(&superblock[0x4..]);

    let block_size_shift = LittleEndian::read_u32(&superblock[0x18..]);
    let block_size = 1u64
        .checked_shl(10 + block_size_shift)
        .ok_or_else(|| E2SizeError::InvalidBlockSize(block_size_shift))?;

    (block_size as u64)
        .checked_mul(block_count as u64)
        .ok_or_else(|| E2SizeError::FilesystemTooBig)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[cfg(not(tarpaulin_include))]
    fn valid_superblock() -> Result<(), E2SizeError> {
        let mut superblock = [0u8; 2048];
        LittleEndian::write_u16(&mut superblock[1024 + 0x38..], 0xEF53);
        LittleEndian::write_u32(&mut superblock[1024 + 0x4..], 256);
        LittleEndian::write_u32(&mut superblock[1024 + 0x18..], 2);

        let mut superblock = std::io::Cursor::new(superblock);
        assert_eq!(e2size(&mut superblock)?, 1048576);
        assert_eq!(e2size(superblock)?, 1048576);

        Ok(())
    }

    #[test]
    #[cfg(not(tarpaulin_include))]
    fn huge_filesystem() -> Result<(), E2SizeError> {
        let mut superblock = [0u8; 2048];
        LittleEndian::write_u16(&mut superblock[1024 + 0x38..], 0xEF53);
        LittleEndian::write_u32(&mut superblock[1024 + 0x4..], 1 << 31);
        LittleEndian::write_u32(&mut superblock[1024 + 0x18..], 22);

        let mut superblock = std::io::Cursor::new(superblock);
        assert_eq!(e2size(&mut superblock)?, 9223372036854775808);

        Ok(())
    }

    #[test]
    #[cfg(not(tarpaulin_include))]
    fn invalid_magic() -> Result<(), E2SizeError> {
        let mut superblock = [0u8; 2048];
        LittleEndian::write_u16(&mut superblock[1024 + 0x38..], 0xEF52);
        LittleEndian::write_u32(&mut superblock[1024 + 0x4..], 1);
        LittleEndian::write_u32(&mut superblock[1024 + 0x18..], 0);

        let mut superblock = std::io::Cursor::new(superblock);
        match e2size(&mut superblock) {
            Err(E2SizeError::BadMagic(e)) => assert_eq!(e, 0xEF52),
            _ => panic!("not reached!"),
        };

        Ok(())
    }

    #[test]
    #[cfg(not(tarpaulin_include))]
    fn invalid_block_size() -> Result<(), E2SizeError> {
        let mut superblock = [0u8; 2048];
        LittleEndian::write_u16(&mut superblock[1024 + 0x38..], 0xEF53);
        LittleEndian::write_u32(&mut superblock[1024 + 0x4..], 1);
        LittleEndian::write_u32(&mut superblock[1024 + 0x18..], 54);

        let mut superblock = std::io::Cursor::new(superblock);
        match e2size(&mut superblock) {
            Err(E2SizeError::InvalidBlockSize(_)) => {}
            Err(e) => panic!("not expected error: {}", e),
            _ => panic!("not reached!"),
        };

        Ok(())
    }

    #[test]
    #[cfg(not(tarpaulin_include))]
    fn filesystem_too_big() -> Result<(), E2SizeError> {
        let mut superblock = [0u8; 2048];
        LittleEndian::write_u16(&mut superblock[1024 + 0x38..], 0xEF53);
        LittleEndian::write_u32(&mut superblock[1024 + 0x4..], 1 << 31);
        LittleEndian::write_u32(&mut superblock[1024 + 0x18..], 23);

        let mut superblock = std::io::Cursor::new(superblock);
        match e2size(&mut superblock) {
            Err(E2SizeError::FilesystemTooBig) => {}
            Err(e) => panic!("not expected error: {}", e),
            _ => panic!("not reached!"),
        };

        Ok(())
    }
}