use crate::blockdev::{BlockDevice, BlockDeviceWrapper};
use crate::dir::Dir;
use crate::error::{check_errno, check_errno_with_path, Error, Result};
use crate::file::File;
use crate::types::{FileType, FsStats, Metadata, OpenFlags};
use ext4_lwext4_sys::{
ext4_atime_get, ext4_cache_flush, ext4_ctime_get, ext4_device_register, ext4_device_unregister,
ext4_dir_mk, ext4_dir_rm, ext4_flink, ext4_fremove, ext4_frename, ext4_fsymlink,
ext4_inode_exist, ext4_journal_start, ext4_journal_stop, ext4_mode_get, ext4_mode_set,
ext4_mount, ext4_mount_point_stats, ext4_mount_stats, ext4_mtime_get, ext4_owner_get,
ext4_owner_set, ext4_readlink, ext4_recover, ext4_umount,
};
use std::ffi::{c_char, CStr, CString};
use std::pin::Pin;
use std::sync::atomic::{AtomicU64, Ordering};
static DEVICE_COUNTER: AtomicU64 = AtomicU64::new(0);
pub struct Ext4Fs {
#[allow(dead_code)]
wrapper: Pin<Box<BlockDeviceWrapper>>,
device_name: CString,
mount_point: CString,
read_only: bool,
journal_active: bool,
}
impl Ext4Fs {
pub fn mount<B: BlockDevice + 'static>(device: B, read_only: bool) -> Result<Self> {
let id = DEVICE_COUNTER.fetch_add(1, Ordering::SeqCst);
let device_name = CString::new(format!("ext4dev{}", id)).unwrap();
let mount_point = CString::new(format!("/mp{}/", id)).unwrap();
let wrapper = BlockDeviceWrapper::new(device);
let ret = unsafe { ext4_device_register(wrapper.as_bdev_ptr(), device_name.as_ptr()) };
check_errno(ret)?;
let ret = unsafe { ext4_mount(device_name.as_ptr(), mount_point.as_ptr(), read_only) };
if ret != 0 {
unsafe { ext4_device_unregister(device_name.as_ptr()) };
return Err(Error::from(ret));
}
let ret = unsafe { ext4_recover(mount_point.as_ptr()) };
if ret != 0 {
}
let journal_active = if !read_only {
let ret = unsafe { ext4_journal_start(mount_point.as_ptr()) };
ret == 0
} else {
false
};
Ok(Self {
wrapper,
device_name,
mount_point,
read_only,
journal_active,
})
}
pub fn umount(self) -> Result<()> {
if self.journal_active {
unsafe { ext4_journal_stop(self.mount_point.as_ptr()) };
}
unsafe { ext4_cache_flush(self.mount_point.as_ptr()) };
let ret = unsafe { ext4_umount(self.mount_point.as_ptr()) };
check_errno(ret)?;
let ret = unsafe { ext4_device_unregister(self.device_name.as_ptr()) };
check_errno(ret)?;
Ok(())
}
#[allow(dead_code)]
pub(crate) fn mount_point(&self) -> &CStr {
&self.mount_point
}
pub(crate) fn make_path(&self, path: &str) -> Result<CString> {
let path = path.strip_prefix('/').unwrap_or(path);
let mount_point = self.mount_point.to_str().map_err(|_| {
Error::InvalidArgument("invalid mount point".to_string())
})?;
let full_path = format!("{}{}", mount_point, path);
CString::new(full_path).map_err(Error::from)
}
pub fn is_read_only(&self) -> bool {
self.read_only
}
pub fn stat(&self) -> Result<FsStats> {
let mut stats = ext4_mount_stats::default();
let ret = unsafe { ext4_mount_point_stats(self.mount_point.as_ptr(), &mut stats) };
check_errno(ret)?;
let volume_name = unsafe {
let name_bytes = &stats.volume_name;
let len = name_bytes.iter().position(|&c| c == 0).unwrap_or(16);
let slice = std::slice::from_raw_parts(name_bytes.as_ptr() as *const u8, len);
String::from_utf8_lossy(slice).into_owned()
};
Ok(FsStats {
block_size: stats.block_size,
total_blocks: stats.blocks_count,
free_blocks: stats.free_blocks_count,
total_inodes: stats.inodes_count as u64,
free_inodes: stats.free_inodes_count as u64,
block_group_count: stats.block_group_count,
blocks_per_group: stats.blocks_per_group,
inodes_per_group: stats.inodes_per_group,
volume_name,
})
}
pub fn open(&self, path: &str, flags: OpenFlags) -> Result<File<'_>> {
File::open(self, path, flags)
}
pub fn open_dir(&self, path: &str) -> Result<Dir<'_>> {
Dir::open(self, path)
}
pub fn mkdir(&self, path: &str, mode: u32) -> Result<()> {
if self.read_only {
return Err(Error::ReadOnly);
}
let full_path = self.make_path(path)?;
let ret = unsafe { ext4_dir_mk(full_path.as_ptr()) };
check_errno_with_path(ret, path)?;
if mode != 0 {
self.set_permissions(path, mode)?;
}
Ok(())
}
pub fn remove(&self, path: &str) -> Result<()> {
if self.read_only {
return Err(Error::ReadOnly);
}
let full_path = self.make_path(path)?;
let ret = unsafe { ext4_fremove(full_path.as_ptr()) };
check_errno_with_path(ret, path)
}
pub fn rmdir(&self, path: &str) -> Result<()> {
if self.read_only {
return Err(Error::ReadOnly);
}
let full_path = self.make_path(path)?;
let ret = unsafe { ext4_dir_rm(full_path.as_ptr()) };
check_errno_with_path(ret, path)
}
pub fn rename(&self, from: &str, to: &str) -> Result<()> {
if self.read_only {
return Err(Error::ReadOnly);
}
let from_path = self.make_path(from)?;
let to_path = self.make_path(to)?;
let ret = unsafe { ext4_frename(from_path.as_ptr(), to_path.as_ptr()) };
check_errno_with_path(ret, from)
}
pub fn link(&self, src: &str, dst: &str) -> Result<()> {
if self.read_only {
return Err(Error::ReadOnly);
}
let src_path = self.make_path(src)?;
let dst_path = self.make_path(dst)?;
let ret = unsafe { ext4_flink(src_path.as_ptr(), dst_path.as_ptr()) };
check_errno_with_path(ret, src)
}
pub fn symlink(&self, target: &str, path: &str) -> Result<()> {
if self.read_only {
return Err(Error::ReadOnly);
}
let target_cstr = CString::new(target)?;
let path_full = self.make_path(path)?;
let ret = unsafe { ext4_fsymlink(target_cstr.as_ptr(), path_full.as_ptr()) };
check_errno_with_path(ret, path)
}
pub fn readlink(&self, path: &str) -> Result<String> {
let full_path = self.make_path(path)?;
let mut buf = vec![0u8; 4096];
let mut rcnt: usize = 0;
let ret = unsafe {
ext4_readlink(
full_path.as_ptr(),
buf.as_mut_ptr() as *mut c_char,
buf.len(),
&mut rcnt,
)
};
check_errno_with_path(ret, path)?;
buf.truncate(rcnt);
String::from_utf8(buf).map_err(|_| Error::InvalidArgument("invalid UTF-8 in symlink".to_string()))
}
pub fn exists(&self, path: &str) -> bool {
self.metadata(path).is_ok()
}
pub fn is_file(&self, path: &str) -> bool {
let full_path = match self.make_path(path) {
Ok(p) => p,
Err(_) => return false,
};
unsafe { ext4_inode_exist(full_path.as_ptr(), FileType::RegularFile.to_raw() as i32) == 0 }
}
pub fn is_dir(&self, path: &str) -> bool {
let full_path = match self.make_path(path) {
Ok(p) => p,
Err(_) => return false,
};
unsafe { ext4_inode_exist(full_path.as_ptr(), FileType::Directory.to_raw() as i32) == 0 }
}
pub fn metadata(&self, path: &str) -> Result<Metadata> {
let full_path = self.make_path(path)?;
let mut mode: u32 = 0;
let ret = unsafe { ext4_mode_get(full_path.as_ptr(), &mut mode) };
check_errno_with_path(ret, path)?;
let file_type = match mode & 0o170000 {
0o100000 => FileType::RegularFile,
0o040000 => FileType::Directory,
0o120000 => FileType::Symlink,
0o060000 => FileType::BlockDevice,
0o020000 => FileType::CharDevice,
0o010000 => FileType::Fifo,
0o140000 => FileType::Socket,
_ => FileType::Unknown,
};
let mut uid: u32 = 0;
let mut gid: u32 = 0;
unsafe { ext4_owner_get(full_path.as_ptr(), &mut uid, &mut gid) };
let mut atime: u32 = 0;
let mut mtime: u32 = 0;
let mut ctime: u32 = 0;
unsafe {
ext4_atime_get(full_path.as_ptr(), &mut atime);
ext4_mtime_get(full_path.as_ptr(), &mut mtime);
ext4_ctime_get(full_path.as_ptr(), &mut ctime);
}
let size = if file_type == FileType::RegularFile {
if let Ok(file) = File::open(self, path, OpenFlags::READ) {
file.size()
} else {
0
}
} else {
0
};
Ok(Metadata {
file_type,
size,
blocks: 0, mode: mode & 0o7777, uid,
gid,
atime: atime as u64,
mtime: mtime as u64,
ctime: ctime as u64,
nlink: 1, })
}
pub fn set_permissions(&self, path: &str, mode: u32) -> Result<()> {
if self.read_only {
return Err(Error::ReadOnly);
}
let full_path = self.make_path(path)?;
let ret = unsafe { ext4_mode_set(full_path.as_ptr(), mode) };
check_errno_with_path(ret, path)
}
pub fn set_owner(&self, path: &str, uid: u32, gid: u32) -> Result<()> {
if self.read_only {
return Err(Error::ReadOnly);
}
let full_path = self.make_path(path)?;
let ret = unsafe { ext4_owner_set(full_path.as_ptr(), uid, gid) };
check_errno_with_path(ret, path)
}
pub fn sync(&self) -> Result<()> {
let ret = unsafe { ext4_cache_flush(self.mount_point.as_ptr()) };
check_errno(ret)
}
}
impl Drop for Ext4Fs {
fn drop(&mut self) {
if self.journal_active {
unsafe { ext4_journal_stop(self.mount_point.as_ptr()) };
}
unsafe {
ext4_cache_flush(self.mount_point.as_ptr());
ext4_umount(self.mount_point.as_ptr());
ext4_device_unregister(self.device_name.as_ptr());
}
}
}