embedded_fat/
volume.rs

1//! The Volume Manager implementation.
2//!
3//! The volume manager handles partitions and open files on a block device.
4
5use core::convert::TryFrom;
6use core::ops::ControlFlow;
7
8use crate::fat::{self, BlockCache, RESERVED_ENTRIES};
9
10use crate::filesystem::{
11    Attributes, ClusterId, DirEntry, Directory, DirectoryInfo, File, FileInfo, Mode,
12    SearchIdGenerator, TimeSource, ToShortFileName, MAX_FILE_SIZE,
13};
14use crate::{debug, Block, BlockCount, BlockDevice, BlockIdx, Error, VolumeType};
15use heapless::Vec;
16
17/// A `VolumeManager` wraps a block device and gives access to the FAT-formatted
18/// volumes within it.
19pub struct Volume<D, T, const MAX_DIRS: usize = 4, const MAX_FILES: usize = 4>
20where
21    D: BlockDevice,
22    T: TimeSource,
23    <D as BlockDevice>::Error: core::fmt::Debug,
24{
25    pub(crate) block_device: D,
26    pub(crate) time_source: T,
27    id_generator: SearchIdGenerator,
28    volume_type: VolumeType,
29    open_dirs: Vec<DirectoryInfo, MAX_DIRS>,
30    open_files: Vec<FileInfo, MAX_FILES>,
31}
32
33impl<D, T> Volume<D, T, 4, 4>
34where
35    D: BlockDevice,
36    T: TimeSource,
37    <D as BlockDevice>::Error: core::fmt::Debug,
38{
39    /// Create a new Volume Manager using a generic `BlockDevice`. From this
40    /// object we can open volumes (partitions) and with those we can open
41    /// files.
42    ///
43    /// This creates a `VolumeManager` with default values
44    /// MAX_DIRS = 4, MAX_FILES = 4, MAX_VOLUMES = 1. Call `VolumeManager::new_with_limits(block_device, time_source)`
45    /// if you need different limits.
46    pub async fn new(
47        block_device: D,
48        time_source: T,
49    ) -> Result<Volume<D, T, 4, 4>, Error<D::Error>> {
50        // Pick a random starting point for the IDs that's not zero, because
51        // zero doesn't stand out in the logs.
52        Self::new_with_limits(block_device, time_source, 5000).await
53    }
54}
55
56impl<D, T, const MAX_DIRS: usize, const MAX_FILES: usize> Volume<D, T, MAX_DIRS, MAX_FILES>
57where
58    D: BlockDevice,
59    T: TimeSource,
60    <D as BlockDevice>::Error: core::fmt::Debug,
61{
62    /// Create a new Volume Manager using a generic `BlockDevice`. From this
63    /// object we can open volumes (partitions) and with those we can open
64    /// files.
65    ///
66    /// You can also give an offset for all the IDs this volume manager
67    /// generates, which might help you find the IDs in your logs when
68    /// debugging.
69    pub async fn new_with_limits(
70        block_device: D,
71        time_source: T,
72        id_offset: u32,
73    ) -> Result<Volume<D, T, MAX_DIRS, MAX_FILES>, Error<D::Error>> {
74        debug!("Creating new embedded-sdmmc::VolumeManager");
75        let volume_type =
76            fat::parse_volume(&block_device, BlockIdx(0), block_device.num_blocks().await?).await?;
77        Ok(Volume {
78            block_device,
79            time_source,
80            id_generator: SearchIdGenerator::new(id_offset),
81            volume_type,
82            open_dirs: Vec::new(),
83            open_files: Vec::new(),
84        })
85    }
86
87    /// Temporarily get access to the underlying block device.
88    pub fn device(&mut self) -> &mut D {
89        &mut self.block_device
90    }
91
92    /// Open the volume's root directory.
93    ///
94    /// You can then read the directory entries with `iterate_dir`, or you can
95    /// use `open_file_in_dir`.
96    pub fn open_root_dir(&mut self) -> Result<Directory, Error<D::Error>> {
97        for dir in self.open_dirs.iter() {
98            if dir.cluster == ClusterId::ROOT_DIR {
99                return Err(Error::DirAlreadyOpen);
100            }
101        }
102
103        let directory_id = Directory(self.id_generator.get());
104        let dir_info = DirectoryInfo {
105            cluster: ClusterId::ROOT_DIR,
106            directory_id,
107        };
108
109        self.open_dirs
110            .push(dir_info)
111            .map_err(|_| Error::TooManyOpenDirs)?;
112
113        Ok(directory_id)
114    }
115
116    /// Open a directory.
117    ///
118    /// You can then read the directory entries with `iterate_dir` and `open_file_in_dir`.
119    ///
120    /// TODO: Work out how to prevent damage occuring to the file system while
121    /// this directory handle is open. In particular, stop this directory
122    /// being unlinked.
123    pub async fn open_dir<N>(
124        &mut self,
125        parent_dir: Directory,
126        name: N,
127    ) -> Result<Directory, Error<D::Error>>
128    where
129        N: ToShortFileName,
130    {
131        if self.open_dirs.is_full() {
132            return Err(Error::TooManyOpenDirs);
133        }
134
135        // Find dir by ID
136        let parent_dir_idx = self.get_dir_by_id(parent_dir)?;
137        let short_file_name = name.to_short_filename().map_err(Error::FilenameError)?;
138
139        // Open the directory
140        let parent_dir_info = &self.open_dirs[parent_dir_idx];
141        let dir_entry = match &self.volume_type {
142            VolumeType::Fat(fat) => {
143                fat.find_directory_entry(&self.block_device, parent_dir_info, &short_file_name)
144                    .await?
145            }
146        };
147
148        if !dir_entry.attributes.is_directory() {
149            return Err(Error::OpenedFileAsDir);
150        }
151
152        // Check it's not already open
153        for d in self.open_dirs.iter() {
154            if d.cluster == dir_entry.cluster {
155                return Err(Error::DirAlreadyOpen);
156            }
157        }
158
159        // Remember this open directory.
160        let directory_id = Directory(self.id_generator.get());
161        let dir_info = DirectoryInfo {
162            directory_id,
163            cluster: dir_entry.cluster,
164        };
165
166        self.open_dirs
167            .push(dir_info)
168            .map_err(|_| Error::TooManyOpenDirs)?;
169
170        Ok(directory_id)
171    }
172
173    /// Close a directory. You cannot perform operations on an open directory
174    /// and so must close it if you want to do something with it.
175    pub fn close_dir(&mut self, directory: Directory) -> Result<(), Error<D::Error>> {
176        for (idx, info) in self.open_dirs.iter().enumerate() {
177            if directory == info.directory_id {
178                self.open_dirs.swap_remove(idx);
179                return Ok(());
180            }
181        }
182        Err(Error::BadHandle)
183    }
184
185    /// Look in a directory for a named file.
186    pub async fn find_directory_entry<N>(
187        &mut self,
188        directory: Directory,
189        name: N,
190    ) -> Result<DirEntry, Error<D::Error>>
191    where
192        N: ToShortFileName,
193    {
194        let directory_idx = self.get_dir_by_id(directory)?;
195        match &self.volume_type {
196            VolumeType::Fat(fat) => {
197                let sfn = name.to_short_filename().map_err(Error::FilenameError)?;
198                fat.find_directory_entry(&self.block_device, &self.open_dirs[directory_idx], &sfn)
199                    .await
200            }
201        }
202    }
203
204    #[allow(missing_docs)]
205    pub async fn find_lfn_directory_entry(
206        &mut self,
207        directory: Directory,
208        name: &str,
209    ) -> Result<DirEntry, Error<D::Error>> {
210        let directory_idx = self.get_dir_by_id(directory)?;
211        match &self.volume_type {
212            VolumeType::Fat(fat) => {
213                fat.find_lfn_directory_entry(
214                    &self.block_device,
215                    &self.open_dirs[directory_idx],
216                    &name,
217                )
218                .await
219            }
220        }
221    }
222
223    /// Call a callback function for each directory entry in a directory.
224    pub async fn iterate_dir<F, U>(
225        &mut self,
226        directory: Directory,
227        func: F,
228    ) -> Result<Option<U>, Error<D::Error>>
229    where
230        F: FnMut(&DirEntry) -> ControlFlow<U>,
231    {
232        let directory_idx = self.get_dir_by_id(directory)?;
233        match &self.volume_type {
234            VolumeType::Fat(fat) => {
235                fat.iterate_dir(&self.block_device, &self.open_dirs[directory_idx], func)
236                    .await
237            }
238        }
239    }
240
241    /// Call a callback function for each directory entry in a directory, with its LFN if it has one.
242    pub async fn iterate_lfn_dir<F, U>(
243        &mut self,
244        directory: Directory,
245        func: F,
246    ) -> Result<Option<U>, Error<D::Error>>
247    where
248        F: FnMut(Option<&str>, &DirEntry) -> ControlFlow<U>,
249    {
250        let directory_idx = self.get_dir_by_id(directory)?;
251        match &self.volume_type {
252            VolumeType::Fat(fat) => {
253                fat.iterate_lfn_dir(&self.block_device, &self.open_dirs[directory_idx], func)
254                    .await
255            }
256        }
257    }
258
259    /// Open a file from a DirEntry. This is obtained by calling iterate_dir.
260    ///
261    /// # Safety
262    ///
263    /// The DirEntry must be a valid DirEntry read from disk, and not just
264    /// random numbers.
265    async unsafe fn open_dir_entry(
266        &mut self,
267        dir_entry: DirEntry,
268        mode: Mode,
269    ) -> Result<File, Error<D::Error>> {
270        // This check is load-bearing - we do an unchecked push later.
271        if self.open_files.is_full() {
272            return Err(Error::TooManyOpenFiles);
273        }
274
275        if dir_entry.attributes.is_read_only() && mode != Mode::ReadOnly {
276            return Err(Error::ReadOnly);
277        }
278
279        if dir_entry.attributes.is_directory() {
280            return Err(Error::OpenedDirAsFile);
281        }
282
283        // Check it's not already open
284        if self.file_is_open(&dir_entry) {
285            return Err(Error::FileAlreadyOpen);
286        }
287
288        let mode = solve_mode_variant(mode, true);
289        let file_id = File(self.id_generator.get());
290
291        let file = match mode {
292            Mode::ReadOnly => FileInfo {
293                file_id,
294                current_cluster: (0, dir_entry.cluster),
295                current_offset: 0,
296                mode,
297                entry: dir_entry,
298                dirty: false,
299            },
300            Mode::ReadWriteAppend => {
301                let mut file = FileInfo {
302                    file_id,
303                    current_cluster: (0, dir_entry.cluster),
304                    current_offset: 0,
305                    mode,
306                    entry: dir_entry,
307                    dirty: false,
308                };
309                // seek_from_end with 0 can't fail
310                file.seek_from_end(0).ok();
311                file
312            }
313            Mode::ReadWriteTruncate => {
314                let mut file = FileInfo {
315                    file_id,
316                    current_cluster: (0, dir_entry.cluster),
317                    current_offset: 0,
318                    mode,
319                    entry: dir_entry,
320                    dirty: false,
321                };
322                match &mut self.volume_type {
323                    VolumeType::Fat(fat) => {
324                        fat.truncate_cluster_chain(&self.block_device, file.entry.cluster)
325                            .await?
326                    }
327                };
328                file.update_length(0);
329                match &self.volume_type {
330                    VolumeType::Fat(fat) => {
331                        file.entry.mtime = self.time_source.get_timestamp();
332                        let fat_type = fat.get_fat_type();
333                        self.write_entry_to_disk(fat_type, &file.entry).await?;
334                    }
335                };
336
337                file
338            }
339            _ => return Err(Error::Unsupported),
340        };
341
342        // Remember this open file - can't be full as we checked already
343        unsafe {
344            self.open_files.push_unchecked(file);
345        }
346
347        Ok(file_id)
348    }
349
350    /// Open a file with the given full path. A file can only be opened once.
351    pub async fn open_file_in_dir<N>(
352        &mut self,
353        directory: Directory,
354        name: N,
355        mode: Mode,
356    ) -> Result<File, Error<D::Error>>
357    where
358        N: ToShortFileName,
359    {
360        // This check is load-bearing - we do an unchecked push later.
361        if self.open_files.is_full() {
362            return Err(Error::TooManyOpenFiles);
363        }
364
365        let directory_idx = self.get_dir_by_id(directory)?;
366        let directory_info = &self.open_dirs[directory_idx];
367        let sfn = name.to_short_filename().map_err(Error::FilenameError)?;
368
369        let dir_entry = match &self.volume_type {
370            VolumeType::Fat(fat) => {
371                fat.find_directory_entry(&self.block_device, directory_info, &sfn)
372                    .await
373            }
374        };
375
376        let dir_entry = match dir_entry {
377            Ok(entry) => {
378                // we are opening an existing file
379                Some(entry)
380            }
381            Err(_)
382                if (mode == Mode::ReadWriteCreate)
383                    | (mode == Mode::ReadWriteCreateOrTruncate)
384                    | (mode == Mode::ReadWriteCreateOrAppend) =>
385            {
386                // We are opening a non-existant file, but that's OK because they
387                // asked us to create it
388                None
389            }
390            _ => {
391                // We are opening a non-existant file, and that's not OK.
392                return Err(Error::FileNotFound);
393            }
394        };
395
396        // Check if it's open already
397        if let Some(dir_entry) = &dir_entry {
398            if self.file_is_open(&dir_entry) {
399                return Err(Error::FileAlreadyOpen);
400            }
401        }
402
403        let mode = solve_mode_variant(mode, dir_entry.is_some());
404
405        match mode {
406            Mode::ReadWriteCreate => {
407                if dir_entry.is_some() {
408                    return Err(Error::FileAlreadyExists);
409                }
410                let att = Attributes::create_from_fat(0);
411                let entry = match &mut self.volume_type {
412                    VolumeType::Fat(fat) => {
413                        fat.write_new_directory_entry(
414                            &self.block_device,
415                            &self.time_source,
416                            directory_info,
417                            sfn,
418                            att,
419                        )
420                        .await?
421                    }
422                };
423
424                let file_id = File(self.id_generator.get());
425
426                let file = FileInfo {
427                    file_id,
428                    current_cluster: (0, entry.cluster),
429                    current_offset: 0,
430                    mode,
431                    entry,
432                    dirty: false,
433                };
434
435                // Remember this open file - can't be full as we checked already
436                unsafe {
437                    self.open_files.push_unchecked(file);
438                }
439
440                Ok(file_id)
441            }
442            _ => {
443                // Safe to unwrap, since we actually have an entry if we got here
444                let dir_entry = dir_entry.unwrap();
445                // Safety: We read this dir entry off disk and didn't change it
446                unsafe { self.open_dir_entry(dir_entry, mode).await }
447            }
448        }
449    }
450
451    /// Delete a closed file with the given filename, if it exists.
452    pub async fn delete_file_in_dir<N>(
453        &mut self,
454        directory: Directory,
455        name: N,
456    ) -> Result<(), Error<D::Error>>
457    where
458        N: ToShortFileName,
459    {
460        let dir_idx = self.get_dir_by_id(directory)?;
461        let dir_info = &self.open_dirs[dir_idx];
462        let sfn = name.to_short_filename().map_err(Error::FilenameError)?;
463
464        let dir_entry = match &self.volume_type {
465            VolumeType::Fat(fat) => {
466                fat.find_directory_entry(&self.block_device, dir_info, &sfn)
467                    .await
468            }
469        }?;
470
471        if dir_entry.attributes.is_directory() {
472            return Err(Error::DeleteDirAsFile);
473        }
474
475        if self.file_is_open(&dir_entry) {
476            return Err(Error::FileAlreadyOpen);
477        }
478
479        match &self.volume_type {
480            VolumeType::Fat(fat) => {
481                fat.delete_directory_entry(&self.block_device, dir_info, &sfn)
482                    .await?
483            }
484        }
485
486        Ok(())
487    }
488
489    /// Check if a file is open
490    ///
491    /// Returns `true` if it's open, `false`, otherwise.
492    fn file_is_open(&self, dir_entry: &DirEntry) -> bool {
493        for f in self.open_files.iter() {
494            if f.entry.entry_block == dir_entry.entry_block
495                && f.entry.entry_offset == dir_entry.entry_offset
496            {
497                return true;
498            }
499        }
500        false
501    }
502
503    /// Read from an open file.
504    pub async fn read(&mut self, file: File, buffer: &mut [u8]) -> Result<usize, Error<D::Error>> {
505        let file_idx = self.get_file_by_id(file)?;
506        // Calculate which file block the current offset lies within
507        // While there is more to read, read the block and copy in to the buffer.
508        // If we need to find the next cluster, walk the FAT.
509        let mut space = buffer.len();
510        let mut read = 0;
511        while space > 0 && !self.open_files[file_idx].eof() {
512            let mut current_cluster = self.open_files[file_idx].current_cluster;
513            let (block_idx, block_offset, block_avail) = self
514                .find_data_on_disk(
515                    &mut current_cluster,
516                    self.open_files[file_idx].current_offset,
517                )
518                .await?;
519            self.open_files[file_idx].current_cluster = current_cluster;
520            let mut blocks = [Block::new()];
521            self.block_device
522                .read(&mut blocks, block_idx, "read")
523                .await
524                .map_err(Error::DeviceError)?;
525            let block = &blocks[0];
526            let to_copy = block_avail
527                .min(space)
528                .min(self.open_files[file_idx].left() as usize);
529            assert!(to_copy != 0);
530            buffer[read..read + to_copy]
531                .copy_from_slice(&block[block_offset..block_offset + to_copy]);
532            read += to_copy;
533            space -= to_copy;
534            self.open_files[file_idx]
535                .seek_from_current(to_copy as i32)
536                .unwrap();
537        }
538        Ok(read)
539    }
540
541    /// Write to a open file.
542    pub async fn write(&mut self, file: File, buffer: &[u8]) -> Result<usize, Error<D::Error>> {
543        #[cfg(feature = "defmt-log")]
544        debug!("write(file={:?}, buffer={:x}", file, buffer);
545
546        #[cfg(feature = "log")]
547        debug!("write(file={:?}, buffer={:x?}", file, buffer);
548
549        // Clone this so we can touch our other structures. Need to ensure we
550        // write it back at the end.
551        let file_idx = self.get_file_by_id(file)?;
552
553        if self.open_files[file_idx].mode == Mode::ReadOnly {
554            return Err(Error::ReadOnly);
555        }
556
557        self.open_files[file_idx].dirty = true;
558
559        if self.open_files[file_idx].entry.cluster.0 < RESERVED_ENTRIES {
560            // file doesn't have a valid allocated cluster (possible zero-length file), allocate one
561            self.open_files[file_idx].entry.cluster = match self.volume_type {
562                VolumeType::Fat(ref mut fat) => {
563                    fat.alloc_cluster(&self.block_device, None, false).await?
564                }
565            };
566            debug!(
567                "Alloc first cluster {:?}",
568                self.open_files[file_idx].entry.cluster
569            );
570        }
571
572        if (self.open_files[file_idx].current_cluster.1) < self.open_files[file_idx].entry.cluster {
573            debug!("Rewinding to start");
574            self.open_files[file_idx].current_cluster =
575                (0, self.open_files[file_idx].entry.cluster);
576        }
577        let bytes_until_max =
578            usize::try_from(MAX_FILE_SIZE - self.open_files[file_idx].current_offset)
579                .map_err(|_| Error::ConversionError)?;
580        let bytes_to_write = core::cmp::min(buffer.len(), bytes_until_max);
581        let mut written = 0;
582
583        while written < bytes_to_write {
584            let mut current_cluster = self.open_files[file_idx].current_cluster;
585            debug!(
586                "Have written bytes {}/{}, finding cluster {:?}",
587                written, bytes_to_write, current_cluster
588            );
589            let current_offset = self.open_files[file_idx].current_offset;
590            let (block_idx, block_offset, block_avail) = match self
591                .find_data_on_disk(&mut current_cluster, current_offset)
592                .await
593            {
594                Ok(vars) => {
595                    debug!(
596                        "Found block_idx={:?}, block_offset={:?}, block_avail={}",
597                        vars.0, vars.1, vars.2
598                    );
599                    vars
600                }
601                Err(Error::EndOfFile) => {
602                    debug!("Extending file");
603                    match self.volume_type {
604                        VolumeType::Fat(ref mut fat) => {
605                            if fat
606                                .alloc_cluster(&self.block_device, Some(current_cluster.1), false)
607                                .await
608                                .is_err()
609                            {
610                                return Ok(written);
611                            }
612                            debug!("Allocated new FAT cluster, finding offsets...");
613                            let new_offset = self
614                                .find_data_on_disk(
615                                    &mut current_cluster,
616                                    self.open_files[file_idx].current_offset,
617                                )
618                                .await
619                                .map_err(|_| Error::AllocationError)?;
620                            debug!("New offset {:?}", new_offset);
621                            new_offset
622                        }
623                    }
624                }
625                Err(e) => return Err(e),
626            };
627            let mut blocks = [Block::new()];
628            let to_copy = core::cmp::min(block_avail, bytes_to_write - written);
629            if block_offset != 0 {
630                debug!("Partial block write");
631                self.block_device
632                    .read(&mut blocks, block_idx, "read")
633                    .await
634                    .map_err(Error::DeviceError)?;
635            }
636            let block = &mut blocks[0];
637            block[block_offset..block_offset + to_copy]
638                .copy_from_slice(&buffer[written..written + to_copy]);
639            debug!("Writing block {:?}", block_idx);
640            self.block_device
641                .write(&blocks, block_idx)
642                .await
643                .map_err(Error::DeviceError)?;
644            written += to_copy;
645            self.open_files[file_idx].current_cluster = current_cluster;
646
647            let to_copy = to_copy as u32;
648            let new_offset = self.open_files[file_idx].current_offset + to_copy;
649            if new_offset > self.open_files[file_idx].entry.size {
650                // We made it longer
651                self.open_files[file_idx].update_length(new_offset);
652            }
653            self.open_files[file_idx]
654                .seek_from_start(new_offset)
655                .unwrap();
656            // Entry update deferred to file close, for performance.
657        }
658        self.open_files[file_idx].entry.attributes.set_archive(true);
659        self.open_files[file_idx].entry.mtime = self.time_source.get_timestamp();
660        Ok(written)
661    }
662
663    /// Close a file with the given full path.
664    pub async fn close_file(&mut self, file: File) -> Result<(), Error<D::Error>> {
665        let mut found_idx = None;
666        for (idx, info) in self.open_files.iter().enumerate() {
667            if file == info.file_id {
668                found_idx = Some((info, idx));
669                break;
670            }
671        }
672
673        let (file_info, file_idx) = found_idx.ok_or(Error::BadHandle)?;
674
675        if file_info.dirty {
676            match self.volume_type {
677                VolumeType::Fat(ref mut fat) => {
678                    debug!("Updating FAT info sector");
679                    fat.update_info_sector(&self.block_device).await?;
680                    debug!("Updating dir entry {:?}", file_info.entry);
681                    if file_info.entry.size != 0 {
682                        // If you have a length, you must have a cluster
683                        assert!(file_info.entry.cluster.0 != 0);
684                    }
685                    let fat_type = fat.get_fat_type();
686                    self.write_entry_to_disk(fat_type, &file_info.entry).await?;
687                }
688            };
689        }
690
691        self.open_files.swap_remove(file_idx);
692        Ok(())
693    }
694
695    /// Check if any files or folders are open.
696    pub fn has_open_handles(&self) -> bool {
697        !(self.open_dirs.is_empty() || self.open_files.is_empty())
698    }
699
700    /// Consume self and return BlockDevice and TimeSource
701    pub fn free(self) -> (D, T) {
702        (self.block_device, self.time_source)
703    }
704
705    /// Check if a file is at End Of File.
706    pub fn file_eof(&self, file: File) -> Result<bool, Error<D::Error>> {
707        let file_idx = self.get_file_by_id(file)?;
708        Ok(self.open_files[file_idx].eof())
709    }
710
711    /// Seek a file with an offset from the start of the file.
712    pub fn file_seek_from_start(&mut self, file: File, offset: u32) -> Result<(), Error<D::Error>> {
713        let file_idx = self.get_file_by_id(file)?;
714        self.open_files[file_idx]
715            .seek_from_start(offset)
716            .map_err(|_| Error::InvalidOffset)?;
717        Ok(())
718    }
719
720    /// Seek a file with an offset from the current position.
721    pub fn file_seek_from_current(
722        &mut self,
723        file: File,
724        offset: i32,
725    ) -> Result<(), Error<D::Error>> {
726        let file_idx = self.get_file_by_id(file)?;
727        self.open_files[file_idx]
728            .seek_from_current(offset)
729            .map_err(|_| Error::InvalidOffset)?;
730        Ok(())
731    }
732
733    /// Seek a file with an offset back from the end of the file.
734    pub fn file_seek_from_end(&mut self, file: File, offset: u32) -> Result<(), Error<D::Error>> {
735        let file_idx = self.get_file_by_id(file)?;
736        self.open_files[file_idx]
737            .seek_from_end(offset)
738            .map_err(|_| Error::InvalidOffset)?;
739        Ok(())
740    }
741
742    /// Get the length of a file
743    pub fn file_length(&self, file: File) -> Result<u32, Error<D::Error>> {
744        let file_idx = self.get_file_by_id(file)?;
745        Ok(self.open_files[file_idx].length())
746    }
747
748    /// Get the current offset of a file
749    pub fn file_offset(&self, file: File) -> Result<u32, Error<D::Error>> {
750        let file_idx = self.get_file_by_id(file)?;
751        Ok(self.open_files[file_idx].current_offset)
752    }
753
754    fn get_dir_by_id(&self, directory: Directory) -> Result<usize, Error<D::Error>> {
755        for (idx, d) in self.open_dirs.iter().enumerate() {
756            if d.directory_id == directory {
757                return Ok(idx);
758            }
759        }
760        Err(Error::BadHandle)
761    }
762
763    fn get_file_by_id(&self, file: File) -> Result<usize, Error<D::Error>> {
764        for (idx, f) in self.open_files.iter().enumerate() {
765            if f.file_id == file {
766                return Ok(idx);
767            }
768        }
769        Err(Error::BadHandle)
770    }
771
772    /// This function turns `desired_offset` into an appropriate block to be
773    /// read. It either calculates this based on the start of the file, or
774    /// from the last cluster we read - whichever is better.
775    async fn find_data_on_disk(
776        &self,
777        start: &mut (u32, ClusterId),
778        desired_offset: u32,
779    ) -> Result<(BlockIdx, usize, usize), Error<D::Error>> {
780        let bytes_per_cluster = match &self.volume_type {
781            VolumeType::Fat(fat) => fat.bytes_per_cluster(),
782        };
783        // How many clusters forward do we need to go?
784        let offset_from_cluster = desired_offset - start.0;
785        let num_clusters = offset_from_cluster / bytes_per_cluster;
786        let mut block_cache = BlockCache::empty();
787        for _ in 0..num_clusters {
788            start.1 = match &self.volume_type {
789                VolumeType::Fat(fat) => {
790                    fat.next_cluster(&self.block_device, start.1, &mut block_cache)
791                        .await?
792                }
793            };
794            start.0 += bytes_per_cluster;
795        }
796        // How many blocks in are we?
797        let offset_from_cluster = desired_offset - start.0;
798        assert!(offset_from_cluster < bytes_per_cluster);
799        let num_blocks = BlockCount(offset_from_cluster / Block::LEN_U32);
800        let block_idx = match &self.volume_type {
801            VolumeType::Fat(fat) => fat.cluster_to_block(start.1),
802        } + num_blocks;
803        let block_offset = (desired_offset % Block::LEN_U32) as usize;
804        let available = Block::LEN - block_offset;
805        Ok((block_idx, block_offset, available))
806    }
807
808    /// Writes a Directory Entry to the disk
809    async fn write_entry_to_disk(
810        &self,
811        fat_type: fat::FatType,
812        entry: &DirEntry,
813    ) -> Result<(), Error<D::Error>> {
814        let mut blocks = [Block::new()];
815        self.block_device
816            .read(&mut blocks, entry.entry_block, "read")
817            .await
818            .map_err(Error::DeviceError)?;
819        let block = &mut blocks[0];
820
821        let start = usize::try_from(entry.entry_offset).map_err(|_| Error::ConversionError)?;
822        block[start..start + 32].copy_from_slice(&entry.serialize(fat_type)[..]);
823
824        self.block_device
825            .write(&blocks, entry.entry_block)
826            .await
827            .map_err(Error::DeviceError)?;
828        Ok(())
829    }
830}
831
832/// Transform mode variants (ReadWriteCreate_Or_Append) to simple modes ReadWriteAppend or
833/// ReadWriteCreate
834fn solve_mode_variant(mode: Mode, dir_entry_is_some: bool) -> Mode {
835    let mut mode = mode;
836    if mode == Mode::ReadWriteCreateOrAppend {
837        if dir_entry_is_some {
838            mode = Mode::ReadWriteAppend;
839        } else {
840            mode = Mode::ReadWriteCreate;
841        }
842    } else if mode == Mode::ReadWriteCreateOrTruncate {
843        if dir_entry_is_some {
844            mode = Mode::ReadWriteTruncate;
845        } else {
846            mode = Mode::ReadWriteCreate;
847        }
848    }
849    mode
850}
851
852// ****************************************************************************
853//
854// Unit Tests
855//
856// ****************************************************************************
857
858// None