Skip to main content

ext4_mkfs/
mkfs.rs

1//! mkfs functionality for creating ext4 filesystems.
2
3use crate::block_device::BlockDevice;
4use crate::{Error, Result};
5use ext4_mkfs_sys::{self as ffi, F_SET_EXT2, F_SET_EXT3, F_SET_EXT4};
6use std::ffi::CString;
7use std::os::raw::{c_int, c_void};
8use std::ptr;
9
10/// Filesystem type to create.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub enum FsType {
13    /// ext2 filesystem
14    Ext2,
15    /// ext3 filesystem
16    Ext3,
17    /// ext4 filesystem (default)
18    #[default]
19    Ext4,
20}
21
22impl FsType {
23    fn to_c_int(self) -> c_int {
24        match self {
25            FsType::Ext2 => F_SET_EXT2,
26            FsType::Ext3 => F_SET_EXT3,
27            FsType::Ext4 => F_SET_EXT4,
28        }
29    }
30}
31
32/// Configuration for creating an ext4 filesystem.
33#[derive(Debug, Clone)]
34pub struct MkfsConfig {
35    /// Filesystem type (ext2, ext3, or ext4)
36    pub fs_type: FsType,
37    /// Block size in bytes (1024, 2048, or 4096)
38    pub block_size: u32,
39    /// Volume label (max 16 characters)
40    pub label: Option<String>,
41    /// UUID for the filesystem
42    pub uuid: Option<[u8; 16]>,
43    /// Enable journaling (only for ext3/ext4)
44    pub journal: bool,
45    /// Inode size (128 or 256)
46    pub inode_size: u32,
47}
48
49impl Default for MkfsConfig {
50    fn default() -> Self {
51        Self {
52            fs_type: FsType::Ext4,
53            block_size: 4096,
54            label: None,
55            uuid: None,
56            journal: false, // Disabled by default for simplicity
57            inode_size: 256,
58        }
59    }
60}
61
62impl MkfsConfig {
63    /// Create a new configuration with default values.
64    pub fn new() -> Self {
65        Self::default()
66    }
67
68    /// Set the filesystem type.
69    pub fn fs_type(mut self, fs_type: FsType) -> Self {
70        self.fs_type = fs_type;
71        self
72    }
73
74    /// Set the block size.
75    pub fn block_size(mut self, size: u32) -> Self {
76        self.block_size = size;
77        self
78    }
79
80    /// Set the volume label.
81    pub fn label(mut self, label: impl Into<String>) -> Self {
82        self.label = Some(label.into());
83        self
84    }
85
86    /// Set the UUID.
87    pub fn uuid(mut self, uuid: [u8; 16]) -> Self {
88        self.uuid = Some(uuid);
89        self
90    }
91
92    /// Enable or disable journaling.
93    pub fn journal(mut self, enable: bool) -> Self {
94        self.journal = enable;
95        self
96    }
97
98    /// Set the inode size.
99    pub fn inode_size(mut self, size: u32) -> Self {
100        self.inode_size = size;
101        self
102    }
103}
104
105/// Context passed to FFI callbacks (type-erased for C compatibility)
106struct BlockDevContext {
107    device: Box<dyn BlockDevice>,
108    buffer: Vec<u8>,
109}
110
111/// Open callback for lwext4
112unsafe extern "C" fn bdev_open(bdev: *mut ffi::ext4_blockdev) -> c_int {
113    let ctx = (*(*bdev).bdif).p_user as *mut BlockDevContext;
114    if ctx.is_null() {
115        return -1;
116    }
117
118    match (*ctx).device.open() {
119        Ok(_) => 0,
120        Err(_) => -1,
121    }
122}
123
124/// Close callback for lwext4
125unsafe extern "C" fn bdev_close(bdev: *mut ffi::ext4_blockdev) -> c_int {
126    let ctx = (*(*bdev).bdif).p_user as *mut BlockDevContext;
127    if ctx.is_null() {
128        return -1;
129    }
130
131    match (*ctx).device.close() {
132        Ok(_) => 0,
133        Err(_) => -1,
134    }
135}
136
137/// Read callback for lwext4
138unsafe extern "C" fn bdev_bread(
139    bdev: *mut ffi::ext4_blockdev,
140    buf: *mut c_void,
141    blk_id: u64,
142    blk_cnt: u32,
143) -> c_int {
144    let ctx = (*(*bdev).bdif).p_user as *mut BlockDevContext;
145    if ctx.is_null() || buf.is_null() {
146        return -1;
147    }
148
149    let block_size = (*(*bdev).bdif).ph_bsize as usize;
150    let size = blk_cnt as usize * block_size;
151    let slice = std::slice::from_raw_parts_mut(buf as *mut u8, size);
152
153    match (*ctx).device.read_blocks(slice, blk_id, blk_cnt) {
154        Ok(_) => 0,
155        Err(_) => -1,
156    }
157}
158
159/// Write callback for lwext4
160unsafe extern "C" fn bdev_bwrite(
161    bdev: *mut ffi::ext4_blockdev,
162    buf: *const c_void,
163    blk_id: u64,
164    blk_cnt: u32,
165) -> c_int {
166    let ctx = (*(*bdev).bdif).p_user as *mut BlockDevContext;
167    if ctx.is_null() || buf.is_null() {
168        return -1;
169    }
170
171    let block_size = (*(*bdev).bdif).ph_bsize as usize;
172    let size = blk_cnt as usize * block_size;
173    let slice = std::slice::from_raw_parts(buf as *const u8, size);
174
175    match (*ctx).device.write_blocks(slice, blk_id, blk_cnt) {
176        Ok(_) => 0,
177        Err(_) => -1,
178    }
179}
180
181/// Minimum filesystem size (1 MB)
182const MIN_FS_SIZE: u64 = 1024 * 1024;
183
184/// Format a block device as an ext4 filesystem.
185///
186/// # Arguments
187/// * `device` - The block device to format
188/// * `config` - Configuration for the filesystem
189///
190/// # Example
191/// ```no_run
192/// use ext4_mkfs::{mkfs, MkfsConfig, IoBlockDevice};
193/// use std::fs::OpenOptions;
194///
195/// let file = OpenOptions::new()
196///     .read(true)
197///     .write(true)
198///     .open("disk.img")
199///     .unwrap();
200///
201/// let device = IoBlockDevice::new(file, 512, 100 * 1024 * 1024);
202/// mkfs(device, MkfsConfig::default()).unwrap();
203/// ```
204pub fn mkfs<D: BlockDevice + 'static>(device: D, config: MkfsConfig) -> Result<()> {
205    let block_size = device.block_size();
206    let block_count = device.block_count();
207    let total_size = block_size as u64 * block_count;
208
209    // Validate minimum size
210    if total_size < MIN_FS_SIZE {
211        return Err(Error::DeviceTooSmall {
212            min: MIN_FS_SIZE,
213            actual: total_size,
214        });
215    }
216
217    // Validate block size
218    if ![1024, 2048, 4096].contains(&config.block_size) {
219        return Err(Error::InvalidConfig(format!(
220            "block size must be 1024, 2048, or 4096, got {}",
221            config.block_size
222        )));
223    }
224
225    // Create the buffer for physical block operations
226    let buffer = vec![0u8; block_size as usize];
227
228    // Create context for callbacks (boxed for type erasure)
229    let mut ctx = Box::new(BlockDevContext {
230        device: Box::new(device),
231        buffer,
232    });
233
234    // Create and initialize the block device interface
235    let mut bdif = ffi::ext4_blockdev_iface {
236        open: Some(bdev_open),
237        bread: Some(bdev_bread),
238        bwrite: Some(bdev_bwrite),
239        close: Some(bdev_close),
240        lock: None,
241        unlock: None,
242        ph_bsize: block_size,
243        ph_bcnt: block_count,
244        ph_bbuf: ctx.buffer.as_mut_ptr(),
245        ph_refctr: 0,
246        bread_ctr: 0,
247        bwrite_ctr: 0,
248        p_user: ctx.as_mut() as *mut BlockDevContext as *mut c_void,
249    };
250
251    // Create the block device structure
252    let mut bdev = ffi::ext4_blockdev {
253        bdif: &mut bdif,
254        part_offset: 0,
255        part_size: total_size,
256        bc: ptr::null_mut(),
257        lg_bsize: 0,
258        lg_bcnt: 0,
259        cache_write_back: 0,
260        fs: ptr::null_mut(),
261        journal: ptr::null_mut(),
262    };
263
264    // Prepare mkfs info
265    let label_cstring = config.label.as_ref().map(|s| {
266        let truncated: String = s.chars().take(16).collect();
267        CString::new(truncated).unwrap_or_else(|_| CString::new("").unwrap())
268    });
269
270    let mut info = ffi::ext4_mkfs_info {
271        len: total_size,
272        block_size: config.block_size,
273        blocks_per_group: 0, // Let lwext4 calculate
274        inodes_per_group: 0, // Let lwext4 calculate
275        inode_size: config.inode_size,
276        inodes: 0,           // Let lwext4 calculate
277        journal_blocks: 0,   // Let lwext4 calculate
278        feat_ro_compat: 0,
279        feat_compat: 0,
280        feat_incompat: 0,
281        bg_desc_reserve_blocks: 0,
282        dsc_size: 0,
283        uuid: config.uuid.unwrap_or([0u8; 16]),
284        journal: config.journal,
285        label: label_cstring
286            .as_ref()
287            .map(|s| s.as_ptr())
288            .unwrap_or(ptr::null()),
289    };
290
291    // Allocate filesystem structure on heap (it's large)
292    // We use a zeroed allocation since ext4_fs is opaque
293    let fs_size = 4096; // Generous size for ext4_fs structure
294    let fs_layout = std::alloc::Layout::from_size_align(fs_size, 8).unwrap();
295    let fs_ptr = unsafe { std::alloc::alloc_zeroed(fs_layout) as *mut ffi::ext4_fs };
296
297    if fs_ptr.is_null() {
298        return Err(Error::InvalidConfig("failed to allocate filesystem structure".into()));
299    }
300
301    // Initialize block device
302    let ret = unsafe { ffi::ext4_block_init(&mut bdev) };
303    if ret != 0 {
304        unsafe { std::alloc::dealloc(fs_ptr as *mut u8, fs_layout) };
305        return Err(Error::from_lwext4(ret));
306    }
307
308    // Create the filesystem
309    let ret = unsafe { ffi::ext4_mkfs(fs_ptr, &mut bdev, &mut info, config.fs_type.to_c_int()) };
310
311    // Cleanup
312    unsafe {
313        ffi::ext4_block_fini(&mut bdev);
314        std::alloc::dealloc(fs_ptr as *mut u8, fs_layout);
315    }
316
317    if ret != 0 {
318        return Err(Error::from_lwext4(ret));
319    }
320
321    Ok(())
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327
328    #[test]
329    fn test_config_builder() {
330        let config = MkfsConfig::new()
331            .fs_type(FsType::Ext4)
332            .block_size(4096)
333            .label("test")
334            .journal(true);
335
336        assert_eq!(config.fs_type, FsType::Ext4);
337        assert_eq!(config.block_size, 4096);
338        assert_eq!(config.label, Some("test".to_string()));
339        assert!(config.journal);
340    }
341
342    #[test]
343    fn test_fs_type_to_c_int() {
344        assert_eq!(FsType::Ext2.to_c_int(), F_SET_EXT2);
345        assert_eq!(FsType::Ext3.to_c_int(), F_SET_EXT3);
346        assert_eq!(FsType::Ext4.to_c_int(), F_SET_EXT4);
347    }
348}