Skip to main content

fuchsia_fatfs/
filesystem.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4use 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    /// Get the root fatfs Dir.
28    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    /// Create a new FatFilesystem.
52    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    /// Get the FatDirectory that represents the root directory of this filesystem.
87    /// Note this should only be called once per filesystem, otherwise multiple conflicting
88    /// FatDirectories will exist.
89    /// We only call it from new() and from_filesystem().
90    fn root_dir(self: Rc<Self>) -> Arc<FatDirectory> {
91        // We start with an empty FatfsDirRef and an open_count of zero.
92        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    /// Mark the filesystem as dirty. This will cause the disk to automatically be flushed after
101    /// one second, and cancel any previous pending flushes.
102    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; // 2048K
170
171    #[fuchsia::test]
172    #[ignore] // TODO(https://fxbug.dev/42133844): Clean up tasks to prevent panic on drop in FatfsFileRef
173    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            // fs should be dirty until the timer expires.
195            assert!(fs_inner.filesystem.is_dirty());
196        }
197        // Wait some time for the flush to happen.
198        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}