Skip to main content

fatfs/
file.rs

1use core::convert::TryFrom;
2
3use crate::dir_entry::DirEntryEditor;
4use crate::error::Error;
5use crate::fs::{FileSystem, ReadWriteSeek};
6use crate::io::{IoBase, Read, Seek, SeekFrom, Write};
7use crate::time::{Date, DateTime, TimeProvider};
8
9const MAX_FILE_SIZE: u32 = u32::MAX;
10
11/// A FAT filesystem file object used for reading and writing data.
12///
13/// This struct is created by the `open_file` or `create_file` methods on `Dir`.
14pub struct File<'a, IO: ReadWriteSeek, TP, OCC> {
15    // Note first_cluster is None if file is empty
16    first_cluster: Option<u32>,
17    // Note: if offset points between clusters current_cluster is the previous cluster
18    current_cluster: Option<u32>,
19    // current position in this file
20    offset: u32,
21    // file dir entry editor - None for root dir
22    entry: Option<DirEntryEditor>,
23    // file-system reference
24    fs: &'a FileSystem<IO, TP, OCC>,
25}
26
27/// An extent containing a file's data on disk.
28///
29/// This is created by the `extents` method on `File`, and represents
30/// a byte range on the disk that contains a file's data. All values
31/// are in bytes.
32#[derive(Clone, Debug)]
33pub struct Extent {
34    pub offset: u64,
35    pub size: u32,
36}
37
38impl<'a, IO: ReadWriteSeek, TP, OCC> File<'a, IO, TP, OCC> {
39    pub(crate) fn new(
40        first_cluster: Option<u32>,
41        entry: Option<DirEntryEditor>,
42        fs: &'a FileSystem<IO, TP, OCC>,
43    ) -> Self {
44        File {
45            first_cluster,
46            entry,
47            fs,
48            current_cluster: None, // cluster before first one
49            offset: 0,
50        }
51    }
52
53    /// Truncate file in current position.
54    ///
55    /// # Errors
56    ///
57    /// `Error::Io` will be returned if the underlying storage object returned an I/O error.
58    ///
59    /// # Panics
60    ///
61    /// Will panic if this is the root directory.
62    pub fn truncate(&mut self) -> Result<(), Error<IO::Error>> {
63        trace!("File::truncate");
64        if let Some(ref mut e) = self.entry {
65            e.set_size(self.offset);
66            if self.offset == 0 {
67                e.set_first_cluster(None, self.fs.fat_type());
68            }
69        } else {
70            // Note: we cannot handle this case because there is no size field
71            panic!("Trying to truncate a file without an entry");
72        }
73        if let Some(current_cluster) = self.current_cluster {
74            // current cluster is none only if offset is 0
75            debug_assert!(self.offset > 0);
76            self.fs.truncate_cluster_chain(current_cluster)
77        } else {
78            debug_assert!(self.offset == 0);
79            if let Some(n) = self.first_cluster {
80                self.fs.free_cluster_chain(n)?;
81                self.first_cluster = None;
82            }
83            Ok(())
84        }
85    }
86
87    /// Get the extents of a file on disk.
88    ///
89    /// This returns an iterator over the byte ranges on-disk occupied by
90    /// this file.
91    pub fn extents(&mut self) -> impl Iterator<Item = Result<Extent, Error<IO::Error>>> + 'a {
92        let fs = self.fs;
93        let cluster_size = fs.cluster_size();
94        let Some(mut bytes_left) = self.size() else {
95            return None.into_iter().flatten();
96        };
97        let Some(first) = self.first_cluster else {
98            return None.into_iter().flatten();
99        };
100
101        Some(
102            core::iter::once(Ok(first))
103                .chain(fs.cluster_iter(first))
104                .map(move |cluster_err| match cluster_err {
105                    Ok(cluster) => {
106                        let size = cluster_size.min(bytes_left);
107                        bytes_left -= size;
108                        Ok(Extent {
109                            offset: fs.offset_from_cluster(cluster),
110                            size,
111                        })
112                    }
113                    Err(e) => Err(e),
114                }),
115        )
116        .into_iter()
117        .flatten()
118    }
119
120    pub(crate) fn abs_pos(&self) -> Option<u64> {
121        // Returns current position relative to filesystem start
122        // Note: when between clusters it returns position after previous cluster
123        match self.current_cluster {
124            Some(n) => {
125                let cluster_size = self.fs.cluster_size();
126                let offset_mod_cluster_size = self.offset % cluster_size;
127                let offset_in_cluster = if offset_mod_cluster_size == 0 {
128                    // position points between clusters - we are returning previous cluster so
129                    // offset must be set to the cluster size
130                    cluster_size
131                } else {
132                    offset_mod_cluster_size
133                };
134                let offset_in_fs = self.fs.offset_from_cluster(n) + u64::from(offset_in_cluster);
135                Some(offset_in_fs)
136            }
137            None => None,
138        }
139    }
140
141    fn flush_dir_entry(&mut self) -> Result<(), Error<IO::Error>> {
142        if let Some(ref mut e) = self.entry {
143            e.flush(self.fs)?;
144        }
145        Ok(())
146    }
147
148    /// Sets date and time of creation for this file.
149    ///
150    /// Note: it is set to a value from the `TimeProvider` when creating a file.
151    #[deprecated]
152    pub fn set_created(&mut self, date_time: DateTime) {
153        if let Some(ref mut e) = self.entry {
154            e.set_created(date_time);
155        }
156    }
157
158    /// Sets date of last access for this file.
159    ///
160    /// Note: it is overwritten by a value from the `TimeProvider` on every file read operation.
161    #[deprecated]
162    pub fn set_accessed(&mut self, date: Date) {
163        if let Some(ref mut e) = self.entry {
164            e.set_accessed(date);
165        }
166    }
167
168    /// Sets date and time of last modification for this file.
169    ///
170    /// Note: it is overwritten by a value from the `TimeProvider` on every file write operation.
171    #[deprecated]
172    pub fn set_modified(&mut self, date_time: DateTime) {
173        if let Some(ref mut e) = self.entry {
174            e.set_modified(date_time);
175        }
176    }
177
178    /// Get the access time of this file.
179    pub fn accessed(&self) -> Date {
180        match self.entry {
181            Some(ref e) => e.inner().accessed(),
182            None => Date::decode(0),
183        }
184    }
185
186    /// Get the creation time of this file.
187    pub fn created(&self) -> DateTime {
188        match self.entry {
189            Some(ref e) => e.inner().created(),
190            None => DateTime::decode(0, 0, 0),
191        }
192    }
193
194    /// Get the modification time of this file.
195    pub fn modified(&self) -> DateTime {
196        match self.entry {
197            Some(ref e) => e.inner().modified(),
198            None => DateTime::decode(0, 0, 0),
199        }
200    }
201
202    pub fn size(&self) -> Option<u32> {
203        match self.entry {
204            Some(ref e) => e.inner().size(),
205            None => None,
206        }
207    }
208
209    fn is_dir(&self) -> bool {
210        match self.entry {
211            Some(ref e) => e.inner().is_dir(),
212            None => true, // root directory
213        }
214    }
215
216    fn bytes_left_in_file(&self) -> Option<usize> {
217        // Note: seeking beyond end of file is not allowed so overflow is impossible
218        self.size().map(|s| (s - self.offset) as usize)
219    }
220
221    fn set_first_cluster(&mut self, cluster: u32) {
222        self.first_cluster = Some(cluster);
223        if let Some(ref mut e) = self.entry {
224            e.set_first_cluster(self.first_cluster, self.fs.fat_type());
225        }
226    }
227
228    pub(crate) fn first_cluster(&self) -> Option<u32> {
229        self.first_cluster
230    }
231
232    fn flush(&mut self) -> Result<(), Error<IO::Error>> {
233        self.flush_dir_entry()?;
234        let mut disk = self.fs.disk.borrow_mut();
235        disk.flush()?;
236        Ok(())
237    }
238
239    pub(crate) fn is_root_dir(&self) -> bool {
240        self.entry.is_none()
241    }
242}
243
244impl<IO: ReadWriteSeek, TP: TimeProvider, OCC> File<'_, IO, TP, OCC> {
245    fn update_dir_entry_after_write(&mut self) {
246        let offset = self.offset;
247        if let Some(ref mut e) = self.entry {
248            let now = self.fs.options.time_provider.get_current_date_time();
249            e.set_modified(now);
250            if e.inner().size().map_or(false, |s| offset > s) {
251                e.set_size(offset);
252            }
253        }
254    }
255}
256
257impl<IO: ReadWriteSeek, TP, OCC> Drop for File<'_, IO, TP, OCC> {
258    fn drop(&mut self) {
259        if let Err(err) = self.flush() {
260            error!("flush failed {:?}", err);
261        }
262    }
263}
264
265// Note: derive cannot be used because of invalid bounds. See: https://github.com/rust-lang/rust/issues/26925
266impl<IO: ReadWriteSeek, TP, OCC> Clone for File<'_, IO, TP, OCC> {
267    fn clone(&self) -> Self {
268        File {
269            first_cluster: self.first_cluster,
270            current_cluster: self.current_cluster,
271            offset: self.offset,
272            entry: self.entry.clone(),
273            fs: self.fs,
274        }
275    }
276}
277
278impl<IO: ReadWriteSeek, TP, OCC> IoBase for File<'_, IO, TP, OCC> {
279    type Error = Error<IO::Error>;
280}
281
282impl<IO: ReadWriteSeek, TP: TimeProvider, OCC> Read for File<'_, IO, TP, OCC> {
283    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
284        trace!("File::read");
285        let cluster_size = self.fs.cluster_size();
286        let current_cluster_opt = if self.offset % cluster_size == 0 {
287            // next cluster
288            match self.current_cluster {
289                None => self.first_cluster,
290                Some(n) => {
291                    let r = self.fs.cluster_iter(n).next();
292                    match r {
293                        Some(Err(err)) => return Err(err),
294                        Some(Ok(n)) => Some(n),
295                        None => None,
296                    }
297                }
298            }
299        } else {
300            self.current_cluster
301        };
302        let Some(current_cluster) = current_cluster_opt else {
303            return Ok(0);
304        };
305        let offset_in_cluster = self.offset % cluster_size;
306        let bytes_left_in_cluster = (cluster_size - offset_in_cluster) as usize;
307        let bytes_left_in_file = self.bytes_left_in_file().unwrap_or(bytes_left_in_cluster);
308        let read_size = buf.len().min(bytes_left_in_cluster).min(bytes_left_in_file);
309        if read_size == 0 {
310            return Ok(0);
311        }
312        trace!("read {} bytes in cluster {}", read_size, current_cluster);
313        let offset_in_fs = self.fs.offset_from_cluster(current_cluster) + u64::from(offset_in_cluster);
314        let read_bytes = {
315            let mut disk = self.fs.disk.borrow_mut();
316            disk.seek(SeekFrom::Start(offset_in_fs))?;
317            disk.read(&mut buf[..read_size])?
318        };
319        if read_bytes == 0 {
320            return Ok(0);
321        }
322        self.offset += read_bytes as u32;
323        self.current_cluster = Some(current_cluster);
324
325        if let Some(ref mut e) = self.entry {
326            if self.fs.options.update_accessed_date {
327                let now = self.fs.options.time_provider.get_current_date();
328                e.set_accessed(now);
329            }
330        }
331        Ok(read_bytes)
332    }
333}
334
335#[cfg(feature = "std")]
336impl<IO: ReadWriteSeek, TP: TimeProvider, OCC> std::io::Read for File<'_, IO, TP, OCC>
337where
338    std::io::Error: From<Error<IO::Error>>,
339{
340    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
341        Ok(Read::read(self, buf)?)
342    }
343}
344
345impl<IO: ReadWriteSeek, TP: TimeProvider, OCC> Write for File<'_, IO, TP, OCC> {
346    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
347        trace!("File::write");
348        let cluster_size = self.fs.cluster_size();
349        let offset_in_cluster = self.offset % cluster_size;
350        let bytes_left_in_cluster = (cluster_size - offset_in_cluster) as usize;
351        let bytes_left_until_max_file_size = (MAX_FILE_SIZE - self.offset) as usize;
352        let write_size = buf.len().min(bytes_left_in_cluster).min(bytes_left_until_max_file_size);
353        // Exit early if we are going to write no data
354        if write_size == 0 {
355            return Ok(0);
356        }
357        // Mark the volume 'dirty'
358        self.fs.set_dirty_flag(true)?;
359        // Get cluster for write possibly allocating new one
360        let current_cluster = if self.offset % cluster_size == 0 {
361            // next cluster
362            let next_cluster = match self.current_cluster {
363                None => self.first_cluster,
364                Some(n) => {
365                    let r = self.fs.cluster_iter(n).next();
366                    match r {
367                        Some(Err(err)) => return Err(err),
368                        Some(Ok(n)) => Some(n),
369                        None => None,
370                    }
371                }
372            };
373            if let Some(n) = next_cluster {
374                n
375            } else {
376                // end of chain reached - allocate new cluster
377                let new_cluster = self.fs.alloc_cluster(self.current_cluster, self.is_dir())?;
378                trace!("allocated cluster {}", new_cluster);
379                if self.first_cluster.is_none() {
380                    self.set_first_cluster(new_cluster);
381                }
382                new_cluster
383            }
384        } else {
385            // self.current_cluster should be a valid cluster
386            match self.current_cluster {
387                Some(n) => n,
388                None => panic!("Offset inside cluster but no cluster allocated"),
389            }
390        };
391        trace!("write {} bytes in cluster {}", write_size, current_cluster);
392        let offset_in_fs = self.fs.offset_from_cluster(current_cluster) + u64::from(offset_in_cluster);
393        let written_bytes = {
394            let mut disk = self.fs.disk.borrow_mut();
395            disk.seek(SeekFrom::Start(offset_in_fs))?;
396            disk.write(&buf[..write_size])?
397        };
398        if written_bytes == 0 {
399            return Ok(0);
400        }
401        // some bytes were writter - update position and optionally size
402        self.offset += written_bytes as u32;
403        self.current_cluster = Some(current_cluster);
404        self.update_dir_entry_after_write();
405        Ok(written_bytes)
406    }
407
408    fn flush(&mut self) -> Result<(), Self::Error> {
409        Self::flush(self)
410    }
411}
412
413#[cfg(feature = "std")]
414impl<IO: ReadWriteSeek, TP: TimeProvider, OCC> std::io::Write for File<'_, IO, TP, OCC>
415where
416    std::io::Error: From<Error<IO::Error>>,
417{
418    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
419        Ok(Write::write(self, buf)?)
420    }
421
422    fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
423        Ok(Write::write_all(self, buf)?)
424    }
425
426    fn flush(&mut self) -> std::io::Result<()> {
427        Ok(Write::flush(self)?)
428    }
429}
430
431impl<IO: ReadWriteSeek, TP, OCC> Seek for File<'_, IO, TP, OCC> {
432    fn seek(&mut self, pos: SeekFrom) -> Result<u64, Self::Error> {
433        trace!("File::seek");
434        let size_opt = self.size();
435        let new_offset_opt: Option<u32> = match pos {
436            SeekFrom::Current(x) => i64::from(self.offset)
437                .checked_add(x)
438                .and_then(|n| u32::try_from(n).ok()),
439            SeekFrom::Start(x) => u32::try_from(x).ok(),
440            SeekFrom::End(o) => size_opt
441                .and_then(|s| i64::from(s).checked_add(o))
442                .and_then(|n| u32::try_from(n).ok()),
443        };
444        let Some(mut new_offset) = new_offset_opt else {
445            error!("Invalid seek offset");
446            return Err(Error::InvalidInput);
447        };
448        if let Some(size) = size_opt {
449            if new_offset > size {
450                warn!("Seek beyond the end of the file");
451                new_offset = size;
452            }
453        }
454        trace!("file seek {} -> {} - entry {:?}", self.offset, new_offset, self.entry);
455        if new_offset == self.offset {
456            // position is the same - nothing to do
457            return Ok(u64::from(self.offset));
458        }
459        let new_offset_in_clusters = self.fs.clusters_from_bytes(u64::from(new_offset));
460        let old_offset_in_clusters = self.fs.clusters_from_bytes(u64::from(self.offset));
461        let new_cluster = if new_offset == 0 {
462            None
463        } else if new_offset_in_clusters == old_offset_in_clusters {
464            self.current_cluster
465        } else if let Some(first_cluster) = self.first_cluster {
466            // calculate number of clusters to skip
467            // return the previous cluster if the offset points to the cluster boundary
468            // Note: new_offset_in_clusters cannot be 0 here because new_offset is not 0
469            debug_assert!(new_offset_in_clusters > 0);
470            let clusters_to_skip = new_offset_in_clusters - 1;
471            let mut cluster = first_cluster;
472            let mut iter = self.fs.cluster_iter(first_cluster);
473            for i in 0..clusters_to_skip {
474                cluster = if let Some(r) = iter.next() {
475                    r?
476                } else {
477                    // cluster chain ends before the new position - seek to the end of the last cluster
478                    new_offset = self.fs.bytes_from_clusters(i + 1) as u32;
479                    break;
480                };
481            }
482            Some(cluster)
483        } else {
484            // empty file - always seek to 0
485            new_offset = 0;
486            None
487        };
488        self.offset = new_offset;
489        self.current_cluster = new_cluster;
490        Ok(u64::from(self.offset))
491    }
492}
493
494#[cfg(feature = "std")]
495impl<IO: ReadWriteSeek, TP: TimeProvider, OCC> std::io::Seek for File<'_, IO, TP, OCC>
496where
497    std::io::Error: From<Error<IO::Error>>,
498{
499    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
500        Ok(Seek::seek(self, pos.into())?)
501    }
502}