fuchsia_fatfs/
file.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.
4
5use crate::directory::FatDirectory;
6use crate::filesystem::{FatFilesystem, FatFilesystemInner};
7use crate::node::Node;
8use crate::refs::FatfsFileRef;
9use crate::types::File;
10use crate::util::{
11    dos_date_to_unix_time, dos_to_unix_time, fatfs_error_to_status, unix_to_dos_time,
12};
13use fidl_fuchsia_io as fio;
14use fuchsia_sync::RwLock;
15use std::cell::UnsafeCell;
16use std::fmt::Debug;
17use std::io::{Read, Seek, Write};
18use std::pin::Pin;
19use std::sync::Arc;
20use vfs::attributes;
21use vfs::directory::entry::EntryInfo;
22use vfs::file::{File as VfsFile, FileIo as VfsFileIo, FileOptions, SyncMode};
23use zx::{self as zx, Status};
24
25fn extend(file: &mut File<'_>, mut current: u64, target: u64) -> Result<(), Status> {
26    let zeros = vec![0; 8192];
27    while current < target {
28        let to_do = (std::cmp::min(target, (current + 8192) / 8192 * 8192) - current) as usize;
29        let written = file.write(&zeros[..to_do]).map_err(fatfs_error_to_status)? as u64;
30        if written == 0 {
31            return Err(Status::NO_SPACE);
32        }
33        current += written;
34    }
35    Ok(())
36}
37
38fn seek_for_write(file: &mut File<'_>, offset: u64) -> Result<(), Status> {
39    if offset > fatfs::MAX_FILE_SIZE as u64 {
40        return Err(Status::INVALID_ARGS);
41    }
42    let real_offset = file.seek(std::io::SeekFrom::Start(offset)).map_err(fatfs_error_to_status)?;
43    if real_offset == offset {
44        return Ok(());
45    }
46    assert!(real_offset < offset);
47    let result = extend(file, real_offset, offset);
48    if let Err(e) = result {
49        // Return the file to its original size.
50        file.seek(std::io::SeekFrom::Start(real_offset)).map_err(fatfs_error_to_status)?;
51        file.truncate().map_err(fatfs_error_to_status)?;
52        return Err(e);
53    }
54    Ok(())
55}
56
57struct FatFileData {
58    name: String,
59    parent: Option<Arc<FatDirectory>>,
60}
61
62/// Represents a single file on the disk.
63pub struct FatFile {
64    file: UnsafeCell<FatfsFileRef>,
65    filesystem: Pin<Arc<FatFilesystem>>,
66    data: RwLock<FatFileData>,
67}
68
69// The only member that isn't `Sync + Send` is the `file` member.
70// `file` is protected by the lock on `filesystem`, so we can safely
71// implement Sync + Send for FatFile.
72unsafe impl Sync for FatFile {}
73unsafe impl Send for FatFile {}
74
75impl FatFile {
76    /// Create a new FatFile.
77    pub(crate) fn new(
78        file: FatfsFileRef,
79        parent: Arc<FatDirectory>,
80        filesystem: Pin<Arc<FatFilesystem>>,
81        name: String,
82    ) -> Arc<Self> {
83        Arc::new(FatFile {
84            file: UnsafeCell::new(file),
85            filesystem,
86            data: RwLock::new(FatFileData { parent: Some(parent), name }),
87        })
88    }
89
90    /// Borrow the underlying Fatfs File mutably.
91    // TODO(https://fxbug.dev/414761492): document or remove this `#[allow]`
92    #[allow(clippy::mut_from_ref)]
93    pub(crate) fn borrow_file_mut<'a>(&self, fs: &'a FatFilesystemInner) -> Option<&mut File<'a>> {
94        // SAFETY: Safe because the file is protected by the lock on fs.
95        unsafe { self.file.get().as_mut() }.unwrap().borrow_mut(fs)
96    }
97
98    pub fn borrow_file<'a>(&self, fs: &'a FatFilesystemInner) -> Result<&File<'a>, Status> {
99        // Safe because the file is protected by the lock on fs.
100        unsafe { self.file.get().as_ref() }.unwrap().borrow(fs).ok_or(Status::BAD_HANDLE)
101    }
102
103    async fn write_or_append(
104        &self,
105        offset: Option<u64>,
106        content: &[u8],
107    ) -> Result<(u64, u64), Status> {
108        let fs_lock = self.filesystem.lock();
109        let file = self.borrow_file_mut(&fs_lock).ok_or(Status::BAD_HANDLE)?;
110        let mut file_offset = match offset {
111            Some(offset) => {
112                seek_for_write(file, offset)?;
113                offset
114            }
115            None => file.seek(std::io::SeekFrom::End(0)).map_err(fatfs_error_to_status)?,
116        };
117        let mut total_written = 0;
118        while total_written < content.len() {
119            let written = file.write(&content[total_written..]).map_err(fatfs_error_to_status)?;
120            if written == 0 {
121                break;
122            }
123            total_written += written;
124            file_offset += written as u64;
125            let result = file.write(&content[total_written..]).map_err(fatfs_error_to_status);
126            match result {
127                Ok(0) => break,
128                Ok(written) => {
129                    total_written += written;
130                    file_offset += written as u64;
131                }
132                Err(e) => {
133                    if total_written > 0 {
134                        break;
135                    }
136                    return Err(e);
137                }
138            }
139        }
140        self.filesystem.mark_dirty();
141        Ok((total_written as u64, file_offset))
142    }
143}
144
145impl Node for FatFile {
146    /// Flush to disk and invalidate the reference that's contained within this FatFile.
147    /// Any operations on the file will return Status::BAD_HANDLE until it is re-attached.
148    fn detach(&self, fs: &FatFilesystemInner) {
149        // Safe because we hold the fs lock.
150        let file = unsafe { self.file.get().as_mut() }.unwrap();
151        // This causes a flush to disk when the underlying fatfs File is dropped.
152        file.take(fs);
153    }
154
155    /// Attach to the given parent and re-open the underlying `FatfsFileRef` this file represents.
156    fn attach(
157        &self,
158        new_parent: Arc<FatDirectory>,
159        name: &str,
160        fs: &FatFilesystemInner,
161    ) -> Result<(), Status> {
162        let mut data = self.data.write();
163        data.name = name.to_owned();
164        // Safe because we hold the fs lock.
165        let file = unsafe { self.file.get().as_mut() }.unwrap();
166        // Safe because we have a reference to the FatFilesystem.
167        unsafe { file.maybe_reopen(fs, &new_parent, name)? };
168        data.parent.replace(new_parent);
169        Ok(())
170    }
171
172    fn did_delete(&self) {
173        self.data.write().parent.take();
174    }
175
176    fn open_ref(&self, fs_lock: &FatFilesystemInner) -> Result<(), Status> {
177        let data = self.data.read();
178        let file_ref = unsafe { self.file.get().as_mut() }.unwrap();
179        unsafe { file_ref.open(&fs_lock, data.parent.as_deref(), &data.name) }
180    }
181
182    /// Close the underlying FatfsFileRef, regardless of the number of open connections.
183    fn shut_down(&self, fs: &FatFilesystemInner) -> Result<(), Status> {
184        unsafe { self.file.get().as_mut() }.unwrap().take(fs);
185        Ok(())
186    }
187
188    fn flush_dir_entry(&self, fs: &FatFilesystemInner) -> Result<(), Status> {
189        if let Some(file) = self.borrow_file_mut(fs) {
190            file.flush_dir_entry().map_err(fatfs_error_to_status)?
191        }
192        Ok(())
193    }
194
195    fn close_ref(&self, fs: &FatFilesystemInner) {
196        unsafe { self.file.get().as_mut() }.unwrap().close(fs);
197    }
198}
199
200impl vfs::node::Node for FatFile {
201    async fn get_attributes(
202        &self,
203        requested_attributes: fio::NodeAttributesQuery,
204    ) -> Result<fio::NodeAttributes2, Status> {
205        let fs_lock = self.filesystem.lock();
206        let file = self.borrow_file(&fs_lock)?;
207        let content_size = file.len() as u64;
208        let creation_time = dos_to_unix_time(file.created());
209        let modification_time = dos_to_unix_time(file.modified());
210        let access_time = dos_date_to_unix_time(file.accessed());
211
212        // Figure out the storage size by rounding content_size up to the nearest
213        // multiple of cluster_size.
214        let cluster_size = fs_lock.cluster_size() as u64;
215        let storage_size = ((content_size + cluster_size - 1) / cluster_size) * cluster_size;
216
217        Ok(attributes!(
218            requested_attributes,
219            Mutable {
220                creation_time: creation_time,
221                modification_time: modification_time,
222                access_time: access_time
223            },
224            Immutable {
225                protocols: fio::NodeProtocolKinds::FILE,
226                abilities: fio::Operations::GET_ATTRIBUTES
227                    | fio::Operations::UPDATE_ATTRIBUTES
228                    | fio::Operations::READ_BYTES
229                    | fio::Operations::WRITE_BYTES,
230                content_size: content_size,
231                storage_size: storage_size,
232                link_count: 1, // FAT does not support hard links, so there is always 1 "link".
233            }
234        ))
235    }
236
237    fn close(self: Arc<Self>) {
238        self.close_ref(&self.filesystem.lock());
239    }
240
241    fn query_filesystem(&self) -> Result<fio::FilesystemInfo, Status> {
242        self.filesystem.query_filesystem()
243    }
244
245    fn will_clone(&self) {
246        self.open_ref(&self.filesystem.lock()).unwrap();
247    }
248}
249
250impl Debug for FatFile {
251    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252        f.debug_struct("FatFile").field("name", &self.data.read().name).finish()
253    }
254}
255
256impl VfsFile for FatFile {
257    fn writable(&self) -> bool {
258        return true;
259    }
260
261    async fn open_file(&self, _options: &FileOptions) -> Result<(), Status> {
262        Ok(())
263    }
264
265    async fn truncate(&self, length: u64) -> Result<(), Status> {
266        let fs_lock = self.filesystem.lock();
267        let file = self.borrow_file_mut(&fs_lock).ok_or(Status::BAD_HANDLE)?;
268        seek_for_write(file, length)?;
269        file.truncate().map_err(fatfs_error_to_status)?;
270        self.filesystem.mark_dirty();
271        Ok(())
272    }
273
274    async fn get_backing_memory(&self, _flags: fio::VmoFlags) -> Result<zx::Vmo, Status> {
275        Err(Status::NOT_SUPPORTED)
276    }
277
278    // Unfortunately, fatfs has deprecated the "set_created" and "set_modified" methods,
279    // saying that a TimeProvider should be used instead. There doesn't seem to be a good way to
280    // use a TimeProvider to change the creation/modification time of a file after the fact,
281    // so we need to use the deprecated methods.
282    #[allow(deprecated)]
283    async fn update_attributes(
284        &self,
285        attributes: fio::MutableNodeAttributes,
286    ) -> Result<(), Status> {
287        const SUPPORTED_MUTABLE_ATTRIBUTES: fio::NodeAttributesQuery =
288            fio::NodeAttributesQuery::CREATION_TIME
289                .union(fio::NodeAttributesQuery::MODIFICATION_TIME);
290
291        if !SUPPORTED_MUTABLE_ATTRIBUTES
292            .contains(vfs::common::mutable_node_attributes_to_query(&attributes))
293        {
294            return Err(Status::NOT_SUPPORTED);
295        }
296
297        let fs_lock = self.filesystem.lock();
298        let file = self.borrow_file_mut(&fs_lock).ok_or(Status::BAD_HANDLE)?;
299        let mut needs_flush = false;
300        if let Some(creation_time) = attributes.creation_time {
301            file.set_created(unix_to_dos_time(creation_time));
302            needs_flush = true;
303        }
304        if let Some(modification_time) = attributes.modification_time {
305            file.set_modified(unix_to_dos_time(modification_time));
306            needs_flush = true;
307        }
308
309        if needs_flush {
310            file.flush().map_err(fatfs_error_to_status)?;
311            self.filesystem.mark_dirty();
312        }
313        Ok(())
314    }
315
316    async fn get_size(&self) -> Result<u64, Status> {
317        let fs_lock = self.filesystem.lock();
318        let file = self.borrow_file(&fs_lock)?;
319        Ok(file.len() as u64)
320    }
321
322    async fn sync(&self, _mode: SyncMode) -> Result<(), Status> {
323        let fs_lock = self.filesystem.lock();
324        let file = self.borrow_file_mut(&fs_lock).ok_or(Status::BAD_HANDLE)?;
325
326        file.flush().map_err(fatfs_error_to_status)?;
327        Ok(())
328    }
329}
330
331impl VfsFileIo for FatFile {
332    async fn read_at(&self, offset: u64, buffer: &mut [u8]) -> Result<u64, Status> {
333        let fs_lock = self.filesystem.lock();
334        let file = self.borrow_file_mut(&fs_lock).ok_or(Status::BAD_HANDLE)?;
335
336        let real_offset =
337            file.seek(std::io::SeekFrom::Start(offset)).map_err(fatfs_error_to_status)?;
338        // Technically, we don't need to do this because the read should return zero bytes later,
339        // but it's better to be explicit.
340        if real_offset != offset {
341            return Ok(0);
342        }
343        let mut total_read = 0;
344        while total_read < buffer.len() {
345            let read = file.read(&mut buffer[total_read..]).map_err(fatfs_error_to_status)?;
346            if read == 0 {
347                break;
348            }
349            total_read += read;
350        }
351        Ok(total_read as u64)
352    }
353
354    async fn write_at(&self, offset: u64, content: &[u8]) -> Result<u64, Status> {
355        self.write_or_append(Some(offset), content).await.map(|r| r.0)
356    }
357
358    async fn append(&self, content: &[u8]) -> Result<(u64, u64), Status> {
359        self.write_or_append(None, content).await
360    }
361}
362
363impl vfs::directory::entry::GetEntryInfo for FatFile {
364    fn entry_info(&self) -> EntryInfo {
365        EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)
366    }
367}
368
369#[cfg(test)]
370mod tests {
371    // We only test things here that aren't covered by fs_tests.
372    use super::*;
373    use crate::node::{Closer, FatNode};
374    use crate::tests::{TestDiskContents, TestFatDisk};
375
376    const TEST_DISK_SIZE: u64 = 2048 << 10; // 2048K
377    const TEST_FILE_CONTENT: &str = "test file contents";
378
379    struct TestFile(Arc<FatFile>);
380
381    impl TestFile {
382        fn new() -> Self {
383            let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
384            let structure =
385                TestDiskContents::dir().add_child("test_file", TEST_FILE_CONTENT.into());
386            structure.create(&disk.root_dir());
387
388            let fs = disk.into_fatfs();
389            let dir = fs.get_fatfs_root();
390            let mut closer = Closer::new(&fs.filesystem());
391            dir.open_ref(&fs.filesystem().lock()).expect("open_ref failed");
392            closer.add(FatNode::Dir(dir.clone()));
393            let file = match dir
394                .open_child("test_file", fio::OpenFlags::empty(), &mut closer)
395                .expect("Open to succeed")
396            {
397                FatNode::File(f) => f,
398                val => panic!("Unexpected value {:?}", val),
399            };
400            file.open_ref(&fs.filesystem().lock()).expect("open_ref failed");
401            TestFile(file)
402        }
403    }
404
405    impl Drop for TestFile {
406        fn drop(&mut self) {
407            self.0.close_ref(&self.0.filesystem.lock());
408        }
409    }
410
411    impl std::ops::Deref for TestFile {
412        type Target = Arc<FatFile>;
413
414        fn deref(&self) -> &Self::Target {
415            &self.0
416        }
417    }
418
419    #[fuchsia::test]
420    async fn test_read_at() {
421        let file = TestFile::new();
422        // Note: fatfs incorrectly casts u64 to i64, which causes this value to wrap
423        // around and become negative, which causes seek() in read_at() to fail.
424        // The error is not particularly important, because fat has a maximum 32-bit file size.
425        // An error like this will only happen if an application deliberately seeks to a (very)
426        // out-of-range position or reads at a nonsensical offset.
427        let mut buffer = [0u8; 512];
428        let err = file.read_at(u64::MAX - 30, &mut buffer).await.expect_err("Read fails");
429        assert_eq!(err, Status::INVALID_ARGS);
430    }
431
432    #[fuchsia::test]
433    async fn test_get_attributes() {
434        let file = TestFile::new();
435        let fio::NodeAttributes2 { mutable_attributes, immutable_attributes } =
436            vfs::node::Node::get_attributes(&**file, fio::NodeAttributesQuery::all())
437                .await
438                .unwrap();
439        assert_eq!(immutable_attributes.content_size.unwrap(), TEST_FILE_CONTENT.len() as u64);
440        assert!(immutable_attributes.storage_size.unwrap() > TEST_FILE_CONTENT.len() as u64);
441        assert_eq!(immutable_attributes.protocols.unwrap(), fio::NodeProtocolKinds::FILE);
442        assert_eq!(
443            immutable_attributes.abilities.unwrap(),
444            fio::Abilities::GET_ATTRIBUTES
445                | fio::Abilities::UPDATE_ATTRIBUTES
446                | fio::Abilities::READ_BYTES
447                | fio::Abilities::WRITE_BYTES
448        );
449        assert!(mutable_attributes.creation_time.is_some());
450        assert!(mutable_attributes.modification_time.is_some());
451    }
452
453    #[fuchsia::test]
454    async fn test_update_attributes() {
455        let file = TestFile::new();
456
457        let new_time = std::time::SystemTime::now()
458            .duration_since(std::time::SystemTime::UNIX_EPOCH)
459            .expect("SystemTime before UNIX EPOCH")
460            .as_nanos();
461        let new_attrs = fio::MutableNodeAttributes {
462            creation_time: Some(new_time.try_into().unwrap()),
463            ..Default::default()
464        };
465        file.update_attributes(new_attrs).await.expect("update attributes failed");
466    }
467}