simple_fatfs/fat/
file.rs

1use super::*;
2
3use core::{cmp, num, ops};
4
5#[cfg(not(feature = "std"))]
6use alloc::{borrow::ToOwned, string::String, vec::Vec};
7use time::{Date, PrimitiveDateTime};
8
9use crate::utils::{self, bincode::BINCODE_CONFIG};
10use crate::{FSError, FSResult, InternalFSError};
11
12use embedded_io::*;
13
14#[derive(Debug)]
15pub(crate) struct FileProps {
16    pub(crate) entry: Properties,
17    /// the byte offset of the R/W pointer
18    ///
19    /// this can't exceed the file size, so they share the same data type
20    pub(crate) offset: FileSize,
21    pub(crate) current_cluster: ClusterIndex,
22}
23
24/// A read-only file within a FAT filesystem
25///
26/// Note: whether or not your FileSystem is RO or R/W, this won't update
27/// the [`ROFile::last_accessed_date()`](Properties::last_accessed_date())
28/// If you want to avoid this behavior in a R/W filesystem, use [`RWFile`]
29#[derive(Debug)]
30pub struct ROFile<'a, S>
31where
32    S: Read + Seek,
33{
34    pub(crate) fs: &'a FileSystem<S>,
35    pub(crate) props: FileProps,
36}
37
38impl<S> ops::Deref for ROFile<'_, S>
39where
40    S: Read + Seek,
41{
42    type Target = Properties;
43
44    fn deref(&self) -> &Self::Target {
45        &self.props.entry
46    }
47}
48
49impl<S> ops::DerefMut for ROFile<'_, S>
50where
51    S: Read + Seek,
52{
53    fn deref_mut(&mut self) -> &mut Self::Target {
54        &mut self.props.entry
55    }
56}
57
58// Constructors
59impl<'a, S> ROFile<'a, S>
60where
61    S: Read + Seek,
62{
63    pub(crate) fn from_props(props: FileProps, fs: &'a FileSystem<S>) -> Self {
64        Self { fs, props }
65    }
66}
67
68// Internal functions
69impl<S> ROFile<'_, S>
70where
71    S: Read + Seek,
72{
73    #[inline]
74    /// Panics if the current cluster doesn't point to another cluster
75    fn next_cluster(&mut self) -> Result<(), <Self as ErrorType>::Error> {
76        // when a `ROFile` is created, `cluster_chain_is_healthy` is called, if it fails, that ROFile is dropped
77        self.props.current_cluster = self.get_next_cluster()?.unwrap();
78
79        Ok(())
80    }
81
82    #[inline]
83    /// Non-[`panic`]king version of [`next_cluster()`](ROFile::next_cluster)
84    fn get_next_cluster(&mut self) -> Result<Option<ClusterIndex>, <Self as ErrorType>::Error> {
85        self.fs.get_next_cluster(self.props.current_cluster)
86    }
87
88    /// Returns that last cluster in the file's cluster chain
89    fn last_cluster_in_chain(&mut self) -> Result<ClusterIndex, <Self as ErrorType>::Error> {
90        // we begin from the current cluster to save some time
91        let mut current_cluster = self.props.current_cluster;
92
93        loop {
94            match self.fs.read_nth_FAT_entry(current_cluster)? {
95                FATEntry::Allocated(next_cluster) => current_cluster = next_cluster,
96                FATEntry::Eof => break,
97                _ => unreachable!(),
98            }
99        }
100
101        Ok(current_cluster)
102    }
103
104    /// Checks whether the cluster chain of this file is healthy or malformed
105    pub(crate) fn cluster_chain_is_healthy(&mut self) -> Result<bool, S::Error> {
106        let mut current_cluster = self.data_cluster;
107        let mut cluster_count = 0;
108
109        loop {
110            cluster_count += 1;
111
112            if cluster_count * self.fs.cluster_size() >= self.file_size {
113                break;
114            }
115
116            match self.fs.read_nth_FAT_entry(current_cluster)? {
117                FATEntry::Allocated(next_cluster) => current_cluster = next_cluster,
118                _ => return Ok(false),
119            };
120        }
121
122        Ok(true)
123    }
124
125    fn offset_from_seekfrom(&self, seekfrom: SeekFrom) -> u64 {
126        match seekfrom {
127            SeekFrom::Start(offset) => offset,
128            SeekFrom::Current(offset) => {
129                let offset = i64::from(self.props.offset) + offset;
130                offset.try_into().unwrap_or(u64::MIN)
131            }
132            SeekFrom::End(offset) => {
133                let offset = i64::from(self.file_size) + offset;
134                offset.try_into().unwrap_or(u64::MIN)
135            }
136        }
137    }
138}
139
140impl<S> ErrorType for ROFile<'_, S>
141where
142    S: Read + Seek,
143{
144    type Error = S::Error;
145}
146
147impl<S> Read for ROFile<'_, S>
148where
149    S: Read + Seek,
150{
151    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
152        let mut bytes_read = 0;
153        // this is the maximum amount of bytes that can be read
154        let read_cap = cmp::min(
155            buf.len(),
156            // we better not panic here (this could be an issue only on 16-bit targets tho)
157            usize::try_from(self.file_size - self.props.offset).unwrap_or(usize::MAX),
158        );
159
160        'outer: loop {
161            let sector_init_offset =
162                self.props.offset % self.fs.cluster_size() / u32::from(self.fs.sector_size());
163            let first_sector_of_cluster = self
164                .fs
165                .data_cluster_to_partition_sector(self.props.current_cluster)
166                + sector_init_offset;
167            let last_sector_of_cluster = first_sector_of_cluster
168                + SectorCount::from(self.fs.sectors_per_cluster())
169                - sector_init_offset
170                - 1;
171            log::debug!(
172                "Reading cluster {} from sectors {} to {}",
173                self.props.current_cluster,
174                first_sector_of_cluster,
175                last_sector_of_cluster
176            );
177
178            for sector in first_sector_of_cluster..=last_sector_of_cluster {
179                self.fs.load_nth_sector(sector)?;
180
181                let start_index = usize::try_from(self.props.offset % u32::from(self.fs.sector_size()))
182                    .expect("sector_size's upper limit is 2^16, within Rust's usize (Rust support 16, 32 and 64-bit archs)");
183                let bytes_to_read = cmp::min(
184                    read_cap - bytes_read,
185                    usize::from(self.fs.sector_size()) - start_index,
186                );
187                log::debug!(
188                    "Gonna read {bytes_to_read} bytes from sector {sector} starting at byte {start_index}"
189                );
190
191                buf[bytes_read..bytes_read + bytes_to_read].copy_from_slice(
192                    &self.fs.sector_buffer.borrow()[start_index..start_index + bytes_to_read],
193                );
194
195                bytes_read += bytes_to_read;
196                self.props.offset += FileSize::try_from(bytes_to_read).unwrap();
197
198                // if we have read as many bytes as we want...
199                if bytes_read >= read_cap {
200                    // ...but we must process get the next cluster for future uses,
201                    // we do that before breaking
202                    if self.props.offset % self.fs.cluster_size() == 0
203                        && self.props.offset < self.file_size
204                    {
205                        self.next_cluster()?;
206                    }
207
208                    break 'outer;
209                }
210            }
211
212            self.next_cluster()?;
213        }
214
215        Ok(bytes_read)
216    }
217}
218
219impl<S> Seek for ROFile<'_, S>
220where
221    S: Read + Seek,
222{
223    fn seek(&mut self, pos: SeekFrom) -> Result<u64, Self::Error> {
224        let mut offset = self.offset_from_seekfrom(pos);
225
226        // seek beyond EOF behaviour is implementation-defined,
227        // so we just move to EOF
228        offset = cmp::min(offset, self.file_size.into());
229
230        let offset = FileSize::try_from(offset)
231            .expect("file_size is u32, so offset must be able to fit in a u32 too");
232
233        log::trace!(
234            "Previous cursor offset is {}, new cursor offset is {}",
235            self.props.offset,
236            offset
237        );
238
239        use cmp::Ordering;
240        match offset.cmp(&self.props.offset) {
241            Ordering::Less => {
242                // here, we basically "rewind" back to the start of the file and then seek to where we want
243                // this of course has performance issues, so TODO: find a solution that is both memory & time efficient
244                // (perhaps we could follow a similar approach to elm-chan's FATFS, by using a cluster link map table, perhaps as an optional feature)
245                self.props.offset = 0;
246                self.props.current_cluster = self.data_cluster;
247                self.seek(SeekFrom::Start(offset.into()))?;
248            }
249            Ordering::Equal => (),
250            Ordering::Greater => {
251                for _ in self.props.offset / self.fs.cluster_size()..offset / self.fs.cluster_size()
252                {
253                    self.next_cluster()?;
254                }
255                self.props.offset = offset;
256            }
257        }
258
259        Ok(self.props.offset.into())
260    }
261}
262
263/// A read-write file within a FAT filesystem
264///
265/// The size of the file will be automatically adjusted
266/// if the cursor goes beyond EOF.
267///
268/// To reduce a file's size, use the [`truncate`](RWFile::truncate) method
269#[derive(Debug)]
270pub struct RWFile<'a, S>
271where
272    S: Read + Write + Seek,
273{
274    pub(crate) ro_file: ROFile<'a, S>,
275    /// Represents whether or not the file has been written to
276    pub(crate) entry_modified: bool,
277}
278
279impl<'a, S> From<ROFile<'a, S>> for RWFile<'a, S>
280where
281    S: Read + Write + Seek,
282{
283    fn from(value: ROFile<'a, S>) -> Self {
284        Self {
285            ro_file: value,
286            entry_modified: false,
287        }
288    }
289}
290
291impl<'a, S> ops::Deref for RWFile<'a, S>
292where
293    S: Read + Write + Seek,
294{
295    type Target = ROFile<'a, S>;
296
297    fn deref(&self) -> &Self::Target {
298        &self.ro_file
299    }
300}
301
302impl<S> ops::DerefMut for RWFile<'_, S>
303where
304    S: Read + Write + Seek,
305{
306    fn deref_mut(&mut self) -> &mut Self::Target {
307        &mut self.ro_file
308    }
309}
310
311// Constructors
312impl<'a, S> RWFile<'a, S>
313where
314    S: Read + Write + Seek,
315{
316    pub(crate) fn from_props(props: FileProps, fs: &'a FileSystem<S>) -> Self {
317        ROFile::from_props(props, fs).into()
318    }
319}
320
321// Public functions
322impl<S> RWFile<'_, S>
323where
324    S: Read + Write + Seek,
325{
326    /// Set the last accessed [`Date`] attribute of this file
327    pub fn set_accessed(&mut self, accessed: Date) {
328        self.accessed = Some(accessed);
329
330        self.entry_modified = true;
331    }
332
333    /// Set the creation [`DateTime`](PrimitiveDateTime) attributes of this file
334    pub fn set_created(&mut self, created: PrimitiveDateTime) {
335        self.created = Some(created);
336
337        self.entry_modified = true;
338    }
339
340    /// Set the last modified [`DateTime`](PrimitiveDateTime) attributes of this file
341    pub fn set_modified(&mut self, modified: PrimitiveDateTime) {
342        self.modified = modified;
343
344        self.entry_modified = true;
345    }
346
347    /// Truncates the file to the cursor position
348    pub fn truncate(&mut self) -> Result<(), <Self as ErrorType>::Error> {
349        let size = self.props.offset;
350
351        // looks like the new truncated size would be smaller than the current one, so we just return
352        if size.next_multiple_of(self.fs.props.cluster_size) >= self.file_size {
353            if size < self.file_size {
354                self.file_size = size;
355            }
356
357            return Ok(());
358        }
359
360        // we store the current offset for later use
361        let previous_offset = cmp::min(self.props.offset, size);
362
363        // we seek back to where the EOF will be
364        self.seek(SeekFrom::Start(size.into()))?;
365
366        // set what the new filesize will be
367        let previous_size = self.file_size;
368        self.file_size = size;
369
370        let mut next_cluster_option = self.get_next_cluster()?;
371
372        // we set the new last cluster in the chain to be EOF
373        self.ro_file
374            .fs
375            .write_nth_FAT_entry(self.ro_file.props.current_cluster, FATEntry::Eof)?;
376
377        // then, we set each cluster after the current one to EOF
378        while let Some(next_cluster) = next_cluster_option {
379            next_cluster_option = self.fs.get_next_cluster(next_cluster)?;
380
381            self.fs.write_nth_FAT_entry(next_cluster, FATEntry::Free)?;
382        }
383
384        // don't forget to seek back to where we started
385        self.seek(SeekFrom::Start(previous_offset.into()))?;
386
387        log::debug!(
388            "Successfully truncated file {} from {} to {} bytes",
389            self.path,
390            previous_size,
391            self.file_size
392        );
393
394        self.entry_modified = true;
395
396        Ok(())
397    }
398
399    /// Remove the current file from the [`FileSystem`]
400    pub fn remove(mut self) -> Result<(), <Self as ErrorType>::Error> {
401        // we begin by removing the corresponding entries...
402        self.ro_file
403            .fs
404            .remove_entry_chain(&self.ro_file.props.entry.chain)?;
405
406        // ... and then we free the data clusters
407
408        // rewind back to the start of the file
409        self.rewind()?;
410
411        let current_cluster = self.ro_file.props.current_cluster;
412        self.ro_file.fs.free_cluster_chain(current_cluster)?;
413
414        // we are removing the file, no reason to sync it back to the filesystem
415        // (apart from that, we also won't overwrite the UNUSED_ENTRY flag
416        // on our dir entry assigned by the remove_entry_chain call above
417        self.entry_modified = false;
418
419        Ok(())
420    }
421}
422
423// Private functions
424impl<S> RWFile<'_, S>
425where
426    S: Read + Write + Seek,
427{
428    fn sync_entry(&mut self) -> FSResult<(), S::Error> {
429        if self.entry_modified {
430            let direntry = FATDirEntry::from(MinProperties::from(self.props.entry.clone()));
431            let mut bytes = [0; DIRENTRY_SIZE];
432            bincode::encode_into_slice(direntry, &mut bytes, BINCODE_CONFIG)
433                .map_err(utils::bincode::map_err_enc)?;
434
435            let chain_start = self.props.entry.chain.location;
436            let file_name = self
437                .path()
438                .file_name()
439                .expect("This file name should be valid")
440                .to_owned();
441            // the first entry of the dirchain could belong to a LFNEntry, so we must handle that
442            let direntry_location = match num::NonZero::new(
443                EntryCount::from(calc_entries_needed(file_name, &self.fs.options.codepage)) - 1,
444            ) {
445                Some(nonzero) => {
446                    chain_start
447                        .nth_entry(self.fs, nonzero)?
448                        .ok_or(FSError::InternalFSError(
449                            InternalFSError::MalformedEntryChain,
450                        ))?
451                }
452                None => chain_start,
453            };
454
455            direntry_location.set_bytes(self.fs, bytes)?;
456
457            self.entry_modified = false;
458        }
459
460        Ok(())
461    }
462
463    #[inline]
464    fn _set_accessed(&mut self) {
465        if self.fs.options.update_file_fields {
466            let now = self.fs.options.clock.now();
467
468            if let Some(accessed) = &mut self.accessed {
469                *accessed = now.date();
470            }
471
472            self.entry_modified = true;
473        }
474    }
475
476    #[inline]
477    fn _set_modified(&mut self) {
478        if self.fs.options.update_file_fields {
479            let now = self.fs.options.clock.now();
480
481            if let Some(accessed) = &mut self.accessed {
482                *accessed = now.date();
483            }
484            self.modified = now;
485
486            self.entry_modified = true;
487        }
488    }
489}
490
491#[derive(Debug)]
492#[non_exhaustive] // TODO: see whether or not to keep this marked as non-exhaustive
493/// A [`RWFile`]-exclusive IO error struct
494pub enum RWFileError<I>
495where
496    I: Error,
497{
498    /// The underlying storage is full.
499    StorageFull,
500    /// An IO error occured
501    IOError(I),
502}
503
504impl<I> Error for RWFileError<I>
505where
506    I: Error,
507{
508    #[inline]
509    fn kind(&self) -> ErrorKind {
510        match self {
511            // TODO: when embedded-io adds a StorageFull variant, use that instead
512            Self::StorageFull => ErrorKind::OutOfMemory,
513            Self::IOError(err) => err.kind(),
514        }
515    }
516}
517
518impl<I> From<I> for RWFileError<I>
519where
520    I: Error,
521{
522    #[inline]
523    fn from(value: I) -> Self {
524        Self::IOError(value)
525    }
526}
527
528impl<S> ErrorType for RWFile<'_, S>
529where
530    S: Read + Write + Seek,
531{
532    type Error = RWFileError<S::Error>;
533}
534
535impl<S> Read for RWFile<'_, S>
536where
537    S: Read + Write + Seek,
538{
539    #[inline]
540    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
541        let res = self.ro_file.read(buf);
542
543        if res.is_ok() {
544            self._set_accessed()
545        };
546
547        res.map_err(|e| e.into())
548    }
549
550    #[inline]
551    fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), ReadExactError<Self::Error>> {
552        let res = self.ro_file.read_exact(buf);
553
554        if res.is_ok() {
555            self._set_accessed()
556        };
557
558        res.map_err(|e| match e {
559            ReadExactError::UnexpectedEof => ReadExactError::UnexpectedEof,
560            ReadExactError::Other(err) => ReadExactError::Other(err.into()),
561        })
562    }
563}
564
565impl<S> Write for RWFile<'_, S>
566where
567    S: Read + Write + Seek,
568{
569    fn write(&mut self, mut buf: &[u8]) -> Result<usize, Self::Error> {
570        let cur_offset = self.props.offset;
571
572        // seek beyond EOF behaviour is implementation-defined,
573        // so we just allocate the maximum possible space
574        if u64::try_from(buf.len()).unwrap_or(u64::MAX) > FileSize::MAX.into() {
575            log::warn!("a file can be up to 2^32 bytes long, can't have a file larger than that");
576
577            buf = &buf[..FileSize::MAX as usize];
578        };
579
580        // allocate clusters
581        self.seek(SeekFrom::Start(u64::from(cur_offset) + buf.len() as u64))?;
582        // rewind back to where we were
583        self.seek(SeekFrom::Start(cur_offset.into()))?;
584
585        let mut bytes_written = 0;
586
587        'outer: loop {
588            log::trace!(
589                "writing file data to cluster: {}",
590                self.props.current_cluster
591            );
592
593            let sector_init_offset =
594                self.props.offset % self.fs.cluster_size() / u32::from(self.fs.sector_size());
595            let first_sector_of_cluster = self
596                .fs
597                .data_cluster_to_partition_sector(self.props.current_cluster)
598                + sector_init_offset;
599            let last_sector_of_cluster = first_sector_of_cluster
600                + SectorCount::from(self.fs.sectors_per_cluster())
601                - sector_init_offset
602                - 1;
603            for sector in first_sector_of_cluster..=last_sector_of_cluster {
604                self.fs.load_nth_sector(sector)?;
605
606                let start_index = usize::try_from(self.props.offset % u32::from(self.fs.sector_size()))
607                    .expect("sector_size's upper limit is 2^16, within Rust's usize (Rust support 16, 32 and 64-bit archs)");
608
609                let bytes_to_write = cmp::min(
610                    buf.len() - bytes_written,
611                    usize::from(self.fs.sector_size()) - start_index,
612                );
613
614                self.fs.sector_buffer.borrow_mut()[start_index..start_index + bytes_to_write]
615                    .copy_from_slice(&buf[bytes_written..bytes_written + bytes_to_write]);
616                self.fs.set_modified();
617
618                bytes_written += bytes_to_write;
619                self.props.offset += FileSize::try_from(bytes_to_write).unwrap();
620
621                // if we have written as many bytes as we want...
622                if bytes_written >= buf.len() {
623                    // ...but we must process get the next cluster for future uses,
624                    // we do that before breaking
625                    if self.props.offset % self.fs.cluster_size() == 0 {
626                        self.next_cluster()?;
627                    }
628
629                    break 'outer;
630                }
631            }
632
633            self.next_cluster()?;
634        }
635
636        // we've written something at this point
637        self._set_modified();
638
639        Ok(bytes_written)
640    }
641
642    // everything is immediately written to the storage medium
643    fn flush(&mut self) -> Result<(), Self::Error> {
644        Ok(())
645    }
646}
647
648impl<S> Seek for RWFile<'_, S>
649where
650    S: Read + Write + Seek,
651{
652    fn seek(&mut self, pos: SeekFrom) -> Result<u64, Self::Error> {
653        let offset = self.offset_from_seekfrom(pos);
654
655        let offset = match FileSize::try_from(offset) {
656            Ok(offset) => offset,
657            Err(_) => {
658                log::warn!(
659                    "a file can be up to 2^32 bytes long, can't have a file larger than that"
660                );
661
662                FileSize::MAX
663            }
664        };
665
666        // in case the cursor goes beyond the EOF, allocate more clusters
667        if offset > self.file_size.next_multiple_of(self.fs.cluster_size()) {
668            let bytes_allocated = if self.file_size == 0 {
669                // even if the file size is zero, a file has a cluster already allocated
670                self.fs.props.cluster_size
671            } else {
672                self.file_size.next_multiple_of(self.fs.cluster_size())
673            };
674            let clusters_to_allocate = (offset - bytes_allocated).div_ceil(self.fs.cluster_size());
675            log::debug!("Seeking beyond EOF, allocating {clusters_to_allocate} more clusters");
676
677            let last_cluster_in_chain = self.last_cluster_in_chain()?;
678
679            // TODO: if possible, find how many clusters we successfully allocated
680            // and modify the file length accordingly
681            match self.fs.allocate_clusters(
682                num::NonZero::new(clusters_to_allocate).expect("This is greater than 1"),
683                Some(last_cluster_in_chain),
684            ) {
685                Ok(_) => (),
686                Err(_) => return Err(RWFileError::StorageFull),
687            };
688
689            self.file_size = offset;
690            log::debug!(
691                "New file size after reallocation is {} bytes",
692                self.file_size
693            );
694        }
695
696        self._set_accessed();
697        self.entry_modified = true;
698
699        self.ro_file.seek(pos).map_err(|e| e.into())
700    }
701}
702
703impl<S> Drop for RWFile<'_, S>
704where
705    S: Read + Write + Seek,
706{
707    fn drop(&mut self) {
708        // nothing to do if this errors out while dropping
709        let _ = self.sync_entry();
710    }
711}