#![deny(missing_docs)]
#![cfg_attr(feature = "unstable", feature(maybe_uninit_uninit_array))]
#![cfg_attr(feature = "unstable", feature(maybe_uninit_array_assume_init))]
#![cfg_attr(feature = "unstable", feature(io_error_more))]
mod inner;
use inner::{Ext2Filesystem, Inode};
type IoResult<T> = std::result::Result<T, Errno>;
use libc::{blksize_t, c_void, ino_t, off_t, time_t};
use nix::dir::Entry;
use nix::errno::Errno;
use nix::sys::stat::{Mode, SFlag};
use nix::unistd::{Gid, Uid};
use std::io::{Error, ErrorKind, Read, Result, Seek, SeekFrom, Write};
use std::mem::MaybeUninit;
use std::mem::{size_of, transmute};
use std::os::unix::ffi::OsStrExt;
use std::path::{Component, Path};
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
#[derive(Debug)]
pub struct Ext2<T: Read + Seek + Write>(Arc<Mutex<Ext2Filesystem<T>>>);
impl<T> Clone for Ext2<T>
where
T: Read + Seek + Write,
{
fn clone(&self) -> Self {
Ext2(self.0.clone())
}
}
pub fn open_ext2_drive<T>(disk: T) -> Result<Ext2<T>>
where
T: Read + Seek + Write,
{
Ext2::new(disk)
}
impl<T> Ext2<T>
where
T: Read + Seek + Write,
{
pub fn new(disk: T) -> Result<Self> {
Ok(Self(Arc::new(Mutex::new(
Ext2Filesystem::new(disk).map_err(|e| Error::from_raw_os_error(e as i32))?,
))))
}
pub fn create<P: AsRef<Path>>(&mut self, path: P) -> Result<File<T>> {
OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(path.as_ref(), self.clone())
}
pub fn open<P: AsRef<Path>>(&mut self, path: P) -> Result<File<T>> {
OpenOptions::new()
.read(true)
.open(path.as_ref(), self.clone())
}
pub fn read_dir<P: AsRef<Path>>(&self, path: P) -> Result<Vec<Entry>> {
let path = get_path(&path)?;
let ext2 = self.0.lock().unwrap();
let iter = _lookup_directory(&ext2, path)?;
let type_field = ext2.get_superblock().directory_entry_contain_type_field();
use inner::DirectoryEntryType::*;
Ok(iter
.enumerate()
.map(move |(i, entry)| {
let dirent = libc::dirent {
d_ino: entry.directory.header.inode as u64,
d_off: i as i64,
d_reclen: size_of::<libc::dirent>() as u16,
d_type: match type_field {
true => match entry.directory.header.type_indicator {
RegularFile => libc::DT_REG,
Directory => libc::DT_DIR,
CharacterDevice => libc::DT_CHR,
BlockDevice => libc::DT_BLK,
Fifo => libc::DT_FIFO,
Socket => libc::DT_SOCK,
SymbolicLink => libc::DT_LNK,
},
false => libc::DT_UNKNOWN,
},
d_name: entry.directory.filename.0,
};
unsafe { transmute::<_, Entry>(dirent) } })
.collect())
}
pub fn create_dir<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
let path = get_path(&path)?;
let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
let parent = path.parent().ok_or_else(|| ErrorKind::AlreadyExists)?;
let filename: &str = path.file_name().unwrap().to_str().unwrap();
let mut ext2 = self.0.lock().unwrap();
let iter = _lookup_directory(&ext2, &parent)?;
let parent = iter.fold(Ok(None), |res, entry| {
if entry.directory.filename == filename.try_into().unwrap() {
return Err(ErrorKind::AlreadyExists);
}
res.map(|opt| {
opt.or({
if unsafe { entry.directory.get_filename() == "." } {
Some(entry)
} else {
None
}
})
})
})?;
let parent_inode_nbr = parent.unwrap().directory.header.inode;
ext2.create_dir(
parent_inode_nbr,
filename,
timestamp as u32,
def_mode() | Mode::S_IXUSR | Mode::S_IXOTH | Mode::S_IXGRP,
(
nix::unistd::geteuid().as_raw(),
nix::unistd::getegid().as_raw(),
),
)?;
Ok(())
}
pub fn remove_dir<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
let path = get_path(&path)?;
let mut ext2 = self.0.lock().unwrap();
let iter = _lookup_directory(&ext2, path)?;
let parent = iter.enumerate().fold(Ok(None), |res, (idx, entry)| {
if idx > 1 {
#[cfg(unstable)]
return Err(ErrorKind::DirectoryNotEmpty);
#[cfg(not(unstable))]
return Err(ErrorKind::PermissionDenied);
}
res.map(|opt| {
opt.or({
if unsafe { entry.directory.get_filename() == ".." } {
Some(entry)
} else {
None
}
})
})
})?;
match path.file_name() {
Some(filename) => Ok(ext2.rmdir(
parent.unwrap().directory.get_inode(),
filename.to_str().unwrap(),
)?),
None => Err(ErrorKind::AlreadyExists.into()),
}
}
pub fn chmod<P: AsRef<Path>>(&mut self, path: P, mode: Mode) -> Result<()> {
let path = get_path(&path)?;
let mut ext2 = self.0.lock().unwrap();
match _find_entry(&ext2, path)? {
Some(entry) => Ok(ext2.chmod(entry.directory.get_inode(), mode)?),
None => Err(ErrorKind::NotFound.into()),
}
}
pub fn chown<P: AsRef<Path>>(&mut self, path: P, owner: Uid, group: Gid) -> Result<()> {
let path = get_path(&path)?;
let mut ext2 = self.0.lock().unwrap();
match _find_entry(&ext2, path)? {
Some(entry) => {
Ok(ext2.chown(entry.directory.get_inode(), owner.into(), group.into())?)
}
None => Err(ErrorKind::NotFound.into()),
}
}
pub fn stat<P: AsRef<Path>>(&self, path: P) -> Result<libc::stat> {
let path = get_path(&path)?;
let ext2 = self.0.lock().unwrap();
match _find_entry(&ext2, path)? {
Some(entry) => Ok(_stat(&ext2, entry.directory.get_inode(), entry.inode)?),
None => Err(ErrorKind::NotFound.into()),
}
}
pub fn remove_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
let path = get_path(&path)?;
#[cfg(unstable)]
path.parent().ok_or_else(|| ErrorKind::IsADirectory)?;
#[cfg(not(unstable))]
path.parent().ok_or_else(|| ErrorKind::PermissionDenied)?;
let mut ext2 = self.0.lock().unwrap();
let parent = _find_entry(&ext2, path.parent().unwrap())?;
let parent_inode_nbr = parent.unwrap().directory.header.inode;
Ok(ext2.unlink(
parent_inode_nbr,
path.file_name().unwrap().to_str().unwrap(),
true,
)?)
}
pub fn utime<P: AsRef<Path>>(&mut self, path: P, time: Option<&libc::utimbuf>) -> Result<()> {
let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
let path = get_path(&path)?;
let mut ext2 = self.0.lock().unwrap();
match _find_entry(&ext2, path)? {
Some(entry) => Ok(ext2.utime(entry.directory.get_inode(), time, timestamp as u32)?),
None => Err(ErrorKind::NotFound.into()),
}
}
pub fn rename<P: AsRef<Path>>(&mut self, path: P, new_path: P) -> Result<()> {
let path = get_path(&path)?;
let new_path = get_path(&new_path)?;
match (path.parent(), new_path.parent()) {
(Some(parent), Some(new_parent)) => {
let mut ext2 = self.0.lock().unwrap();
if let Ok(Some(_)) = _find_entry(&ext2, new_path) {
return Err(ErrorKind::AlreadyExists.into());
}
let child = _find_entry(&ext2, parent)?;
match child {
Some(child) => {
let new_parent = _find_entry(&ext2, new_parent)?;
Ok(ext2.rename(
child.directory.get_inode(),
path.file_name().unwrap().to_str().unwrap(),
new_parent.unwrap().directory.get_inode(),
new_path.file_name().unwrap().to_str().unwrap(),
)?)
}
None => Err(ErrorKind::NotFound.into()),
}
}
_ => Err(ErrorKind::Unsupported.into()),
}
}
pub fn link<P: AsRef<Path>>(&mut self, target_path: P, link_path: P) -> Result<()> {
let target_path = get_path(&target_path)?;
let link_path = get_path(&link_path)?;
match link_path.parent() {
Some(link_parent) => {
let mut ext2 = self.0.lock().unwrap();
if let Ok(Some(_)) = _find_entry(&ext2, link_path) {
return Err(ErrorKind::AlreadyExists.into());
}
let target_entry = _find_entry(&ext2, target_path)?;
match target_entry {
Some(target_entry) => {
let parent_link = _find_entry(&ext2, link_parent)?;
ext2.link(
parent_link.unwrap().directory.get_inode(),
target_entry.directory.get_inode(),
link_path.file_name().unwrap().to_str().unwrap(),
)?;
Ok(())
}
None => Err(ErrorKind::NotFound.into()),
}
}
_ => Err(ErrorKind::Unsupported.into()),
}
}
pub fn symlink<P: AsRef<Path>>(&mut self, target_path: P, link_path: P) -> Result<()> {
let link_path = get_path(&link_path)?;
let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
match link_path.parent() {
Some(link_parent) => {
let mut ext2 = self.0.lock().unwrap();
if let Ok(Some(_)) = _find_entry(&ext2, link_path) {
return Err(ErrorKind::AlreadyExists.into());
}
let parent_link_entry = _find_entry(&ext2, link_parent)?;
ext2.symlink(
parent_link_entry.unwrap().directory.get_inode(),
target_path.as_ref().to_str().unwrap(),
link_path.file_name().unwrap().to_str().unwrap(),
timestamp as u32,
)?;
Ok(())
}
_ => Err(ErrorKind::Unsupported.into()),
}
}
}
fn def_mode() -> Mode {
Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IRGRP | Mode::S_IROTH
}
fn get_path<'a, P: AsRef<Path>>(path: &'a P) -> Result<&'a Path> {
let path = path.as_ref();
for component in path.components() {
if component == Component::ParentDir {
return Err(ErrorKind::Unsupported.into());
}
}
match path.is_absolute() {
true => Ok(path),
false => Err(ErrorKind::Unsupported.into()),
}
}
fn _find_entry<T>(ext2: &Ext2Filesystem<T>, path: &Path) -> Result<Option<inner::Entry>>
where
T: Read + Seek + Write,
{
Ok(match path.parent() {
Some(parent) => {
let mut iter = _lookup_directory(ext2, parent)?;
iter.find(|entry| unsafe { entry.directory.get_filename() } == path.file_name().unwrap().to_str().unwrap())
}
None => {
let mut iter = _lookup_directory(ext2, path)?;
iter.find(|entry| unsafe { entry.directory.get_filename() } == ".")
}
})
}
fn _lookup_directory<'a, T>(
ext2: &'a Ext2Filesystem<T>,
path: &Path,
) -> Result<impl Iterator<Item = inner::Entry> + 'a>
where
T: Read + Seek + Write,
{
debug_assert_eq!(path.is_absolute(), true);
let mut iter = ext2.lookup_directory(2).expect("Root mut be a directory");
for directory in path.components() {
if directory == Component::RootDir {
continue;
} else {
let elem = iter.find(|entry| {
let filelen = directory.as_os_str().len();
unsafe {
match libc::memcmp(
entry.directory.filename.0.as_ptr() as *const c_void,
directory.as_os_str().as_bytes().as_ptr() as *const c_void,
filelen,
) {
0 => true,
_ => false,
}
}
});
match elem {
None => return Err(ErrorKind::NotFound.into()),
Some(entry) => {
let inode = entry.directory.get_inode();
iter = ext2.lookup_directory(inode)?;
}
}
}
}
Ok(iter)
}
fn _stat<T>(ext2: &Ext2Filesystem<T>, inode_nbr: u32, inode: Inode) -> Result<libc::stat>
where
T: Read + Seek + Write,
{
let mut stat = MaybeUninit::<libc::stat>::zeroed();
let ptr = stat.as_mut_ptr();
unsafe {
(*ptr).st_dev = 0; (*ptr).st_ino = inode_nbr as ino_t;
(*ptr).st_mode = inode.type_and_perm.0 as u32; (*ptr).st_nlink = inode.nbr_hard_links as u64; (*ptr).st_uid = inode.user_id as u32; (*ptr).st_gid = inode.group_id as u32; (*ptr).st_rdev = 0; (*ptr).st_size = inode.low_size as off_t; (*ptr).st_atime = inode.last_access_time as time_t;
(*ptr).st_mtime = inode.last_modification_time as time_t;
(*ptr).st_ctime = inode.creation_time as time_t;
(*ptr).st_blksize = ext2.get_block_size() as blksize_t;
(*ptr).st_blocks = inode.nbr_disk_sectors as i64; (*ptr).st_atime_nsec = 0;
(*ptr).st_mtime_nsec = 0;
(*ptr).st_ctime_nsec = 0;
Ok(stat.assume_init())
}
}
#[derive(Debug, Copy, Clone)]
pub struct OpenOptions {
read: bool,
write: bool,
create: bool,
append: bool,
truncate: bool,
}
impl OpenOptions {
pub fn new() -> Self {
OpenOptions {
read: false,
write: false,
create: false,
append: false,
truncate: false,
}
}
pub fn read(&mut self, read: bool) -> &mut Self {
self.read = read;
self
}
pub fn write(&mut self, write: bool) -> &mut Self {
self.write = write;
self
}
pub fn create(&mut self, create: bool) -> &mut Self {
self.create = create;
self
}
pub fn append(&mut self, append: bool) -> &mut Self {
self.append = append;
self
}
pub fn truncate(&mut self, truncate: bool) -> &mut Self {
self.truncate = truncate;
self
}
pub fn open<T, P: AsRef<Path>>(&mut self, path: P, ext2_clone: Ext2<T>) -> Result<File<T>>
where
T: Read + Seek + Write,
{
let path = get_path(&path)?;
#[cfg(unstable)]
path.parent().ok_or_else(|| ErrorKind::IsADirectory)?;
#[cfg(not(unstable))]
path.parent().ok_or_else(|| ErrorKind::PermissionDenied)?;
let mut ext2 = ext2_clone.0.lock().unwrap();
let file = _find_entry(&ext2, path)?;
match file {
Some(file) => {
if file.inode.is_a_directory() {
#[cfg(unstable)]
return Err(ErrorKind::IsADirectory.into());
#[cfg(not(unstable))]
Err(ErrorKind::PermissionDenied.into())
} else {
if self.truncate && self.write {
ext2.truncate(file.directory.get_inode(), 0)?;
}
let curr_offset = if self.append && self.write {
ext2.read_inode(file.directory.get_inode())?.get_size() as i64
} else {
0
};
drop(ext2);
Ok(File {
inode: file.directory.get_inode(),
curr_offset: curr_offset as u64,
ext2: ext2_clone,
options: *self,
})
}
}
None => {
if self.create && self.write {
let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
let parent = _find_entry(&ext2, path.parent().unwrap())?;
let entry = ext2.create(
path.file_name().unwrap().to_str().unwrap(),
parent.unwrap().directory.get_inode(),
timestamp as u32,
(def_mode().bits(), SFlag::S_IFREG).try_into().unwrap(),
(
nix::unistd::geteuid().as_raw(),
nix::unistd::getegid().as_raw(),
),
)?;
drop(ext2);
Ok(File {
inode: entry.directory.get_inode(),
curr_offset: 0,
ext2: ext2_clone,
options: *self,
})
} else {
Err(ErrorKind::NotFound.into())
}
}
}
}
}
#[derive(Debug)]
pub struct File<T>
where
T: Read + Seek + Write,
{
inode: u32,
curr_offset: u64,
ext2: Ext2<T>,
options: OpenOptions,
}
impl<T> File<T>
where
T: Read + Seek + Write,
{
pub fn metadata() {
unimplemented!();
}
}
impl<T> Seek for File<T>
where
T: Read + Seek + Write,
{
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
let ext2 = self.ext2.0.lock().unwrap();
let file_len = ext2.read_inode(self.inode)?.get_size() as i64;
match pos {
SeekFrom::Start(u) => {
if u > file_len as u64 {
return Err(ErrorKind::UnexpectedEof.into());
}
self.curr_offset = u;
}
SeekFrom::End(i) => {
if i < 0 || i > file_len {
return Err(ErrorKind::UnexpectedEof.into());
}
self.curr_offset = (file_len - i) as u64;
}
SeekFrom::Current(i) => {
let new_curr_offset = self.curr_offset as i64 + i;
if new_curr_offset < 0 || new_curr_offset > file_len {
return Err(ErrorKind::UnexpectedEof.into());
}
self.curr_offset = new_curr_offset as u64;
}
}
Ok(self.curr_offset)
}
}
impl<T> Write for File<T>
where
T: Read + Seek + Write,
{
fn write(&mut self, buf: &[u8]) -> Result<usize> {
if !self.options.write {
return Err(ErrorKind::PermissionDenied.into());
}
let mut ext2 = self.ext2.0.lock().unwrap();
Ok(ext2
.write(self.inode, &mut self.curr_offset, buf)
.map(|s| s.0 as usize)?)
}
fn flush(&mut self) -> Result<()> {
Ok(())
}
}
impl<T> Read for File<T>
where
T: Read + Seek + Write,
{
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
if !self.options.read {
return Err(ErrorKind::PermissionDenied.into());
}
let mut ext2 = self.ext2.0.lock().unwrap();
Ok(ext2
.read(self.inode, &mut self.curr_offset, buf)
.map(|s| s as usize)?)
}
}