fuchsia_fatfs/
filesystem.rs1use crate::directory::FatDirectory;
5use crate::refs::FatfsDirRef;
6use crate::types::{Dir, Disk, FileSystem};
7use crate::util::fatfs_error_to_status;
8use crate::{FATFS_INFO_NAME, MAX_FILENAME_LEN};
9use anyhow::Error;
10use fatfs::{DefaultTimeProvider, FsOptions, LossyOemCpConverter};
11use fidl_fuchsia_io as fio;
12use fuchsia_async::{MonotonicInstant, Task, Timer};
13use fuchsia_sync::{Mutex, MutexGuard};
14use std::marker::PhantomPinned;
15use std::pin::Pin;
16use std::sync::Arc;
17use zx::{AsHandleRef, Event, MonotonicDuration, Status};
18
19pub struct FatFilesystemInner {
20 filesystem: Option<FileSystem>,
21 _pinned: PhantomPinned,
24}
25
26impl FatFilesystemInner {
27 pub fn root_dir(&self) -> Dir<'_> {
29 self.filesystem.as_ref().unwrap().root_dir()
30 }
31
32 pub fn with_disk<F, T>(&self, func: F) -> T
33 where
34 F: FnOnce(&Box<dyn Disk>) -> T,
35 {
36 self.filesystem.as_ref().unwrap().with_disk(func)
37 }
38
39 pub fn shut_down(&mut self) -> Result<(), Status> {
40 self.filesystem.take().ok_or(Status::BAD_STATE)?.unmount().map_err(fatfs_error_to_status)
41 }
42
43 pub fn cluster_size(&self) -> u32 {
44 self.filesystem.as_ref().map_or(0, |f| f.cluster_size())
45 }
46
47 pub fn total_clusters(&self) -> Result<u32, Status> {
48 Ok(self
49 .filesystem
50 .as_ref()
51 .ok_or(Status::BAD_STATE)?
52 .stats()
53 .map_err(fatfs_error_to_status)?
54 .total_clusters())
55 }
56
57 pub fn free_clusters(&self) -> Result<u32, Status> {
58 Ok(self
59 .filesystem
60 .as_ref()
61 .ok_or(Status::BAD_STATE)?
62 .stats()
63 .map_err(fatfs_error_to_status)?
64 .free_clusters())
65 }
66
67 pub fn sector_size(&self) -> Result<u16, Status> {
68 Ok(self
69 .filesystem
70 .as_ref()
71 .ok_or(Status::BAD_STATE)?
72 .stats()
73 .map_err(fatfs_error_to_status)?
74 .sector_size())
75 }
76}
77
78pub struct FatFilesystem {
79 inner: Mutex<FatFilesystemInner>,
80 dirty_task: Mutex<Option<(MonotonicInstant, Task<()>)>>,
81 fs_id: Event,
82}
83
84impl FatFilesystem {
85 pub fn new(
87 disk: Box<dyn Disk>,
88 options: FsOptions<DefaultTimeProvider, LossyOemCpConverter>,
89 ) -> Result<(Pin<Arc<Self>>, Arc<FatDirectory>), Error> {
90 let inner = Mutex::new(FatFilesystemInner {
91 filesystem: Some(fatfs::FileSystem::new(disk, options)?),
92 _pinned: PhantomPinned,
93 });
94 let result =
95 Arc::pin(FatFilesystem { inner, dirty_task: Mutex::new(None), fs_id: Event::create() });
96 Ok((result.clone(), result.root_dir()))
97 }
98
99 #[cfg(test)]
100 pub fn from_filesystem(filesystem: FileSystem) -> (Pin<Arc<Self>>, Arc<FatDirectory>) {
101 let inner =
102 Mutex::new(FatFilesystemInner { filesystem: Some(filesystem), _pinned: PhantomPinned });
103 let result =
104 Arc::pin(FatFilesystem { inner, dirty_task: Mutex::new(None), fs_id: Event::create() });
105 (result.clone(), result.root_dir())
106 }
107
108 pub fn fs_id(&self) -> &Event {
109 &self.fs_id
110 }
111
112 fn root_dir(self: Pin<Arc<Self>>) -> Arc<FatDirectory> {
117 let dir = FatfsDirRef::empty();
119 FatDirectory::new(dir, None, self, "/".to_owned())
120 }
121
122 pub fn lock(&self) -> MutexGuard<'_, FatFilesystemInner> {
124 self.inner.lock()
125 }
126
127 pub fn mark_dirty(self: &Pin<Arc<Self>>) {
130 let deadline = MonotonicInstant::after(MonotonicDuration::from_seconds(1));
131 match &mut *self.dirty_task.lock() {
132 Some((time, _)) => *time = deadline,
133 x @ None => {
134 let this = self.clone();
135 *x = Some((
136 deadline,
137 Task::spawn(async move {
138 loop {
139 let deadline;
140 {
141 let mut task = this.dirty_task.lock();
142 deadline = task.as_ref().unwrap().0;
143 if MonotonicInstant::now() >= deadline {
144 *task = None;
145 break;
146 }
147 }
148 Timer::new(deadline).await;
149 }
150 let _ = this.lock().filesystem.as_ref().map(|f| f.flush());
151 }),
152 ));
153 }
154 }
155 }
156
157 pub fn query_filesystem(&self) -> Result<fio::FilesystemInfo, Status> {
158 let fs_lock = self.lock();
159
160 let cluster_size = fs_lock.cluster_size() as u64;
161 let total_clusters = fs_lock.total_clusters()? as u64;
162 let free_clusters = fs_lock.free_clusters()? as u64;
163 let total_bytes = cluster_size * total_clusters;
164 let used_bytes = cluster_size * (total_clusters - free_clusters);
165
166 Ok(fio::FilesystemInfo {
167 total_bytes,
168 used_bytes,
169 total_nodes: 0,
170 used_nodes: 0,
171 free_shared_pool_bytes: 0,
172 fs_id: self.fs_id().get_koid()?.raw_koid(),
173 block_size: cluster_size as u32,
174 max_filename_size: MAX_FILENAME_LEN,
175 fs_type: fidl_fuchsia_fs::VfsType::Fatfs.into_primitive(),
176 padding: 0,
177 name: FATFS_INFO_NAME,
178 })
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use crate::node::Node;
186 use crate::tests::{TestDiskContents, TestFatDisk};
187 use fidl::endpoints::Proxy;
188 use scopeguard::defer;
189 use vfs::directory::entry_container::Directory;
190 use vfs::execution_scope::ExecutionScope;
191 use vfs::path::Path;
192
193 const TEST_DISK_SIZE: u64 = 2048 << 10; #[fuchsia::test]
196 #[ignore] async fn test_automatic_flush() {
198 let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
199 let structure = TestDiskContents::dir().add_child("test", "Hello".into());
200 structure.create(&disk.root_dir());
201
202 let fs = disk.into_fatfs();
203 let dir = fs.get_fatfs_root();
204 dir.open_ref(&fs.filesystem().lock()).unwrap();
205 defer! { dir.close_ref(&fs.filesystem().lock()) };
206
207 let scope = ExecutionScope::new();
208 let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
209 dir.clone().open(
210 scope.clone(),
211 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE,
212 Path::validate_and_split("test").unwrap(),
213 server_end,
214 );
215
216 assert!(fs.filesystem().dirty_task.lock().is_none());
217 let file = fio::FileProxy::new(proxy.into_channel().unwrap());
218 file.write("hello there".as_bytes()).await.unwrap().map_err(Status::from_raw).unwrap();
219 {
220 let fs_lock = fs.filesystem().lock();
221 assert!(fs_lock.filesystem.as_ref().unwrap().is_dirty());
223 }
224 Timer::new(MonotonicInstant::after(MonotonicDuration::from_millis(1500))).await;
227 {
228 let fs_lock = fs.filesystem().lock();
229 assert_eq!(fs_lock.filesystem.as_ref().unwrap().is_dirty(), false);
230 }
231 }
232}