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    use vfs::path::Path;
130
131    #[derive(Debug, PartialEq)]
132    /// Helper class for creating a filesystem layout on a FAT disk programatically.
133    pub enum TestDiskContents {
134        File(String),
135        Dir(HashMap<String, TestDiskContents>),
136    }
137
138    impl From<&str> for TestDiskContents {
139        fn from(string: &str) -> Self {
140            TestDiskContents::File(string.to_owned())
141        }
142    }
143
144    impl TestDiskContents {
145        /// Create a new, empty directory.
146        pub fn dir() -> Self {
147            TestDiskContents::Dir(HashMap::new())
148        }
149
150        /// Add a new child to this directory.
151        pub fn add_child(mut self, name: &str, child: Self) -> Self {
152            match &mut self {
153                TestDiskContents::Dir(map) => map.insert(name.to_owned(), child),
154                _ => panic!("Can't add to a file"),
155            };
156            self
157        }
158
159        /// Add this TestDiskContents to the given fatfs Dir
160        pub fn create(&self, dir: &Dir<'_>) {
161            match self {
162                TestDiskContents::File(_) => {
163                    panic!("Can't have the root directory be a file!");
164                }
165                TestDiskContents::Dir(map) => {
166                    for (name, value) in map.iter() {
167                        value.create_fs_structure(&name, dir);
168                    }
169                }
170            };
171        }
172
173        fn create_fs_structure(&self, name: &str, dir: &Dir<'_>) {
174            match self {
175                TestDiskContents::File(content) => {
176                    let mut file = dir.create_file(name).expect("Creating file to succeed");
177                    file.truncate().expect("Truncate to succeed");
178                    file.write_all(content.as_bytes()).expect("Write to succeed");
179                }
180                TestDiskContents::Dir(map) => {
181                    let new_dir = dir.create_dir(name).expect("Creating directory to succeed");
182                    for (name, value) in map.iter() {
183                        value.create_fs_structure(&name, &new_dir);
184                    }
185                }
186            };
187        }
188
189        pub fn verify(&self, remote: fio::NodeProxy) -> BoxFuture<'_, Result<(), Error>> {
190            // Unfortunately, there is no way to verify from the server side, so we use
191            // the fuchsia.io protocol to check everything is as expected.
192            match self {
193                TestDiskContents::File(content) => {
194                    let remote = fio::FileProxy::new(remote.into_channel().unwrap());
195                    let mut file_contents: Vec<u8> = Vec::with_capacity(content.len());
196
197                    return async move {
198                        loop {
199                            let mut vec = remote
200                                .read(content.len() as u64)
201                                .await
202                                .context("Read failed")?
203                                .map_err(Status::from_raw)
204                                .context("Read error")?;
205                            if vec.len() == 0 {
206                                break;
207                            }
208                            file_contents.append(&mut vec);
209                        }
210
211                        if file_contents.as_slice() != content.as_bytes() {
212                            return Err(anyhow!(
213                                "File contents mismatch: expected {}, got {}",
214                                content,
215                                String::from_utf8_lossy(&file_contents)
216                            ));
217                        }
218                        Ok(())
219                    }
220                    .boxed();
221                }
222                TestDiskContents::Dir(map) => {
223                    let remote = fio::DirectoryProxy::new(remote.into_channel().unwrap());
224                    // TODO(simonshields): we should check that no other files exist, but
225                    // GetDirents() is going to be a pain to deal with.
226
227                    return async move {
228                        for (name, value) in map.iter() {
229                            let (proxy, server_end) =
230                                fidl::endpoints::create_proxy::<fio::NodeMarker>();
231                            remote
232                                .deprecated_open(
233                                    fio::OpenFlags::RIGHT_READABLE,
234                                    fio::ModeType::empty(),
235                                    name,
236                                    server_end,
237                                )
238                                .context("Sending open failed")?;
239                            value
240                                .verify(proxy)
241                                .await
242                                .with_context(|| format!("Verifying {}", name))?;
243                        }
244                        Ok(())
245                    }
246                    .boxed();
247                }
248            }
249        }
250    }
251
252    /// Helper class for creating an empty FAT-formatted VMO.
253    pub struct TestFatDisk {
254        fs: FileSystem,
255    }
256
257    impl TestFatDisk {
258        /// Create an empty disk with size at least |size| bytes.
259        pub fn empty_disk(size: u64) -> Self {
260            let mut buffer: Vec<u8> = Vec::with_capacity(size as usize);
261            buffer.resize(size as usize, 0);
262            let cursor = std::io::Cursor::new(buffer.as_mut_slice());
263
264            format_volume(cursor, FormatVolumeOptions::new()).expect("format volume to succeed");
265            let wrapper: Box<dyn Disk> = Box::new(std::io::Cursor::new(buffer));
266            TestFatDisk {
267                fs: fatfs::FileSystem::new(wrapper, FsOptions::new())
268                    .expect("creating FS to succeed"),
269            }
270        }
271
272        /// Get the root directory (as a fatfs Dir).
273        pub fn root_dir<'a>(&'a self) -> Dir<'a> {
274            self.fs.root_dir()
275        }
276
277        /// Convert this TestFatDisk into a FatFs for testing against.
278        pub fn into_fatfs(self) -> FatFs {
279            self.fs.flush().unwrap();
280            let (filesystem, root_dir) = FatFilesystem::from_filesystem(self.fs);
281            FatFs::from_filesystem(filesystem, root_dir)
282        }
283    }
284
285    impl Deref for TestFatDisk {
286        type Target = FileSystem;
287
288        fn deref(&self) -> &Self::Target {
289            &self.fs
290        }
291    }
292
293    const TEST_DISK_SIZE: u64 = 2048 << 10;
294
295    #[fuchsia::test]
296    #[ignore] // TODO(https://fxbug.dev/42133844): Clean up tasks to prevent panic on drop in FatfsFileRef
297    async fn test_create_disk() {
298        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
299
300        let structure = TestDiskContents::dir()
301            .add_child("test", "This is a test file".into())
302            .add_child("empty_folder", TestDiskContents::dir());
303
304        structure.create(&disk.root_dir());
305
306        let fatfs = disk.into_fatfs();
307        let scope = ExecutionScope::new();
308        let (proxy, remote) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
309        let root = fatfs.get_root().expect("get_root OK");
310        root.clone().open(scope, fio::OpenFlags::RIGHT_READABLE, Path::dot(), remote);
311        root.close();
312
313        structure.verify(proxy).await.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}