Skip to main content

ext4_mkfs/
block_device.rs

1//! Block device trait and implementations.
2
3use crate::{Error, Result};
4use std::io::{Read, Seek, SeekFrom, Write};
5
6/// Trait for block device operations.
7///
8/// Implement this trait to provide a custom block device backend.
9/// The block device must support reading and writing at block granularity.
10pub trait BlockDevice: Send {
11    /// Returns the physical block size in bytes.
12    fn block_size(&self) -> u32;
13
14    /// Returns the total number of blocks.
15    fn block_count(&self) -> u64;
16
17    /// Read blocks from the device.
18    ///
19    /// # Arguments
20    /// * `buf` - Buffer to read into (must be at least `block_count * block_size` bytes)
21    /// * `block_id` - Starting block number
22    /// * `block_count` - Number of blocks to read
23    fn read_blocks(&mut self, buf: &mut [u8], block_id: u64, block_count: u32) -> Result<()>;
24
25    /// Write blocks to the device.
26    ///
27    /// # Arguments
28    /// * `buf` - Buffer to write from (must be at least `block_count * block_size` bytes)
29    /// * `block_id` - Starting block number
30    /// * `block_count` - Number of blocks to write
31    fn write_blocks(&mut self, buf: &[u8], block_id: u64, block_count: u32) -> Result<()>;
32
33    /// Open the device (optional).
34    fn open(&mut self) -> Result<()> {
35        Ok(())
36    }
37
38    /// Close the device (optional).
39    fn close(&mut self) -> Result<()> {
40        Ok(())
41    }
42}
43
44/// A block device backed by any type implementing `Read + Write + Seek`.
45///
46/// This is useful for using files, memory buffers, or any seekable I/O as a block device.
47pub struct IoBlockDevice<T> {
48    inner: T,
49    block_size: u32,
50    block_count: u64,
51}
52
53impl<T> IoBlockDevice<T>
54where
55    T: Read + Write + Seek,
56{
57    /// Create a new IoBlockDevice wrapping the given I/O object.
58    ///
59    /// # Arguments
60    /// * `inner` - The underlying I/O object
61    /// * `block_size` - Block size in bytes (typically 512 or 4096)
62    /// * `total_size` - Total size of the device in bytes
63    pub fn new(inner: T, block_size: u32, total_size: u64) -> Self {
64        let block_count = total_size / block_size as u64;
65        Self {
66            inner,
67            block_size,
68            block_count,
69        }
70    }
71
72    /// Get a reference to the underlying I/O object.
73    pub fn inner(&self) -> &T {
74        &self.inner
75    }
76
77    /// Get a mutable reference to the underlying I/O object.
78    pub fn inner_mut(&mut self) -> &mut T {
79        &mut self.inner
80    }
81
82    /// Consume the wrapper and return the underlying I/O object.
83    pub fn into_inner(self) -> T {
84        self.inner
85    }
86}
87
88impl<T> BlockDevice for IoBlockDevice<T>
89where
90    T: Read + Write + Seek + Send,
91{
92    fn block_size(&self) -> u32 {
93        self.block_size
94    }
95
96    fn block_count(&self) -> u64 {
97        self.block_count
98    }
99
100    fn read_blocks(&mut self, buf: &mut [u8], block_id: u64, block_count: u32) -> Result<()> {
101        let offset = block_id * self.block_size as u64;
102        let size = block_count as usize * self.block_size as usize;
103
104        if buf.len() < size {
105            return Err(Error::InvalidConfig(format!(
106                "buffer too small: need {} bytes, got {}",
107                size,
108                buf.len()
109            )));
110        }
111
112        self.inner.seek(SeekFrom::Start(offset))?;
113        self.inner.read_exact(&mut buf[..size])?;
114        Ok(())
115    }
116
117    fn write_blocks(&mut self, buf: &[u8], block_id: u64, block_count: u32) -> Result<()> {
118        let offset = block_id * self.block_size as u64;
119        let size = block_count as usize * self.block_size as usize;
120
121        if buf.len() < size {
122            return Err(Error::InvalidConfig(format!(
123                "buffer too small: need {} bytes, got {}",
124                size,
125                buf.len()
126            )));
127        }
128
129        self.inner.seek(SeekFrom::Start(offset))?;
130        self.inner.write_all(&buf[..size])?;
131        Ok(())
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use std::io::Cursor;
139
140    #[test]
141    fn test_io_block_device_creation() {
142        let data = vec![0u8; 4096];
143        let cursor = Cursor::new(data);
144        let device = IoBlockDevice::new(cursor, 512, 4096);
145
146        assert_eq!(device.block_size(), 512);
147        assert_eq!(device.block_count(), 8); // 4096 / 512 = 8
148    }
149
150    #[test]
151    fn test_io_block_device_write_read() {
152        let data = vec![0u8; 4096];
153        let cursor = Cursor::new(data);
154        let mut device = IoBlockDevice::new(cursor, 512, 4096);
155
156        // Write test data
157        let write_buf = vec![0xAB; 512];
158        device.write_blocks(&write_buf, 0, 1).unwrap();
159
160        // Read it back
161        let mut read_buf = vec![0u8; 512];
162        device.read_blocks(&mut read_buf, 0, 1).unwrap();
163
164        assert_eq!(read_buf, write_buf);
165    }
166
167    #[test]
168    fn test_io_block_device_write_read_multiple_blocks() {
169        let data = vec![0u8; 4096];
170        let cursor = Cursor::new(data);
171        let mut device = IoBlockDevice::new(cursor, 512, 4096);
172
173        // Write 2 blocks at block 2
174        let write_buf = vec![0xCD; 1024];
175        device.write_blocks(&write_buf, 2, 2).unwrap();
176
177        // Read them back
178        let mut read_buf = vec![0u8; 1024];
179        device.read_blocks(&mut read_buf, 2, 2).unwrap();
180
181        assert_eq!(read_buf, write_buf);
182
183        // Verify block 0 is still zero
184        let mut zero_buf = vec![0u8; 512];
185        device.read_blocks(&mut zero_buf, 0, 1).unwrap();
186        assert_eq!(zero_buf, vec![0u8; 512]);
187    }
188
189    #[test]
190    fn test_io_block_device_buffer_too_small() {
191        let data = vec![0u8; 4096];
192        let cursor = Cursor::new(data);
193        let mut device = IoBlockDevice::new(cursor, 512, 4096);
194
195        // Try to read 2 blocks into a 512-byte buffer
196        let mut small_buf = vec![0u8; 512];
197        let result = device.read_blocks(&mut small_buf, 0, 2);
198        assert!(result.is_err());
199
200        // Try to write 2 blocks from a 512-byte buffer
201        let small_write_buf = vec![0u8; 512];
202        let result = device.write_blocks(&small_write_buf, 0, 2);
203        assert!(result.is_err());
204    }
205
206    #[test]
207    fn test_io_block_device_into_inner() {
208        let data = vec![0u8; 1024];
209        let cursor = Cursor::new(data);
210        let device = IoBlockDevice::new(cursor, 512, 1024);
211
212        let recovered = device.into_inner();
213        assert_eq!(recovered.into_inner().len(), 1024);
214    }
215
216    #[test]
217    fn test_block_device_open_close_default() {
218        let data = vec![0u8; 1024];
219        let cursor = Cursor::new(data);
220        let mut device = IoBlockDevice::new(cursor, 512, 1024);
221
222        // Default implementations should succeed
223        assert!(device.open().is_ok());
224        assert!(device.close().is_ok());
225    }
226}