use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};
pub mod ext;
pub mod fat;
pub mod rootdevs;
pub use rootdevs::{DeviceEntry, RootDevs};
#[derive(Debug, Clone, Copy)]
pub struct FileMeta {
pub mode: u16,
pub uid: u32,
pub gid: u32,
pub mtime: u32,
pub atime: u32,
pub ctime: u32,
}
impl Default for FileMeta {
fn default() -> Self {
Self {
mode: 0o644,
uid: 0,
gid: 0,
mtime: 0,
atime: 0,
ctime: 0,
}
}
}
impl FileMeta {
pub fn with_mode(mode: u16) -> Self {
Self {
mode,
..Self::default()
}
}
}
pub enum FileSource {
HostPath(PathBuf),
Reader {
reader: Box<dyn ReadSeek + Send>,
len: u64,
},
Zero(u64),
}
impl FileSource {
pub fn len(&self) -> io::Result<u64> {
match self {
FileSource::HostPath(p) => Ok(std::fs::metadata(p)?.len()),
FileSource::Reader { len, .. } => Ok(*len),
FileSource::Zero(n) => Ok(*n),
}
}
pub fn is_empty(&self) -> io::Result<bool> {
self.len().map(|n| n == 0)
}
pub fn open(self) -> io::Result<(Box<dyn ReadSeek + Send>, u64)> {
match self {
FileSource::HostPath(p) => {
let f = File::open(&p)?;
let len = f.metadata()?.len();
Ok((Box::new(f), len))
}
FileSource::Reader { reader, len } => Ok((reader, len)),
FileSource::Zero(n) => Ok((Box::new(ZeroReader { remaining: n }), n)),
}
}
}
pub trait ReadSeek: Read + Seek {}
impl<T: Read + Seek> ReadSeek for T {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeviceKind {
Char,
Block,
Fifo,
Socket,
}
#[derive(Debug, Clone)]
pub struct DirEntry {
pub name: String,
pub inode: u32,
pub kind: EntryKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntryKind {
Regular,
Dir,
Symlink,
Char,
Block,
Fifo,
Socket,
Unknown,
}
pub trait Filesystem: Sized {
type FormatOpts;
fn format(
dev: &mut dyn crate::block::BlockDevice,
opts: &Self::FormatOpts,
) -> crate::Result<Self>;
fn open(dev: &mut dyn crate::block::BlockDevice) -> crate::Result<Self>;
fn create_file(
&mut self,
dev: &mut dyn crate::block::BlockDevice,
path: &Path,
src: FileSource,
meta: FileMeta,
) -> crate::Result<()>;
fn create_dir(
&mut self,
dev: &mut dyn crate::block::BlockDevice,
path: &Path,
meta: FileMeta,
) -> crate::Result<()>;
fn create_symlink(
&mut self,
dev: &mut dyn crate::block::BlockDevice,
path: &Path,
target: &Path,
meta: FileMeta,
) -> crate::Result<()>;
fn create_device(
&mut self,
dev: &mut dyn crate::block::BlockDevice,
path: &Path,
kind: DeviceKind,
major: u32,
minor: u32,
meta: FileMeta,
) -> crate::Result<()>;
fn remove(&mut self, dev: &mut dyn crate::block::BlockDevice, path: &Path)
-> crate::Result<()>;
fn list(
&mut self,
dev: &mut dyn crate::block::BlockDevice,
path: &Path,
) -> crate::Result<Vec<DirEntry>>;
fn read_file<'a>(
&'a mut self,
dev: &'a mut dyn crate::block::BlockDevice,
path: &Path,
) -> crate::Result<Box<dyn Read + 'a>>;
fn flush(&mut self, dev: &mut dyn crate::block::BlockDevice) -> crate::Result<()>;
}
struct ZeroReader {
remaining: u64,
}
impl Read for ZeroReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.remaining == 0 {
return Ok(0);
}
let n = (buf.len() as u64).min(self.remaining) as usize;
buf[..n].fill(0);
self.remaining -= n as u64;
Ok(n)
}
}
impl Seek for ZeroReader {
fn seek(&mut self, _pos: SeekFrom) -> io::Result<u64> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"ZeroReader does not support seeking",
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_source_streams_in_chunks() {
let src = FileSource::Zero(10_000);
let (mut reader, len) = src.open().unwrap();
assert_eq!(len, 10_000);
let mut total = 0;
let mut buf = [0u8; 4096];
loop {
let n = reader.read(&mut buf).unwrap();
if n == 0 {
break;
}
assert!(buf[..n].iter().all(|&b| b == 0));
total += n;
}
assert_eq!(total, 10_000);
}
#[test]
fn host_path_source_length_matches_file() {
use tempfile::NamedTempFile;
let mut f = NamedTempFile::new().unwrap();
std::io::Write::write_all(f.as_file_mut(), b"hello world").unwrap();
let src = FileSource::HostPath(f.path().to_path_buf());
assert_eq!(src.len().unwrap(), 11);
}
}