1use crate::filesystem::FatFilesystem;
5use crate::node::Node;
6use anyhow::Error;
7use fatfs::FsOptions;
8use fidl_fuchsia_fs::{AdminRequest, AdminShutdownResponder};
9use std::rc::Rc;
10use std::sync::Arc;
11use vfs::directory::entry::DirectoryEntry;
12use vfs::directory::entry_container::Directory;
13use vfs::execution_scope::ExecutionScope;
14use zx::Status;
15
16pub mod component;
17mod directory;
18mod file;
19mod filesystem;
20mod node;
21mod refs;
22mod types;
23mod util;
24
25pub use directory::FatDirectory;
26pub use util::fatfs_error_to_status;
27
28#[cfg(fuzz)]
29mod fuzzer;
30#[cfg(fuzz)]
31use fuzz::fuzz;
32#[cfg(fuzz)]
33#[fuzz]
34fn fuzz_fatfs(fs: &[u8]) {
35 fuzzer::fuzz_fatfs(fs);
36}
37
38pub use types::Disk;
39
40pub const MAX_FILENAME_LEN: u32 = 255;
46
47pub const FATFS_INFO_NAME: [i8; 32] = [
50 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,
51 0, 0, 0, 0, 0,
52];
53
54pub trait RootDirectory: DirectoryEntry + Directory {}
55impl<T: DirectoryEntry + Directory> RootDirectory for T {}
56
57pub struct FatFs {
58 root: Arc<FatDirectory>,
59}
60
61impl FatFs {
62 pub fn new(disk: Box<dyn Disk>, scope: ExecutionScope) -> Result<Self, Error> {
64 let (_inner, root) = FatFilesystem::new(disk, FsOptions::new(), scope)?;
65 Ok(FatFs { root })
66 }
67
68 #[cfg(test)]
69 pub fn from_filesystem(root: Arc<FatDirectory>) -> Self {
70 FatFs { root }
71 }
72
73 #[cfg(any(test, fuzz))]
74 pub fn get_fatfs_root(&self) -> Arc<FatDirectory> {
75 self.root.clone()
76 }
77
78 pub fn filesystem(&self) -> &FatFilesystem {
79 self.root.fs()
80 }
81
82 pub fn is_present(&self) -> bool {
83 self.filesystem().with_disk(|disk| disk.is_present())
84 }
85
86 pub fn get_root(&self) -> Result<Arc<FatDirectory>, Status> {
89 self.root.open_ref()?;
91 Ok(self.root.clone())
92 }
93
94 pub fn handle_admin(
95 &self,
96 scope: &ExecutionScope,
97 req: AdminRequest,
98 ) -> Option<AdminShutdownResponder> {
99 match req {
100 AdminRequest::Shutdown { responder } => {
101 scope.shutdown();
102 Some(responder)
103 }
104 }
105 }
106
107 pub fn shut_down(self) -> Result<(), Status> {
122 let FatFs { root } = self;
123 let inner = root.fs().clone();
124 root.shut_down()?;
125 drop(root);
126 let fs = Rc::into_inner(inner).ok_or(Status::BAD_STATE)?;
127 fs.shut_down()
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::types::{Dir, FileSystem};
135 use anyhow::{Context, anyhow};
136 use fatfs::{FormatVolumeOptions, format_volume};
137 use fidl::endpoints::Proxy;
138 use fidl_fuchsia_io as fio;
139 use futures::future::BoxFuture;
140 use futures::prelude::*;
141 use std::collections::HashMap;
142 use std::io::Write;
143 use std::ops::Deref;
144 use vfs::node::Node;
145
146 #[derive(Debug, PartialEq)]
147 pub enum TestDiskContents {
149 File(String),
150 Dir(HashMap<String, TestDiskContents>),
151 }
152
153 impl From<&str> for TestDiskContents {
154 fn from(string: &str) -> Self {
155 TestDiskContents::File(string.to_owned())
156 }
157 }
158
159 impl TestDiskContents {
160 pub fn dir() -> Self {
162 TestDiskContents::Dir(HashMap::new())
163 }
164
165 pub fn add_child(mut self, name: &str, child: Self) -> Self {
167 match &mut self {
168 TestDiskContents::Dir(map) => map.insert(name.to_owned(), child),
169 _ => panic!("Can't add to a file"),
170 };
171 self
172 }
173
174 pub fn create(&self, dir: &Dir<'_>) {
176 match self {
177 TestDiskContents::File(_) => {
178 panic!("Can't have the root directory be a file!");
179 }
180 TestDiskContents::Dir(map) => {
181 for (name, value) in map.iter() {
182 value.create_fs_structure(&name, dir);
183 }
184 }
185 };
186 }
187
188 fn create_fs_structure(&self, name: &str, dir: &Dir<'_>) {
189 match self {
190 TestDiskContents::File(content) => {
191 let mut file = dir.create_file(name).expect("Creating file to succeed");
192 file.truncate().expect("Truncate to succeed");
193 file.write_all(content.as_bytes()).expect("Write to succeed");
194 }
195 TestDiskContents::Dir(map) => {
196 let new_dir = dir.create_dir(name).expect("Creating directory to succeed");
197 for (name, value) in map.iter() {
198 value.create_fs_structure(&name, &new_dir);
199 }
200 }
201 };
202 }
203
204 pub fn verify(&self, remote: fio::NodeProxy) -> BoxFuture<'_, Result<(), Error>> {
205 match self {
208 TestDiskContents::File(content) => {
209 let remote = fio::FileProxy::new(remote.into_channel().unwrap());
210 let mut file_contents: Vec<u8> = Vec::with_capacity(content.len());
211
212 return async move {
213 loop {
214 let mut vec = remote
215 .read(content.len() as u64)
216 .await
217 .context("Read failed")?
218 .map_err(Status::from_raw)
219 .context("Read error")?;
220 if vec.len() == 0 {
221 break;
222 }
223 file_contents.append(&mut vec);
224 }
225
226 if file_contents.as_slice() != content.as_bytes() {
227 return Err(anyhow!(
228 "File contents mismatch: expected {}, got {}",
229 content,
230 String::from_utf8_lossy(&file_contents)
231 ));
232 }
233 Ok(())
234 }
235 .boxed();
236 }
237 TestDiskContents::Dir(map) => {
238 let remote = fio::DirectoryProxy::new(remote.into_channel().unwrap());
239 return async move {
243 for (name, value) in map.iter() {
244 let (proxy, server_end) =
245 fidl::endpoints::create_proxy::<fio::NodeMarker>();
246 remote
247 .open(
248 name,
249 fio::PERM_READABLE,
250 &Default::default(),
251 server_end.into_channel(),
252 )
253 .context("Sending open failed")?;
254 value
255 .verify(proxy)
256 .await
257 .with_context(|| format!("Verifying {}", name))?;
258 }
259 Ok(())
260 }
261 .boxed();
262 }
263 }
264 }
265 }
266
267 pub struct TestFatDisk {
269 fs: FileSystem,
270 }
271
272 impl TestFatDisk {
273 pub fn empty_disk(size: u64) -> Self {
275 let mut buffer: Vec<u8> = Vec::with_capacity(size as usize);
276 buffer.resize(size as usize, 0);
277 let cursor = std::io::Cursor::new(buffer.as_mut_slice());
278
279 format_volume(cursor, FormatVolumeOptions::new()).expect("format volume to succeed");
280 let wrapper: Box<dyn Disk> = Box::new(std::io::Cursor::new(buffer));
281 TestFatDisk {
282 fs: fatfs::FileSystem::new(wrapper, FsOptions::new())
283 .expect("creating FS to succeed"),
284 }
285 }
286
287 pub fn root_dir<'a>(&'a self) -> Dir<'a> {
289 self.fs.root_dir()
290 }
291
292 pub fn into_fatfs(self) -> FatFs {
294 self.fs.flush().unwrap();
295 let (_filesystem, root_dir) = FatFilesystem::from_filesystem(self.fs);
296 FatFs::from_filesystem(root_dir)
297 }
298 }
299
300 impl Deref for TestFatDisk {
301 type Target = FileSystem;
302
303 fn deref(&self) -> &Self::Target {
304 &self.fs
305 }
306 }
307
308 const TEST_DISK_SIZE: u64 = 2048 << 10;
309
310 #[fuchsia::test]
311 #[ignore] async fn test_create_disk() {
313 let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
314
315 let structure = TestDiskContents::dir()
316 .add_child("test", "This is a test file".into())
317 .add_child("empty_folder", TestDiskContents::dir());
318
319 structure.create(&disk.root_dir());
320
321 let fatfs = disk.into_fatfs();
322 let root = fatfs.get_root().unwrap();
323 let proxy = vfs::directory::serve_read_only(root.clone(), ExecutionScope::new());
324 root.close();
325
326 structure
327 .verify(fio::NodeProxy::from_channel(proxy.into_channel().unwrap().into()))
328 .await
329 .expect("Verify succeeds");
330 }
331
332 #[fuchsia::test]
333 fn test_unset_date() {
334 let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
335 let root = disk.root_dir();
338 let epoch = fatfs::DateTime {
339 date: fatfs::Date { year: 1980, month: 1, day: 1 },
340 time: fatfs::Time { hour: 0, min: 0, sec: 0, millis: 0 },
341 };
342 assert_eq!(root.created(), epoch);
343 assert_eq!(root.modified(), epoch);
344 assert_eq!(root.accessed(), epoch.date);
345 }
346}