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