Skip to main content

embedded_sdmmc/filesystem/
files.rs

1//! File related code
2
3use super::TimeSource;
4use crate::{
5    BlockDevice, Error, RawVolume, VolumeManager,
6    filesystem::{ClusterId, DirEntry, Handle},
7};
8use embedded_io::{ErrorType, Read, Seek, SeekFrom, Write};
9
10/// A handle for an open file on disk.
11///
12/// Do NOT drop this object! It doesn't hold a reference to the Volume Manager
13/// it was created from and cannot update the directory entry if you drop it.
14/// Additionally, the VolumeManager will think you still have the file open if
15/// you just drop it, and it won't let you open the file again.
16///
17/// Instead you must pass it to [`crate::VolumeManager::close_file`] to close it
18/// cleanly.
19///
20/// If you want your files to close themselves on drop, create your own File
21/// type that wraps this one and also holds a `VolumeManager` reference. You'll
22/// then also need to put your `VolumeManager` in some kind of Mutex or RefCell,
23/// and deal with the fact you can't put them both in the same struct any more
24/// because one refers to the other. Basically, it's complicated and there's a
25/// reason we did it this way.
26#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
27#[derive(Debug, Copy, Clone, PartialEq, Eq)]
28pub struct RawFile(pub(crate) Handle);
29
30impl RawFile {
31    /// Convert a raw file into a droppable [`File`]
32    pub fn to_file<D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>(
33        self,
34        volume_mgr: &VolumeManager<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>,
35    ) -> File<'_, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
36    where
37        D: crate::BlockDevice,
38        T: crate::TimeSource,
39    {
40        File::new(self, volume_mgr)
41    }
42}
43
44/// A handle for an open file on disk, which closes on drop.
45///
46/// In contrast to a `RawFile`, a `File`  holds a mutable reference to its
47/// parent `VolumeManager`, which restricts which operations you can perform.
48///
49/// If you drop a value of this type, it closes the file automatically, but any
50/// error that may occur will be ignored. To handle potential errors, use
51/// the [`File::close`] method.
52pub struct File<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
53where
54    D: crate::BlockDevice,
55    T: crate::TimeSource,
56{
57    raw_file: RawFile,
58    volume_mgr: &'a VolumeManager<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>,
59}
60
61impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
62    File<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
63where
64    D: crate::BlockDevice,
65    T: crate::TimeSource,
66{
67    /// Create a new `File` from a `RawFile`
68    pub fn new(
69        raw_file: RawFile,
70        volume_mgr: &'a VolumeManager<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>,
71    ) -> Self {
72        File {
73            raw_file,
74            volume_mgr,
75        }
76    }
77
78    /// Read from the file
79    ///
80    /// Returns how many bytes were read, or an error.
81    ///
82    /// See [`VolumeManager::read`] for details, except the file given is this
83    /// file.
84    pub fn read(&self, buffer: &mut [u8]) -> Result<usize, crate::Error<D::Error>> {
85        self.volume_mgr.read(self.raw_file, buffer)
86    }
87
88    /// Write to the file
89    ///
90    /// See [`VolumeManager::write`] for details, except the file given is this
91    /// file.
92    pub fn write(&self, buffer: &[u8]) -> Result<(), crate::Error<D::Error>> {
93        self.volume_mgr.write(self.raw_file, buffer)
94    }
95
96    /// Check if a file is at End Of File.
97    ///
98    /// See [`VolumeManager::file_eof`] for details, except the file given is this
99    /// file.
100    pub fn is_eof(&self) -> bool {
101        self.volume_mgr
102            .file_eof(self.raw_file)
103            .expect("Corrupt file ID")
104    }
105
106    /// Seek a file with an offset from the current position.
107    ///
108    /// See [`VolumeManager::file_seek_from_current`] for details, except the
109    /// file given is this file.
110    pub fn seek_from_current(&self, offset: i32) -> Result<(), crate::Error<D::Error>> {
111        self.volume_mgr
112            .file_seek_from_current(self.raw_file, offset)
113    }
114
115    /// Seek a file with an offset from the start of the file.
116    ///
117    /// See [`VolumeManager::file_seek_from_start`] for details, except the
118    /// file given is this file.
119    pub fn seek_from_start(&self, offset: u32) -> Result<(), crate::Error<D::Error>> {
120        self.volume_mgr.file_seek_from_start(self.raw_file, offset)
121    }
122
123    /// Seek a file with an offset back from the end of the file.
124    ///
125    /// See [`VolumeManager::file_seek_from_end`] for details, except the file
126    /// given is this file.
127    pub fn seek_from_end(&self, offset: u32) -> Result<(), crate::Error<D::Error>> {
128        self.volume_mgr.file_seek_from_end(self.raw_file, offset)
129    }
130
131    /// Get the length of a file
132    ///
133    /// See [`VolumeManager::file_length`] for details, except the file given
134    /// is this file.
135    pub fn length(&self) -> u32 {
136        self.volume_mgr
137            .file_length(self.raw_file)
138            .expect("Corrupt file ID")
139    }
140
141    /// Get the current offset of a file
142    ///
143    /// See [`VolumeManager::file_offset`] for details, except the file given
144    /// is this file.
145    pub fn offset(&self) -> u32 {
146        self.volume_mgr
147            .file_offset(self.raw_file)
148            .expect("Corrupt file ID")
149    }
150
151    /// Convert back to a raw file
152    pub fn to_raw_file(self) -> RawFile {
153        let f = self.raw_file;
154        core::mem::forget(self);
155        f
156    }
157
158    /// Flush any written data by updating the directory entry.
159    ///
160    /// See [`VolumeManager::flush_file`] for details, except the file given
161    /// is this file.
162    pub fn flush(&self) -> Result<(), Error<D::Error>> {
163        self.volume_mgr.flush_file(self.raw_file)
164    }
165
166    /// Consume the `File` handle and close it. The behavior of this is similar
167    /// to using [`core::mem::drop`] or letting the `File` go out of scope,
168    /// except this lets the user handle any errors that may occur in the process,
169    /// whereas when using drop, any errors will be discarded silently.
170    pub fn close(self) -> Result<(), Error<D::Error>> {
171        let result = self.volume_mgr.close_file(self.raw_file);
172        core::mem::forget(self);
173        result
174    }
175}
176
177impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> Drop
178    for File<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
179where
180    D: crate::BlockDevice,
181    T: crate::TimeSource,
182{
183    fn drop(&mut self) {
184        _ = self.volume_mgr.close_file(self.raw_file);
185    }
186}
187
188impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
189    core::fmt::Debug for File<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
190where
191    D: crate::BlockDevice,
192    T: crate::TimeSource,
193{
194    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
195        write!(f, "File({})", self.raw_file.0.0)
196    }
197}
198
199impl<
200    D: BlockDevice,
201    T: TimeSource,
202    const MAX_DIRS: usize,
203    const MAX_FILES: usize,
204    const MAX_VOLUMES: usize,
205> ErrorType for File<'_, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
206{
207    type Error = crate::Error<D::Error>;
208}
209
210impl<
211    D: BlockDevice,
212    T: TimeSource,
213    const MAX_DIRS: usize,
214    const MAX_FILES: usize,
215    const MAX_VOLUMES: usize,
216> Read for File<'_, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
217{
218    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
219        if buf.is_empty() {
220            Ok(0)
221        } else {
222            File::read(self, buf)
223        }
224    }
225}
226
227impl<
228    D: BlockDevice,
229    T: TimeSource,
230    const MAX_DIRS: usize,
231    const MAX_FILES: usize,
232    const MAX_VOLUMES: usize,
233> Write for File<'_, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
234{
235    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
236        if buf.is_empty() {
237            Ok(0)
238        } else {
239            File::write(self, buf)?;
240            Ok(buf.len())
241        }
242    }
243
244    fn flush(&mut self) -> Result<(), Self::Error> {
245        Self::flush(self)
246    }
247}
248
249impl<
250    D: BlockDevice,
251    T: TimeSource,
252    const MAX_DIRS: usize,
253    const MAX_FILES: usize,
254    const MAX_VOLUMES: usize,
255> Seek for File<'_, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
256{
257    fn seek(&mut self, pos: SeekFrom) -> Result<u64, Self::Error> {
258        match pos {
259            SeekFrom::Start(offset) => {
260                self.seek_from_start(offset.try_into().map_err(|_| Error::InvalidOffset)?)?
261            }
262            SeekFrom::End(offset) => {
263                self.seek_from_end((-offset).try_into().map_err(|_| Error::InvalidOffset)?)?
264            }
265            SeekFrom::Current(offset) => {
266                self.seek_from_current(offset.try_into().map_err(|_| Error::InvalidOffset)?)?
267            }
268        }
269        Ok(self.offset().into())
270    }
271}
272
273#[cfg(feature = "defmt-log")]
274impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
275    defmt::Format for File<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
276where
277    D: crate::BlockDevice,
278    T: crate::TimeSource,
279{
280    fn format(&self, fmt: defmt::Formatter) {
281        defmt::write!(fmt, "File({})", self.raw_file.0.0)
282    }
283}
284
285/// Errors related to file operations
286#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
288pub enum FileError {
289    /// Tried to use an invalid offset.
290    InvalidOffset,
291}
292
293/// The different ways we can open a file.
294#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
295#[derive(Debug, PartialEq, Eq, Copy, Clone)]
296pub enum Mode {
297    /// Open a file for reading, if it exists.
298    ReadOnly,
299    /// Open a file for appending (writing to the end of the existing file), if it exists.
300    ReadWriteAppend,
301    /// Open a file and remove all contents, before writing to the start of the existing file, if it exists.
302    ReadWriteTruncate,
303    /// Create a new empty file. Fail if it exists.
304    ReadWriteCreate,
305    /// Create a new empty file, or truncate an existing file.
306    ReadWriteCreateOrTruncate,
307    /// Create a new empty file, or append to an existing file.
308    ReadWriteCreateOrAppend,
309}
310
311/// Internal metadata about an open file
312#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
313#[derive(Debug, Clone)]
314pub(crate) struct FileInfo {
315    /// Handle for this file
316    pub(crate) raw_file: RawFile,
317    /// The handle for the volume this directory is on
318    pub(crate) raw_volume: RawVolume,
319    /// The last cluster we accessed, and how many bytes that short-cuts us.
320    ///
321    /// This saves us walking from the very start of the FAT chain when we move
322    /// forward through a file.
323    pub(crate) current_cluster: (u32, ClusterId),
324    /// How far through the file we've read (in bytes).
325    pub(crate) current_offset: u32,
326    /// What mode the file was opened in
327    pub(crate) mode: Mode,
328    /// DirEntry of this file
329    pub(crate) entry: DirEntry,
330    /// Did we write to this file?
331    pub(crate) dirty: bool,
332}
333
334impl FileInfo {
335    /// Are we at the end of the file?
336    pub fn eof(&self) -> bool {
337        self.current_offset == self.entry.size
338    }
339
340    /// How long is the file?
341    pub fn length(&self) -> u32 {
342        self.entry.size
343    }
344
345    /// Seek to a new position in the file, relative to the start of the file.
346    pub fn seek_from_start(&mut self, offset: u32) -> Result<(), FileError> {
347        if offset > self.entry.size {
348            return Err(FileError::InvalidOffset);
349        }
350        self.current_offset = offset;
351        Ok(())
352    }
353
354    /// Seek to a new position in the file, relative to the end of the file.
355    pub fn seek_from_end(&mut self, offset: u32) -> Result<(), FileError> {
356        if offset > self.entry.size {
357            return Err(FileError::InvalidOffset);
358        }
359        self.current_offset = self.entry.size - offset;
360        Ok(())
361    }
362
363    /// Seek to a new position in the file, relative to the current position.
364    pub fn seek_from_current(&mut self, offset: i32) -> Result<(), FileError> {
365        let new_offset = i64::from(self.current_offset) + i64::from(offset);
366        if new_offset < 0 || new_offset > i64::from(self.entry.size) {
367            return Err(FileError::InvalidOffset);
368        }
369        self.current_offset = new_offset as u32;
370        Ok(())
371    }
372
373    /// Amount of file left to read.
374    pub fn left(&self) -> u32 {
375        self.entry.size - self.current_offset
376    }
377
378    /// Update the file's length.
379    pub(crate) fn update_length(&mut self, new: u32) {
380        self.entry.size = new;
381    }
382}
383
384// ****************************************************************************
385//
386// End Of File
387//
388// ****************************************************************************