use crate::filesystem::FatFilesystem;
use crate::node::Node;
use anyhow::Error;
use fatfs::FsOptions;
use fidl_fuchsia_fs::{AdminRequest, AdminShutdownResponder};
use std::pin::Pin;
use std::sync::Arc;
use vfs::directory::entry::DirectoryEntry;
use vfs::directory::entry_container::Directory;
use vfs::execution_scope::ExecutionScope;
use zx::Status;
mod directory;
mod file;
mod filesystem;
mod node;
mod refs;
mod types;
mod util;
pub use directory::FatDirectory;
pub use util::fatfs_error_to_status;
#[cfg(fuzz)]
mod fuzzer;
#[cfg(fuzz)]
use fuzz::fuzz;
#[cfg(fuzz)]
#[fuzz]
fn fuzz_fatfs(fs: &[u8]) {
fuzzer::fuzz_fatfs(fs);
}
pub use types::Disk;
pub const MAX_FILENAME_LEN: u32 = 255;
pub const FATFS_INFO_NAME: [i8; 32] = [
0x66, 0x61, 0x74, 0x66, 0x73, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
];
pub trait RootDirectory: DirectoryEntry + Directory {}
impl<T: DirectoryEntry + Directory> RootDirectory for T {}
pub struct FatFs {
inner: Pin<Arc<FatFilesystem>>,
root: Arc<FatDirectory>,
}
impl FatFs {
pub fn new(disk: Box<dyn Disk>) -> Result<Self, Error> {
let (inner, root) = FatFilesystem::new(disk, FsOptions::new())?;
Ok(FatFs { inner, root })
}
#[cfg(test)]
pub fn from_filesystem(inner: Pin<Arc<FatFilesystem>>, root: Arc<FatDirectory>) -> Self {
FatFs { inner, root }
}
#[cfg(any(test, fuzz))]
pub fn get_fatfs_root(&self) -> Arc<FatDirectory> {
self.root.clone()
}
pub fn filesystem(&self) -> &FatFilesystem {
return &self.inner;
}
pub fn is_present(&self) -> bool {
self.inner.lock().unwrap().with_disk(|disk| disk.is_present())
}
pub fn get_root(&self) -> Result<Arc<FatDirectory>, Status> {
self.root.open_ref(&self.inner.lock().unwrap())?;
Ok(self.root.clone())
}
pub async fn handle_admin(
&self,
scope: &ExecutionScope,
req: AdminRequest,
) -> Option<AdminShutdownResponder> {
match req {
AdminRequest::Shutdown { responder } => {
scope.shutdown();
Some(responder)
}
}
}
pub fn shut_down(&self) -> Result<(), Status> {
let mut fs = self.inner.lock().unwrap();
self.root.shut_down(&fs)?;
fs.shut_down()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{Dir, FileSystem};
use anyhow::{anyhow, Context};
use fatfs::{format_volume, FormatVolumeOptions};
use fidl::endpoints::Proxy;
use fidl_fuchsia_io as fio;
use futures::future::BoxFuture;
use futures::prelude::*;
use std::collections::HashMap;
use std::io::Write;
use std::ops::Deref;
use vfs::node::Node;
use vfs::path::Path;
#[derive(Debug, PartialEq)]
pub enum TestDiskContents {
File(String),
Dir(HashMap<String, TestDiskContents>),
}
impl From<&str> for TestDiskContents {
fn from(string: &str) -> Self {
TestDiskContents::File(string.to_owned())
}
}
impl TestDiskContents {
pub fn dir() -> Self {
TestDiskContents::Dir(HashMap::new())
}
pub fn add_child(mut self, name: &str, child: Self) -> Self {
match &mut self {
TestDiskContents::Dir(map) => map.insert(name.to_owned(), child),
_ => panic!("Can't add to a file"),
};
self
}
pub fn create(&self, dir: &Dir<'_>) {
match self {
TestDiskContents::File(_) => {
panic!("Can't have the root directory be a file!");
}
TestDiskContents::Dir(map) => {
for (name, value) in map.iter() {
value.create_fs_structure(&name, dir);
}
}
};
}
fn create_fs_structure(&self, name: &str, dir: &Dir<'_>) {
match self {
TestDiskContents::File(content) => {
let mut file = dir.create_file(name).expect("Creating file to succeed");
file.truncate().expect("Truncate to succeed");
file.write_all(content.as_bytes()).expect("Write to succeed");
}
TestDiskContents::Dir(map) => {
let new_dir = dir.create_dir(name).expect("Creating directory to succeed");
for (name, value) in map.iter() {
value.create_fs_structure(&name, &new_dir);
}
}
};
}
pub fn verify(&self, remote: fio::NodeProxy) -> BoxFuture<'_, Result<(), Error>> {
match self {
TestDiskContents::File(content) => {
let remote = fio::FileProxy::new(remote.into_channel().unwrap());
let mut file_contents: Vec<u8> = Vec::with_capacity(content.len());
return async move {
loop {
let mut vec = remote
.read(content.len() as u64)
.await
.context("Read failed")?
.map_err(Status::from_raw)
.context("Read error")?;
if vec.len() == 0 {
break;
}
file_contents.append(&mut vec);
}
if file_contents.as_slice() != content.as_bytes() {
return Err(anyhow!(
"File contents mismatch: expected {}, got {}",
content,
String::from_utf8_lossy(&file_contents)
));
}
Ok(())
}
.boxed();
}
TestDiskContents::Dir(map) => {
let remote = fio::DirectoryProxy::new(remote.into_channel().unwrap());
return async move {
for (name, value) in map.iter() {
let (proxy, server_end) =
fidl::endpoints::create_proxy::<fio::NodeMarker>();
remote
.open(
fio::OpenFlags::RIGHT_READABLE,
fio::ModeType::empty(),
name,
server_end,
)
.context("Sending open failed")?;
value
.verify(proxy)
.await
.with_context(|| format!("Verifying {}", name))?;
}
Ok(())
}
.boxed();
}
}
}
}
pub struct TestFatDisk {
fs: FileSystem,
}
impl TestFatDisk {
pub fn empty_disk(size: u64) -> Self {
let mut buffer: Vec<u8> = Vec::with_capacity(size as usize);
buffer.resize(size as usize, 0);
let cursor = std::io::Cursor::new(buffer.as_mut_slice());
format_volume(cursor, FormatVolumeOptions::new()).expect("format volume to succeed");
let wrapper: Box<dyn Disk> = Box::new(std::io::Cursor::new(buffer));
TestFatDisk {
fs: fatfs::FileSystem::new(wrapper, FsOptions::new())
.expect("creating FS to succeed"),
}
}
pub fn root_dir<'a>(&'a self) -> Dir<'a> {
self.fs.root_dir()
}
pub fn into_fatfs(self) -> FatFs {
self.fs.flush().unwrap();
let (filesystem, root_dir) = FatFilesystem::from_filesystem(self.fs);
FatFs::from_filesystem(filesystem, root_dir)
}
}
impl Deref for TestFatDisk {
type Target = FileSystem;
fn deref(&self) -> &Self::Target {
&self.fs
}
}
const TEST_DISK_SIZE: u64 = 2048 << 10;
#[fuchsia::test]
#[ignore] async fn test_create_disk() {
let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
let structure = TestDiskContents::dir()
.add_child("test", "This is a test file".into())
.add_child("empty_folder", TestDiskContents::dir());
structure.create(&disk.root_dir());
let fatfs = disk.into_fatfs();
let scope = ExecutionScope::new();
let (proxy, remote) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
let root = fatfs.get_root().expect("get_root OK");
root.clone().open(scope, fio::OpenFlags::RIGHT_READABLE, Path::dot(), remote);
root.close();
structure.verify(proxy).await.expect("Verify succeeds");
}
#[fuchsia::test]
fn test_unset_date() {
let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
let root = disk.root_dir();
let epoch = fatfs::DateTime {
date: fatfs::Date { year: 1980, month: 1, day: 1 },
time: fatfs::Time { hour: 0, min: 0, sec: 0, millis: 0 },
};
assert_eq!(root.created(), epoch);
assert_eq!(root.modified(), epoch);
assert_eq!(root.accessed(), epoch.date);
}
}