1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub enum FsType {
13 Ext2,
15 Ext3,
17 #[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#[derive(Debug, Clone)]
34pub struct MkfsConfig {
35 pub fs_type: FsType,
37 pub block_size: u32,
39 pub label: Option<String>,
41 pub uuid: Option<[u8; 16]>,
43 pub journal: bool,
45 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, inode_size: 256,
58 }
59 }
60}
61
62impl MkfsConfig {
63 pub fn new() -> Self {
65 Self::default()
66 }
67
68 pub fn fs_type(mut self, fs_type: FsType) -> Self {
70 self.fs_type = fs_type;
71 self
72 }
73
74 pub fn block_size(mut self, size: u32) -> Self {
76 self.block_size = size;
77 self
78 }
79
80 pub fn label(mut self, label: impl Into<String>) -> Self {
82 self.label = Some(label.into());
83 self
84 }
85
86 pub fn uuid(mut self, uuid: [u8; 16]) -> Self {
88 self.uuid = Some(uuid);
89 self
90 }
91
92 pub fn journal(mut self, enable: bool) -> Self {
94 self.journal = enable;
95 self
96 }
97
98 pub fn inode_size(mut self, size: u32) -> Self {
100 self.inode_size = size;
101 self
102 }
103}
104
105struct BlockDevContext {
107 device: Box<dyn BlockDevice>,
108 buffer: Vec<u8>,
109}
110
111unsafe 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
124unsafe 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
137unsafe 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
159unsafe 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
181const MIN_FS_SIZE: u64 = 1024 * 1024;
183
184pub 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 if total_size < MIN_FS_SIZE {
211 return Err(Error::DeviceTooSmall {
212 min: MIN_FS_SIZE,
213 actual: total_size,
214 });
215 }
216
217 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 let buffer = vec![0u8; block_size as usize];
227
228 let mut ctx = Box::new(BlockDevContext {
230 device: Box::new(device),
231 buffer,
232 });
233
234 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 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 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, inodes_per_group: 0, inode_size: config.inode_size,
276 inodes: 0, journal_blocks: 0, 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 let fs_size = 4096; 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 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 let ret = unsafe { ffi::ext4_mkfs(fs_ptr, &mut bdev, &mut info, config.fs_type.to_c_int()) };
310
311 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}