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, Timer};
13use std::cell::RefCell;
14use std::rc::Rc;
15use std::sync::Arc;
16use vfs::execution_scope::ExecutionScope;
17use zx::{Event, MonotonicDuration, Status};
18
19pub struct FatFilesystem {
20 filesystem: FileSystem,
21 dirty_task: RefCell<Option<MonotonicInstant>>,
22 fs_id: Event,
23 scope: ExecutionScope,
24}
25
26impl FatFilesystem {
27 pub fn fatfs_root_dir(&self) -> Dir<'_> {
29 self.filesystem.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.with_disk(func)
37 }
38
39 pub fn cluster_size(&self) -> u32 {
40 self.filesystem.cluster_size()
41 }
42
43 pub fn total_clusters(&self) -> Result<u32, Status> {
44 Ok(self.filesystem.stats().map_err(fatfs_error_to_status)?.total_clusters())
45 }
46
47 pub fn free_clusters(&self) -> Result<u32, Status> {
48 Ok(self.filesystem.stats().map_err(fatfs_error_to_status)?.free_clusters())
49 }
50
51 pub fn new(
53 disk: Box<dyn Disk>,
54 options: FsOptions<DefaultTimeProvider, LossyOemCpConverter>,
55 scope: ExecutionScope,
56 ) -> Result<(Rc<Self>, Arc<FatDirectory>), Error> {
57 let filesystem = fatfs::FileSystem::new(disk, options)?;
58 let result = Rc::new(FatFilesystem {
59 filesystem,
60 dirty_task: RefCell::new(None),
61 fs_id: Event::create(),
62 scope,
63 });
64 Ok((result.clone(), result.root_dir()))
65 }
66
67 #[cfg(test)]
68 pub fn from_filesystem(filesystem: FileSystem) -> (Rc<Self>, Arc<FatDirectory>) {
69 let result = Rc::new(FatFilesystem {
70 filesystem,
71 dirty_task: RefCell::new(None),
72 fs_id: Event::create(),
73 scope: ExecutionScope::new(),
74 });
75 (result.clone(), result.root_dir())
76 }
77
78 pub fn fs_id(&self) -> &Event {
79 &self.fs_id
80 }
81
82 pub fn scope(&self) -> &ExecutionScope {
83 &self.scope
84 }
85
86 fn root_dir(self: Rc<Self>) -> Arc<FatDirectory> {
91 let dir = FatfsDirRef::empty(self);
93 FatDirectory::new(dir, None, "/".to_owned())
94 }
95
96 pub fn shut_down(self) -> Result<(), Status> {
97 self.filesystem.unmount().map_err(fatfs_error_to_status)
98 }
99
100 pub fn mark_dirty(self: &Rc<Self>) {
103 let deadline = MonotonicInstant::after(MonotonicDuration::from_seconds(1));
104 match &mut *self.dirty_task.borrow_mut() {
105 Some(time) => *time = deadline,
106 x @ None => {
107 *x = Some(deadline);
108 let this = Rc::downgrade(self);
109 self.scope.spawn_local(async move {
110 loop {
111 let deadline;
112 {
113 let this_rc = match this.upgrade() {
114 Some(a) => a,
115 None => return,
116 };
117 let mut task = this_rc.dirty_task.borrow_mut();
118 if let Some(t) = task.as_ref() {
119 deadline = *t;
120 } else {
121 break;
122 }
123 if MonotonicInstant::now() >= deadline {
124 *task = None;
125 break;
126 }
127 }
128 Timer::new(deadline).await;
129 }
130 if let Some(this_rc) = this.upgrade() {
131 let _ = this_rc.filesystem.flush();
132 }
133 });
134 }
135 }
136 }
137
138 pub fn query_filesystem(&self) -> Result<fio::FilesystemInfo, Status> {
139 let cluster_size = self.cluster_size() as u64;
140 let total_clusters = self.total_clusters()? as u64;
141 let free_clusters = self.free_clusters()? as u64;
142 let total_bytes = cluster_size * total_clusters;
143 let used_bytes = cluster_size * (total_clusters - free_clusters);
144
145 Ok(fio::FilesystemInfo {
146 total_bytes,
147 used_bytes,
148 total_nodes: 0,
149 used_nodes: 0,
150 free_shared_pool_bytes: 0,
151 fs_id: self.fs_id().koid()?.raw_koid(),
152 block_size: cluster_size as u32,
153 max_filename_size: MAX_FILENAME_LEN,
154 fs_type: fidl_fuchsia_fs::VfsType::Fatfs.into_primitive(),
155 padding: 0,
156 name: FATFS_INFO_NAME,
157 })
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164 use crate::node::Node;
165 use crate::tests::{TestDiskContents, TestFatDisk};
166 use fidl::endpoints::Proxy;
167 use scopeguard::defer;
168
169 const TEST_DISK_SIZE: u64 = 2048 << 10; #[fuchsia::test]
172 #[ignore] async fn test_automatic_flush() {
174 let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
175 let structure = TestDiskContents::dir().add_child("test", "Hello".into());
176 structure.create(&disk.root_dir());
177
178 let fs = disk.into_fatfs();
179 let dir = fs.get_fatfs_root();
180 dir.open_ref().unwrap();
181 defer! { dir.close_ref() };
182
183 let proxy = vfs::serve_file(
184 dir.clone(),
185 vfs::Path::validate_and_split("test").unwrap(),
186 vfs::execution_scope::ExecutionScope::new(),
187 fio::PERM_READABLE | fio::PERM_WRITABLE,
188 );
189 assert!(fs.filesystem().dirty_task.borrow().is_none());
190 let file = fio::FileProxy::new(proxy.into_channel().unwrap());
191 file.write("hello there".as_bytes()).await.unwrap().map_err(Status::err_from_raw).unwrap();
192 {
193 let fs_inner = fs.filesystem();
194 assert!(fs_inner.filesystem.is_dirty());
196 }
197 Timer::new(MonotonicInstant::after(MonotonicDuration::from_millis(1500))).await;
199 {
200 let fs_inner = fs.filesystem();
201 assert_eq!(fs_inner.filesystem.is_dirty(), false);
202 }
203 }
204}