fuchsia_fatfs/
lib.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::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
39/// Number of UCS-2 characters that fit in a VFAT LFN.
40/// Note that FAT doesn't support the full range of Unicode characters (UCS-2 is only 16 bits),
41/// and short file names can't encode the full 16-bit range of UCS-2.
42/// This is the minimum possible value. For instance, a 300 byte UTF-8 string could fit inside 255
43/// UCS-2 codepoints (if it had some 16 bit characters), but a 300 byte ASCII string would not fit.
44pub const MAX_FILENAME_LEN: u32 = 255;
45
46// An array used to initialize the FilesystemInfo |name| field. This just spells "fatfs" 0-padded to
47// 32 bytes.
48pub 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    /// Create a new FatFs using the given ReadWriteSeek as the disk.
63    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    /// Get the root directory of this filesystem.
87    /// The caller must call close() on the returned entry when it's finished with it.
88    pub fn get_root(&self) -> Result<Arc<FatDirectory>, Status> {
89        // Make sure it's open.
90        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    /// Shut down the filesystem.
108    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    /// Helper class for creating a filesystem layout on a FAT disk programatically.
132    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        /// Create a new, empty directory.
145        pub fn dir() -> Self {
146            TestDiskContents::Dir(HashMap::new())
147        }
148
149        /// Add a new child to this directory.
150        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        /// Add this TestDiskContents to the given fatfs Dir
159        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            // Unfortunately, there is no way to verify from the server side, so we use
190            // the fuchsia.io protocol to check everything is as expected.
191            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                    // TODO(simonshields): we should check that no other files exist, but
224                    // GetDirents() is going to be a pain to deal with.
225
226                    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    /// Helper class for creating an empty FAT-formatted VMO.
252    pub struct TestFatDisk {
253        fs: FileSystem,
254    }
255
256    impl TestFatDisk {
257        /// Create an empty disk with size at least |size| bytes.
258        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        /// Get the root directory (as a fatfs Dir).
272        pub fn root_dir<'a>(&'a self) -> Dir<'a> {
273            self.fs.root_dir()
274        }
275
276        /// Convert this TestFatDisk into a FatFs for testing against.
277        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] // TODO(https://fxbug.dev/42133844): Clean up tasks to prevent panic on drop in FatfsFileRef
296    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        // FAT doesn't give the root directory a created/modified/access time,
320        // so this is a good way to check that we return valid dates for a "zero" date.
321        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}