use crate::block_device::BlockDevice;
use crate::{Error, Result};
use ext4_mkfs_sys::{self as ffi, F_SET_EXT2, F_SET_EXT3, F_SET_EXT4};
use std::ffi::CString;
use std::os::raw::{c_int, c_void};
use std::ptr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FsType {
Ext2,
Ext3,
#[default]
Ext4,
}
impl FsType {
fn to_c_int(self) -> c_int {
match self {
FsType::Ext2 => F_SET_EXT2,
FsType::Ext3 => F_SET_EXT3,
FsType::Ext4 => F_SET_EXT4,
}
}
}
#[derive(Debug, Clone)]
pub struct MkfsConfig {
pub fs_type: FsType,
pub block_size: u32,
pub label: Option<String>,
pub uuid: Option<[u8; 16]>,
pub journal: bool,
pub inode_size: u32,
}
impl Default for MkfsConfig {
fn default() -> Self {
Self {
fs_type: FsType::Ext4,
block_size: 4096,
label: None,
uuid: None,
journal: false, inode_size: 256,
}
}
}
impl MkfsConfig {
pub fn new() -> Self {
Self::default()
}
pub fn fs_type(mut self, fs_type: FsType) -> Self {
self.fs_type = fs_type;
self
}
pub fn block_size(mut self, size: u32) -> Self {
self.block_size = size;
self
}
pub fn label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn uuid(mut self, uuid: [u8; 16]) -> Self {
self.uuid = Some(uuid);
self
}
pub fn journal(mut self, enable: bool) -> Self {
self.journal = enable;
self
}
pub fn inode_size(mut self, size: u32) -> Self {
self.inode_size = size;
self
}
}
struct BlockDevContext {
device: Box<dyn BlockDevice>,
buffer: Vec<u8>,
}
unsafe extern "C" fn bdev_open(bdev: *mut ffi::ext4_blockdev) -> c_int {
let ctx = (*(*bdev).bdif).p_user as *mut BlockDevContext;
if ctx.is_null() {
return -1;
}
match (*ctx).device.open() {
Ok(_) => 0,
Err(_) => -1,
}
}
unsafe extern "C" fn bdev_close(bdev: *mut ffi::ext4_blockdev) -> c_int {
let ctx = (*(*bdev).bdif).p_user as *mut BlockDevContext;
if ctx.is_null() {
return -1;
}
match (*ctx).device.close() {
Ok(_) => 0,
Err(_) => -1,
}
}
unsafe extern "C" fn bdev_bread(
bdev: *mut ffi::ext4_blockdev,
buf: *mut c_void,
blk_id: u64,
blk_cnt: u32,
) -> c_int {
let ctx = (*(*bdev).bdif).p_user as *mut BlockDevContext;
if ctx.is_null() || buf.is_null() {
return -1;
}
let block_size = (*(*bdev).bdif).ph_bsize as usize;
let size = blk_cnt as usize * block_size;
let slice = std::slice::from_raw_parts_mut(buf as *mut u8, size);
match (*ctx).device.read_blocks(slice, blk_id, blk_cnt) {
Ok(_) => 0,
Err(_) => -1,
}
}
unsafe extern "C" fn bdev_bwrite(
bdev: *mut ffi::ext4_blockdev,
buf: *const c_void,
blk_id: u64,
blk_cnt: u32,
) -> c_int {
let ctx = (*(*bdev).bdif).p_user as *mut BlockDevContext;
if ctx.is_null() || buf.is_null() {
return -1;
}
let block_size = (*(*bdev).bdif).ph_bsize as usize;
let size = blk_cnt as usize * block_size;
let slice = std::slice::from_raw_parts(buf as *const u8, size);
match (*ctx).device.write_blocks(slice, blk_id, blk_cnt) {
Ok(_) => 0,
Err(_) => -1,
}
}
const MIN_FS_SIZE: u64 = 1024 * 1024;
pub fn mkfs<D: BlockDevice + 'static>(device: D, config: MkfsConfig) -> Result<()> {
let block_size = device.block_size();
let block_count = device.block_count();
let total_size = block_size as u64 * block_count;
if total_size < MIN_FS_SIZE {
return Err(Error::DeviceTooSmall {
min: MIN_FS_SIZE,
actual: total_size,
});
}
if ![1024, 2048, 4096].contains(&config.block_size) {
return Err(Error::InvalidConfig(format!(
"block size must be 1024, 2048, or 4096, got {}",
config.block_size
)));
}
let buffer = vec![0u8; block_size as usize];
let mut ctx = Box::new(BlockDevContext {
device: Box::new(device),
buffer,
});
let mut bdif = ffi::ext4_blockdev_iface {
open: Some(bdev_open),
bread: Some(bdev_bread),
bwrite: Some(bdev_bwrite),
close: Some(bdev_close),
lock: None,
unlock: None,
ph_bsize: block_size,
ph_bcnt: block_count,
ph_bbuf: ctx.buffer.as_mut_ptr(),
ph_refctr: 0,
bread_ctr: 0,
bwrite_ctr: 0,
p_user: ctx.as_mut() as *mut BlockDevContext as *mut c_void,
};
let mut bdev = ffi::ext4_blockdev {
bdif: &mut bdif,
part_offset: 0,
part_size: total_size,
bc: ptr::null_mut(),
lg_bsize: 0,
lg_bcnt: 0,
cache_write_back: 0,
fs: ptr::null_mut(),
journal: ptr::null_mut(),
};
let label_cstring = config.label.as_ref().map(|s| {
let truncated: String = s.chars().take(16).collect();
CString::new(truncated).unwrap_or_else(|_| CString::new("").unwrap())
});
let mut info = ffi::ext4_mkfs_info {
len: total_size,
block_size: config.block_size,
blocks_per_group: 0, inodes_per_group: 0, inode_size: config.inode_size,
inodes: 0, journal_blocks: 0, feat_ro_compat: 0,
feat_compat: 0,
feat_incompat: 0,
bg_desc_reserve_blocks: 0,
dsc_size: 0,
uuid: config.uuid.unwrap_or([0u8; 16]),
journal: config.journal,
label: label_cstring
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(ptr::null()),
};
let fs_size = 4096; let fs_layout = std::alloc::Layout::from_size_align(fs_size, 8).unwrap();
let fs_ptr = unsafe { std::alloc::alloc_zeroed(fs_layout) as *mut ffi::ext4_fs };
if fs_ptr.is_null() {
return Err(Error::InvalidConfig("failed to allocate filesystem structure".into()));
}
let ret = unsafe { ffi::ext4_block_init(&mut bdev) };
if ret != 0 {
unsafe { std::alloc::dealloc(fs_ptr as *mut u8, fs_layout) };
return Err(Error::from_lwext4(ret));
}
let ret = unsafe { ffi::ext4_mkfs(fs_ptr, &mut bdev, &mut info, config.fs_type.to_c_int()) };
unsafe {
ffi::ext4_block_fini(&mut bdev);
std::alloc::dealloc(fs_ptr as *mut u8, fs_layout);
}
if ret != 0 {
return Err(Error::from_lwext4(ret));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_builder() {
let config = MkfsConfig::new()
.fs_type(FsType::Ext4)
.block_size(4096)
.label("test")
.journal(true);
assert_eq!(config.fs_type, FsType::Ext4);
assert_eq!(config.block_size, 4096);
assert_eq!(config.label, Some("test".to_string()));
assert!(config.journal);
}
#[test]
fn test_fs_type_to_c_int() {
assert_eq!(FsType::Ext2.to_c_int(), F_SET_EXT2);
assert_eq!(FsType::Ext3.to_c_int(), F_SET_EXT3);
assert_eq!(FsType::Ext4.to_c_int(), F_SET_EXT4);
}
}