embedded_sdmmc/
lib.rs

1//! embedded-sdmmc-rs - A SD/MMC Library written in Embedded Rust
2
3#![cfg_attr(not(test), no_std)]
4#![allow(dead_code)]
5#![deny(missing_docs)]
6
7// ****************************************************************************
8//
9// Imports
10//
11// ****************************************************************************
12
13use byteorder::{ByteOrder, LittleEndian};
14use core::convert::TryFrom;
15use log::debug;
16
17#[macro_use]
18mod structure;
19
20mod blockdevice;
21mod fat;
22mod filesystem;
23mod sdmmc;
24mod sdmmc_proto;
25
26pub use crate::blockdevice::{Block, BlockCount, BlockDevice, BlockIdx};
27pub use crate::fat::FatVolume;
28use crate::fat::RESERVED_ENTRIES;
29pub use crate::filesystem::{
30    Attributes, Cluster, DirEntry, Directory, File, FilenameError, Mode, ShortFileName, TimeSource,
31    Timestamp, MAX_FILE_SIZE,
32};
33pub use crate::sdmmc::Error as SdMmcError;
34pub use crate::sdmmc::SdMmcSpi;
35
36// ****************************************************************************
37//
38// Public Types
39//
40// ****************************************************************************
41
42/// Represents all the ways the functions in this crate can fail.
43#[derive(Debug, Clone)]
44pub enum Error<E>
45where
46    E: core::fmt::Debug,
47{
48    /// The underlying block device threw an error.
49    DeviceError(E),
50    /// The filesystem is badly formatted (or this code is buggy).
51    FormatError(&'static str),
52    /// The given `VolumeIdx` was bad,
53    NoSuchVolume,
54    /// The given filename was bad
55    FilenameError(FilenameError),
56    /// Out of memory opening directories
57    TooManyOpenDirs,
58    /// Out of memory opening files
59    TooManyOpenFiles,
60    /// That file doesn't exist
61    FileNotFound,
62    /// You can't open a file twice
63    FileAlreadyOpen,
64    /// You can't open a directory twice
65    DirAlreadyOpen,
66    /// You can't open a directory as a file
67    OpenedDirAsFile,
68    /// We can't do that yet
69    Unsupported,
70    /// Tried to read beyond end of file
71    EndOfFile,
72    /// Found a bad cluster
73    BadCluster,
74    /// Error while converting types
75    ConversionError,
76    /// The device does not have enough space for the operation
77    NotEnoughSpace,
78    /// Cluster was not properly allocated by the library
79    AllocationError,
80    /// Jumped to free space during fat traversing
81    JumpedFree,
82    /// Tried to open Read-Only file with write mode
83    ReadOnly,
84    /// Tried to create an existing file
85    FileAlreadyExists,
86}
87
88/// We have to track what directories are open to prevent users from modifying
89/// open directories (like creating a file when we have an open iterator).
90pub const MAX_OPEN_DIRS: usize = 4;
91
92/// We have to track what files and directories are open to prevent users from
93/// deleting open files (like Windows does).
94pub const MAX_OPEN_FILES: usize = 4;
95
96/// A `Controller` wraps a block device and gives access to the volumes within it.
97pub struct Controller<D, T>
98where
99    D: BlockDevice,
100    T: TimeSource,
101    <D as BlockDevice>::Error: core::fmt::Debug,
102{
103    block_device: D,
104    timesource: T,
105    open_dirs: [(VolumeIdx, Cluster); MAX_OPEN_DIRS],
106    open_files: [(VolumeIdx, Cluster); MAX_OPEN_DIRS],
107}
108
109/// Represents a partition with a filesystem within it.
110#[derive(Debug, PartialEq, Eq)]
111pub struct Volume {
112    idx: VolumeIdx,
113    volume_type: VolumeType,
114}
115
116/// This enum holds the data for the various different types of filesystems we
117/// support.
118#[derive(Debug, PartialEq, Eq)]
119pub enum VolumeType {
120    /// FAT16/FAT32 formatted volumes.
121    Fat(FatVolume),
122}
123
124/// A `VolumeIdx` is a number which identifies a volume (or partition) on a
125/// disk. `VolumeIdx(0)` is the first primary partition on an MBR partitioned
126/// disk.
127#[derive(Debug, PartialEq, Eq, Copy, Clone)]
128pub struct VolumeIdx(pub usize);
129
130// ****************************************************************************
131//
132// Public Data
133//
134// ****************************************************************************
135
136// None
137
138// ****************************************************************************
139//
140// Private Types
141//
142// ****************************************************************************
143
144/// Marker for a FAT32 partition. Sometimes also use for FAT16 formatted
145/// partitions.
146const PARTITION_ID_FAT32_LBA: u8 = 0x0C;
147/// Marker for a FAT16 partition with LBA. Seen on a Raspberry Pi SD card.
148const PARTITION_ID_FAT16_LBA: u8 = 0x0E;
149/// Marker for a FAT16 partition. Seen on a card formatted with the official
150/// SD-Card formatter.
151const PARTITION_ID_FAT16: u8 = 0x06;
152/// Marker for a FAT32 partition. What Macosx disk utility (and also SD-Card formatter?)
153/// use.
154const PARTITION_ID_FAT32_CHS_LBA: u8 = 0x0B;
155
156// ****************************************************************************
157//
158// Private Data
159//
160// ****************************************************************************
161
162// None
163
164// ****************************************************************************
165//
166// Public Functions / Impl for Public Types
167//
168// ****************************************************************************
169
170impl<D, T> Controller<D, T>
171where
172    D: BlockDevice,
173    T: TimeSource,
174    <D as BlockDevice>::Error: core::fmt::Debug,
175{
176    /// Create a new Disk Controller using a generic `BlockDevice`. From this
177    /// controller we can open volumes (partitions) and with those we can open
178    /// files.
179    pub fn new(block_device: D, timesource: T) -> Controller<D, T> {
180        debug!("Creating new embedded-sdmmc::Controller");
181        Controller {
182            block_device,
183            timesource,
184            open_dirs: [(VolumeIdx(0), Cluster::INVALID); 4],
185            open_files: [(VolumeIdx(0), Cluster::INVALID); 4],
186        }
187    }
188
189    /// Temporarily get access to the underlying block device.
190    pub fn device(&mut self) -> &mut D {
191        &mut self.block_device
192    }
193
194    /// Get a volume (or partition) based on entries in the Master Boot
195    /// Record. We do not support GUID Partition Table disks. Nor do we
196    /// support any concept of drive letters - that is for a higher layer to
197    /// handle.
198    pub fn get_volume(&mut self, volume_idx: VolumeIdx) -> Result<Volume, Error<D::Error>> {
199        const PARTITION1_START: usize = 446;
200        const PARTITION2_START: usize = PARTITION1_START + PARTITION_INFO_LENGTH;
201        const PARTITION3_START: usize = PARTITION2_START + PARTITION_INFO_LENGTH;
202        const PARTITION4_START: usize = PARTITION3_START + PARTITION_INFO_LENGTH;
203        const FOOTER_START: usize = 510;
204        const FOOTER_VALUE: u16 = 0xAA55;
205        const PARTITION_INFO_LENGTH: usize = 16;
206        const PARTITION_INFO_STATUS_INDEX: usize = 0;
207        const PARTITION_INFO_TYPE_INDEX: usize = 4;
208        const PARTITION_INFO_LBA_START_INDEX: usize = 8;
209        const PARTITION_INFO_NUM_BLOCKS_INDEX: usize = 12;
210
211        let (part_type, lba_start, num_blocks) = {
212            let mut blocks = [Block::new()];
213            self.block_device
214                .read(&mut blocks, BlockIdx(0), "read_mbr")
215                .map_err(Error::DeviceError)?;
216            let block = &blocks[0];
217            // We only support Master Boot Record (MBR) partitioned cards, not
218            // GUID Partition Table (GPT)
219            if LittleEndian::read_u16(&block[FOOTER_START..FOOTER_START + 2]) != FOOTER_VALUE {
220                return Err(Error::FormatError("Invalid MBR signature"));
221            }
222            let partition = match volume_idx {
223                VolumeIdx(0) => {
224                    &block[PARTITION1_START..(PARTITION1_START + PARTITION_INFO_LENGTH)]
225                }
226                VolumeIdx(1) => {
227                    &block[PARTITION2_START..(PARTITION2_START + PARTITION_INFO_LENGTH)]
228                }
229                VolumeIdx(2) => {
230                    &block[PARTITION3_START..(PARTITION3_START + PARTITION_INFO_LENGTH)]
231                }
232                VolumeIdx(3) => {
233                    &block[PARTITION4_START..(PARTITION4_START + PARTITION_INFO_LENGTH)]
234                }
235                _ => {
236                    return Err(Error::NoSuchVolume);
237                }
238            };
239            // Only 0x80 and 0x00 are valid (bootable, and non-bootable)
240            if (partition[PARTITION_INFO_STATUS_INDEX] & 0x7F) != 0x00 {
241                return Err(Error::FormatError("Invalid partition status"));
242            }
243            let lba_start = LittleEndian::read_u32(
244                &partition[PARTITION_INFO_LBA_START_INDEX..(PARTITION_INFO_LBA_START_INDEX + 4)],
245            );
246            let num_blocks = LittleEndian::read_u32(
247                &partition[PARTITION_INFO_NUM_BLOCKS_INDEX..(PARTITION_INFO_NUM_BLOCKS_INDEX + 4)],
248            );
249            (
250                partition[PARTITION_INFO_TYPE_INDEX],
251                BlockIdx(lba_start),
252                BlockCount(num_blocks),
253            )
254        };
255        match part_type {
256            PARTITION_ID_FAT32_CHS_LBA
257            | PARTITION_ID_FAT32_LBA
258            | PARTITION_ID_FAT16_LBA
259            | PARTITION_ID_FAT16 => {
260                let volume = fat::parse_volume(self, lba_start, num_blocks)?;
261                Ok(Volume {
262                    idx: volume_idx,
263                    volume_type: volume,
264                })
265            }
266            _ => Err(Error::FormatError("Partition type not supported")),
267        }
268    }
269
270    /// Open a directory. You can then read the directory entries in a random
271    /// order using `get_directory_entry`.
272    ///
273    /// TODO: Work out how to prevent damage occuring to the file system while
274    /// this directory handle is open. In particular, stop this directory
275    /// being unlinked.
276    pub fn open_root_dir(&mut self, volume: &Volume) -> Result<Directory, Error<D::Error>> {
277        // Find a free directory entry, and check the root dir isn't open. As
278        // we already know the root dir's magic cluster number, we can do both
279        // checks in one loop.
280        let mut open_dirs_row = None;
281        for (i, d) in self.open_dirs.iter().enumerate() {
282            if *d == (volume.idx, Cluster::ROOT_DIR) {
283                return Err(Error::DirAlreadyOpen);
284            }
285            if d.1 == Cluster::INVALID {
286                open_dirs_row = Some(i);
287                break;
288            }
289        }
290        let open_dirs_row = open_dirs_row.ok_or(Error::TooManyOpenDirs)?;
291        // Remember this open directory
292        self.open_dirs[open_dirs_row] = (volume.idx, Cluster::ROOT_DIR);
293        Ok(Directory {
294            cluster: Cluster::ROOT_DIR,
295            entry: None,
296        })
297    }
298
299    /// Open a directory. You can then read the directory entries in a random
300    /// order using `get_directory_entry`.
301    ///
302    /// TODO: Work out how to prevent damage occuring to the file system while
303    /// this directory handle is open. In particular, stop this directory
304    /// being unlinked.
305    pub fn open_dir(
306        &mut self,
307        volume: &Volume,
308        parent_dir: &Directory,
309        name: &str,
310    ) -> Result<Directory, Error<D::Error>> {
311        // Find a free open directory table row
312        let mut open_dirs_row = None;
313        for (i, d) in self.open_dirs.iter().enumerate() {
314            if d.1 == Cluster::INVALID {
315                open_dirs_row = Some(i);
316            }
317        }
318        let open_dirs_row = open_dirs_row.ok_or(Error::TooManyOpenDirs)?;
319
320        // Open the directory
321        let dir_entry = match &volume.volume_type {
322            VolumeType::Fat(fat) => fat.find_directory_entry(self, parent_dir, name)?,
323        };
324
325        if !dir_entry.attributes.is_directory() {
326            return Err(Error::OpenedDirAsFile);
327        }
328
329        // Check it's not already open
330        for (_i, dir_table_row) in self.open_dirs.iter().enumerate() {
331            if *dir_table_row == (volume.idx, dir_entry.cluster) {
332                return Err(Error::DirAlreadyOpen);
333            }
334        }
335        // Remember this open directory
336        self.open_dirs[open_dirs_row] = (volume.idx, dir_entry.cluster);
337        Ok(Directory {
338            cluster: dir_entry.cluster,
339            entry: Some(dir_entry),
340        })
341    }
342
343    /// Close a directory. You cannot perform operations on an open directory
344    /// and so must close it if you want to do something with it.
345    pub fn close_dir(&mut self, volume: &Volume, dir: Directory) {
346        let target = (volume.idx, dir.cluster);
347        for d in self.open_dirs.iter_mut() {
348            if *d == target {
349                d.1 = Cluster::INVALID;
350                break;
351            }
352        }
353        drop(dir);
354    }
355
356    /// Look in a directory for a named file.
357    pub fn find_directory_entry(
358        &mut self,
359        volume: &Volume,
360        dir: &Directory,
361        name: &str,
362    ) -> Result<DirEntry, Error<D::Error>> {
363        match &volume.volume_type {
364            VolumeType::Fat(fat) => fat.find_directory_entry(self, dir, name),
365        }
366    }
367
368    /// Call a callback function for each directory entry in a directory.
369    pub fn iterate_dir<F>(
370        &mut self,
371        volume: &Volume,
372        dir: &Directory,
373        func: F,
374    ) -> Result<(), Error<D::Error>>
375    where
376        F: FnMut(&DirEntry),
377    {
378        match &volume.volume_type {
379            VolumeType::Fat(fat) => fat.iterate_dir(self, dir, func),
380        }
381    }
382
383    /// Open a file with the given full path. A file can only be opened once.
384    pub fn open_file_in_dir(
385        &mut self,
386        volume: &mut Volume,
387        dir: &Directory,
388        name: &str,
389        mode: Mode,
390    ) -> Result<File, Error<D::Error>> {
391        // Find a free directory entry
392        let mut open_files_row = None;
393        for (i, d) in self.open_files.iter().enumerate() {
394            if d.1 == Cluster::INVALID {
395                open_files_row = Some(i);
396            }
397        }
398        let open_files_row = open_files_row.ok_or(Error::TooManyOpenDirs)?;
399        let dir_entry = match &volume.volume_type {
400            VolumeType::Fat(fat) => fat.find_directory_entry(self, dir, name),
401        };
402
403        let dir_entry = match dir_entry {
404            Ok(entry) => Some(entry),
405            Err(_)
406                if (mode == Mode::ReadWriteCreate)
407                    | (mode == Mode::ReadWriteCreateOrTruncate)
408                    | (mode == Mode::ReadWriteCreateOrAppend) =>
409            {
410                None
411            }
412            _ => return Err(Error::FileNotFound),
413        };
414
415        if let Some(entry) = &dir_entry {
416            // Check it's not already open
417            for dir_table_row in self.open_files.iter() {
418                if *dir_table_row == (volume.idx, entry.cluster) {
419                    return Err(Error::DirAlreadyOpen);
420                }
421            }
422            if entry.attributes.is_directory() {
423                return Err(Error::OpenedDirAsFile);
424            }
425            if entry.attributes.is_read_only() && mode != Mode::ReadOnly {
426                return Err(Error::ReadOnly);
427            }
428        };
429
430        let mut mode = mode;
431        if mode == Mode::ReadWriteCreateOrAppend {
432            if dir_entry.is_some() {
433                mode = Mode::ReadWriteAppend;
434            } else {
435                mode = Mode::ReadWriteCreate;
436            }
437        } else if mode == Mode::ReadWriteCreateOrTruncate {
438            if dir_entry.is_some() {
439                mode = Mode::ReadWriteTruncate;
440            } else {
441                mode = Mode::ReadWriteCreate;
442            }
443        }
444
445        let file = match mode {
446            Mode::ReadOnly => {
447                // Safe to unwrap, since we actually have an entry if we got here
448                let dir_entry = dir_entry.unwrap();
449
450                File {
451                    starting_cluster: dir_entry.cluster,
452                    current_cluster: (0, dir_entry.cluster),
453                    current_offset: 0,
454                    length: dir_entry.size,
455                    mode,
456                    entry: dir_entry,
457                }
458            }
459            Mode::ReadWriteAppend => {
460                // Safe to unwrap, since we actually have an entry if we got here
461                let dir_entry = dir_entry.unwrap();
462
463                let mut file = File {
464                    starting_cluster: dir_entry.cluster,
465                    current_cluster: (0, dir_entry.cluster),
466                    current_offset: 0,
467                    length: dir_entry.size,
468                    mode,
469                    entry: dir_entry,
470                };
471                // seek_from_end with 0 can't fail
472                file.seek_from_end(0).ok();
473                file
474            }
475            Mode::ReadWriteTruncate => {
476                // Safe to unwrap, since we actually have an entry if we got here
477                let dir_entry = dir_entry.unwrap();
478
479                let mut file = File {
480                    starting_cluster: dir_entry.cluster,
481                    current_cluster: (0, dir_entry.cluster),
482                    current_offset: 0,
483                    length: dir_entry.size,
484                    mode,
485                    entry: dir_entry,
486                };
487                match &mut volume.volume_type {
488                    VolumeType::Fat(fat) => {
489                        fat.truncate_cluster_chain(self, file.starting_cluster)?
490                    }
491                };
492                file.update_length(0);
493                // TODO update entry Timestamps
494                match &volume.volume_type {
495                    VolumeType::Fat(fat) => {
496                        let fat_type = fat.get_fat_type();
497                        self.write_entry_to_disk(fat_type, &file.entry)?;
498                    }
499                };
500
501                file
502            }
503            Mode::ReadWriteCreate => {
504                if dir_entry.is_some() {
505                    return Err(Error::FileAlreadyExists);
506                }
507                let file_name =
508                    ShortFileName::create_from_str(name).map_err(Error::FilenameError)?;
509                let att = Attributes::create_from_fat(0);
510                let entry = match &mut volume.volume_type {
511                    VolumeType::Fat(fat) => {
512                        fat.write_new_directory_entry(self, dir, file_name, att)?
513                    }
514                };
515
516                File {
517                    starting_cluster: entry.cluster,
518                    current_cluster: (0, entry.cluster),
519                    current_offset: 0,
520                    length: entry.size,
521                    mode,
522                    entry,
523                }
524            }
525            _ => return Err(Error::Unsupported),
526        };
527        // Remember this open file
528        self.open_files[open_files_row] = (volume.idx, file.starting_cluster);
529        Ok(file)
530    }
531
532    /// Read from an open file.
533    pub fn read(
534        &mut self,
535        volume: &Volume,
536        file: &mut File,
537        buffer: &mut [u8],
538    ) -> Result<usize, Error<D::Error>> {
539        // Calculate which file block the current offset lies within
540        // While there is more to read, read the block and copy in to the buffer.
541        // If we need to find the next cluster, walk the FAT.
542        let mut space = buffer.len();
543        let mut read = 0;
544        while space > 0 && !file.eof() {
545            let (block_idx, block_offset, block_avail) =
546                self.find_data_on_disk(volume, &mut file.current_cluster, file.current_offset)?;
547            let mut blocks = [Block::new()];
548            self.block_device
549                .read(&mut blocks, block_idx, "read")
550                .map_err(Error::DeviceError)?;
551            let block = &blocks[0];
552            let to_copy = block_avail.min(space).min(file.left() as usize);
553            assert!(to_copy != 0);
554            buffer[read..read + to_copy]
555                .copy_from_slice(&block[block_offset..block_offset + to_copy]);
556            read += to_copy;
557            space -= to_copy;
558            file.seek_from_current(to_copy as i32).unwrap();
559        }
560        Ok(read)
561    }
562
563    /// Write to a open file.
564    pub fn write(
565        &mut self,
566        volume: &mut Volume,
567        file: &mut File,
568        buffer: &[u8],
569    ) -> Result<usize, Error<D::Error>> {
570        debug!(
571            "write(volume={:?}, file={:?}, buffer={:x?}",
572            volume, file, buffer
573        );
574        if file.mode == Mode::ReadOnly {
575            return Err(Error::ReadOnly);
576        }
577        if file.starting_cluster.0 < RESERVED_ENTRIES {
578            // file doesn't have a valid allocated cluster (possible zero-length file), allocate one
579            file.starting_cluster = match &mut volume.volume_type {
580                VolumeType::Fat(fat) => fat.alloc_cluster(self, None, false)?,
581            };
582            file.entry.cluster = file.starting_cluster;
583            debug!("Alloc first cluster {:?}", file.starting_cluster);
584        }
585        if (file.current_cluster.1).0 < file.starting_cluster.0 {
586            debug!("Rewinding to start");
587            file.current_cluster = (0, file.starting_cluster);
588        }
589        let bytes_until_max = usize::try_from(MAX_FILE_SIZE - file.current_offset)
590            .map_err(|_| Error::ConversionError)?;
591        let bytes_to_write = core::cmp::min(buffer.len(), bytes_until_max);
592        let mut written = 0;
593
594        while written < bytes_to_write {
595            let mut current_cluster = file.current_cluster;
596            debug!(
597                "Have written bytes {}/{}, finding cluster {:?}",
598                written, bytes_to_write, current_cluster
599            );
600            let (block_idx, block_offset, block_avail) =
601                match self.find_data_on_disk(volume, &mut current_cluster, file.current_offset) {
602                    Ok(vars) => {
603                        debug!(
604                            "Found block_idx={:?}, block_offset={:?}, block_avail={}",
605                            vars.0, vars.1, vars.2
606                        );
607                        vars
608                    }
609                    Err(Error::EndOfFile) => {
610                        debug!("Extending file");
611                        match &mut volume.volume_type {
612                            VolumeType::Fat(ref mut fat) => {
613                                if fat
614                                    .alloc_cluster(self, Some(current_cluster.1), false)
615                                    .is_err()
616                                {
617                                    return Ok(written);
618                                }
619                                debug!("Allocated new FAT cluster, finding offsets...");
620                                let new_offset = self
621                                    .find_data_on_disk(
622                                        volume,
623                                        &mut current_cluster,
624                                        file.current_offset,
625                                    )
626                                    .map_err(|_| Error::AllocationError)?;
627                                debug!("New offset {:?}", new_offset);
628                                new_offset
629                            }
630                        }
631                    }
632                    Err(e) => return Err(e),
633                };
634            let mut blocks = [Block::new()];
635            let to_copy = core::cmp::min(block_avail, bytes_to_write - written);
636            if block_offset != 0 {
637                debug!("Partial block write");
638                self.block_device
639                    .read(&mut blocks, block_idx, "read")
640                    .map_err(Error::DeviceError)?;
641            }
642            let block = &mut blocks[0];
643            block[block_offset..block_offset + to_copy]
644                .copy_from_slice(&buffer[written..written + to_copy]);
645            debug!("Writing block {:?}", block_idx);
646            self.block_device
647                .write(&blocks, block_idx)
648                .map_err(Error::DeviceError)?;
649            written += to_copy;
650            file.current_cluster = current_cluster;
651            let to_copy = i32::try_from(to_copy).map_err(|_| Error::ConversionError)?;
652            // TODO: Should we do this once when the whole file is written?
653            file.update_length(file.length + (to_copy as u32));
654            file.seek_from_current(to_copy).unwrap();
655            file.entry.attributes.set_archive(true);
656            file.entry.mtime = self.timesource.get_timestamp();
657            debug!("Updating FAT info sector");
658            match &mut volume.volume_type {
659                VolumeType::Fat(fat) => {
660                    fat.update_info_sector(self)?;
661                    debug!("Updating dir entry");
662                    self.write_entry_to_disk(fat.get_fat_type(), &file.entry)?;
663                }
664            }
665        }
666        Ok(written)
667    }
668
669    /// Close a file with the given full path.
670    pub fn close_file(&mut self, volume: &Volume, file: File) -> Result<(), Error<D::Error>> {
671        let target = (volume.idx, file.starting_cluster);
672        for d in self.open_files.iter_mut() {
673            if *d == target {
674                d.1 = Cluster::INVALID;
675                break;
676            }
677        }
678        drop(file);
679        Ok(())
680    }
681
682    /// This function turns `desired_offset` into an appropriate block to be
683    /// read. It either calculates this based on the start of the file, or
684    /// from the last cluster we read - whichever is better.
685    fn find_data_on_disk(
686        &mut self,
687        volume: &Volume,
688        start: &mut (u32, Cluster),
689        desired_offset: u32,
690    ) -> Result<(BlockIdx, usize, usize), Error<D::Error>> {
691        let bytes_per_cluster = match &volume.volume_type {
692            VolumeType::Fat(fat) => fat.bytes_per_cluster(),
693        };
694        // How many clusters forward do we need to go?
695        let offset_from_cluster = desired_offset - start.0;
696        let num_clusters = offset_from_cluster / bytes_per_cluster;
697        for _ in 0..num_clusters {
698            start.1 = match &volume.volume_type {
699                VolumeType::Fat(fat) => fat.next_cluster(self, start.1)?,
700            };
701            start.0 += bytes_per_cluster;
702        }
703        // How many blocks in are we?
704        let offset_from_cluster = desired_offset - start.0;
705        assert!(offset_from_cluster < bytes_per_cluster);
706        let num_blocks = BlockCount(offset_from_cluster / Block::LEN_U32);
707        let block_idx = match &volume.volume_type {
708            VolumeType::Fat(fat) => fat.cluster_to_block(start.1),
709        } + num_blocks;
710        let block_offset = (desired_offset % Block::LEN_U32) as usize;
711        let available = Block::LEN - block_offset;
712        Ok((block_idx, block_offset, available))
713    }
714
715    /// Writes a Directory Entry to the disk
716    fn write_entry_to_disk(
717        &mut self,
718        fat_type: fat::FatType,
719        entry: &DirEntry,
720    ) -> Result<(), Error<D::Error>> {
721        let mut blocks = [Block::new()];
722        self.block_device
723            .read(&mut blocks, entry.entry_block, "read")
724            .map_err(Error::DeviceError)?;
725        let block = &mut blocks[0];
726
727        let start = usize::try_from(entry.entry_offset).map_err(|_| Error::ConversionError)?;
728        block[start..start + 32].copy_from_slice(&entry.serialize(fat_type)[..]);
729
730        self.block_device
731            .write(&blocks, entry.entry_block)
732            .map_err(Error::DeviceError)?;
733        Ok(())
734    }
735}
736
737// ****************************************************************************
738//
739// Private Functions / Impl for Private Types
740//
741// ****************************************************************************
742
743// None
744
745// ****************************************************************************
746//
747// Unit Tests
748//
749// ****************************************************************************
750
751#[cfg(test)]
752mod tests {
753    use super::*;
754
755    struct DummyBlockDevice;
756
757    struct Clock;
758
759    #[derive(Debug)]
760    enum Error {
761        Unknown,
762    }
763
764    impl TimeSource for Clock {
765        fn get_timestamp(&self) -> Timestamp {
766            // TODO: Return actual time
767            Timestamp {
768                year_since_1970: 0,
769                zero_indexed_month: 0,
770                zero_indexed_day: 0,
771                hours: 0,
772                minutes: 0,
773                seconds: 0,
774            }
775        }
776    }
777
778    impl BlockDevice for DummyBlockDevice {
779        type Error = Error;
780
781        /// Read one or more blocks, starting at the given block index.
782        fn read(
783            &self,
784            blocks: &mut [Block],
785            start_block_idx: BlockIdx,
786            _reason: &str,
787        ) -> Result<(), Self::Error> {
788            // Actual blocks taken from an SD card, except I've changed the start and length of partition 0.
789            static BLOCKS: [Block; 3] = [
790                Block {
791                    contents: [
792                        0xfa, 0xb8, 0x00, 0x10, 0x8e, 0xd0, 0xbc, 0x00, 0xb0, 0xb8, 0x00, 0x00,
793                        0x8e, 0xd8, 0x8e, 0xc0, // 0x000
794                        0xfb, 0xbe, 0x00, 0x7c, 0xbf, 0x00, 0x06, 0xb9, 0x00, 0x02, 0xf3, 0xa4,
795                        0xea, 0x21, 0x06, 0x00, // 0x010
796                        0x00, 0xbe, 0xbe, 0x07, 0x38, 0x04, 0x75, 0x0b, 0x83, 0xc6, 0x10, 0x81,
797                        0xfe, 0xfe, 0x07, 0x75, // 0x020
798                        0xf3, 0xeb, 0x16, 0xb4, 0x02, 0xb0, 0x01, 0xbb, 0x00, 0x7c, 0xb2, 0x80,
799                        0x8a, 0x74, 0x01, 0x8b, // 0x030
800                        0x4c, 0x02, 0xcd, 0x13, 0xea, 0x00, 0x7c, 0x00, 0x00, 0xeb, 0xfe, 0x00,
801                        0x00, 0x00, 0x00, 0x00, // 0x040
802                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
803                        0x00, 0x00, 0x00, 0x00, // 0x050
804                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
805                        0x00, 0x00, 0x00, 0x00, // 0x060
806                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
807                        0x00, 0x00, 0x00, 0x00, // 0x070
808                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
809                        0x00, 0x00, 0x00, 0x00, // 0x080
810                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
811                        0x00, 0x00, 0x00, 0x00, // 0x090
812                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
813                        0x00, 0x00, 0x00, 0x00, // 0x0A0
814                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
815                        0x00, 0x00, 0x00, 0x00, // 0x0B0
816                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
817                        0x00, 0x00, 0x00, 0x00, // 0x0C0
818                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
819                        0x00, 0x00, 0x00, 0x00, // 0x0D0
820                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
821                        0x00, 0x00, 0x00, 0x00, // 0x0E0
822                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
823                        0x00, 0x00, 0x00, 0x00, // 0x0F0
824                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
825                        0x00, 0x00, 0x00, 0x00, // 0x100
826                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
827                        0x00, 0x00, 0x00, 0x00, // 0x110
828                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
829                        0x00, 0x00, 0x00, 0x00, // 0x120
830                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
831                        0x00, 0x00, 0x00, 0x00, // 0x130
832                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
833                        0x00, 0x00, 0x00, 0x00, // 0x140
834                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
835                        0x00, 0x00, 0x00, 0x00, // 0x150
836                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
837                        0x00, 0x00, 0x00, 0x00, // 0x160
838                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
839                        0x00, 0x00, 0x00, 0x00, // 0x170
840                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
841                        0x00, 0x00, 0x00, 0x00, // 0x180
842                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
843                        0x00, 0x00, 0x00, 0x00, // 0x190
844                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
845                        0x00, 0x00, 0x00, 0x00, // 0x1A0
846                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0xca, 0xde, 0x06,
847                        0x00, 0x00, 0x00, 0x04, // 0x1B0
848                        0x01, 0x04, 0x0c, 0xfe, 0xc2, 0xff, 0x01, 0x00, 0x00, 0x00, 0x33, 0x22,
849                        0x11, 0x00, 0x00, 0x00, // 0x1C0
850                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
851                        0x00, 0x00, 0x00, 0x00, // 0x1D0
852                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
853                        0x00, 0x00, 0x00, 0x00, // 0x1E0
854                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
855                        0x00, 0x00, 0x55, 0xaa, // 0x1F0
856                    ],
857                },
858                Block {
859                    contents: [
860                        0xeb, 0x58, 0x90, 0x6d, 0x6b, 0x66, 0x73, 0x2e, 0x66, 0x61, 0x74, 0x00,
861                        0x02, 0x08, 0x20, 0x00, // 0x000
862                        0x02, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x10, 0x00, 0x04, 0x00,
863                        0x00, 0x08, 0x00, 0x00, // 0x010
864                        0x00, 0x20, 0x76, 0x00, 0x80, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
865                        0x02, 0x00, 0x00, 0x00, // 0x020
866                        0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
867                        0x00, 0x00, 0x00, 0x00, // 0x030
868                        0x80, 0x01, 0x29, 0x0b, 0xa8, 0x89, 0x27, 0x50, 0x69, 0x63, 0x74, 0x75,
869                        0x72, 0x65, 0x73, 0x20, // 0x040
870                        0x20, 0x20, 0x46, 0x41, 0x54, 0x33, 0x32, 0x20, 0x20, 0x20, 0x0e, 0x1f,
871                        0xbe, 0x77, 0x7c, 0xac, // 0x050
872                        0x22, 0xc0, 0x74, 0x0b, 0x56, 0xb4, 0x0e, 0xbb, 0x07, 0x00, 0xcd, 0x10,
873                        0x5e, 0xeb, 0xf0, 0x32, // 0x060
874                        0xe4, 0xcd, 0x16, 0xcd, 0x19, 0xeb, 0xfe, 0x54, 0x68, 0x69, 0x73, 0x20,
875                        0x69, 0x73, 0x20, 0x6e, // 0x070
876                        0x6f, 0x74, 0x20, 0x61, 0x20, 0x62, 0x6f, 0x6f, 0x74, 0x61, 0x62, 0x6c,
877                        0x65, 0x20, 0x64, 0x69, // 0x080
878                        0x73, 0x6b, 0x2e, 0x20, 0x20, 0x50, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20,
879                        0x69, 0x6e, 0x73, 0x65, // 0x090
880                        0x72, 0x74, 0x20, 0x61, 0x20, 0x62, 0x6f, 0x6f, 0x74, 0x61, 0x62, 0x6c,
881                        0x65, 0x20, 0x66, 0x6c, // 0x0A0
882                        0x6f, 0x70, 0x70, 0x79, 0x20, 0x61, 0x6e, 0x64, 0x0d, 0x0a, 0x70, 0x72,
883                        0x65, 0x73, 0x73, 0x20, // 0x0B0
884                        0x61, 0x6e, 0x79, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x74, 0x6f, 0x20, 0x74,
885                        0x72, 0x79, 0x20, 0x61, // 0x0C0
886                        0x67, 0x61, 0x69, 0x6e, 0x20, 0x2e, 0x2e, 0x2e, 0x20, 0x0d, 0x0a, 0x00,
887                        0x00, 0x00, 0x00, 0x00, // 0x0D0
888                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
889                        0x00, 0x00, 0x00, 0x00, // 0x0E0
890                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
891                        0x00, 0x00, 0x00, 0x00, // 0x0F0
892                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
893                        0x00, 0x00, 0x00, 0x00, // 0x100
894                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
895                        0x00, 0x00, 0x00, 0x00, // 0x110
896                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
897                        0x00, 0x00, 0x00, 0x00, // 0x120
898                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
899                        0x00, 0x00, 0x00, 0x00, // 0x130
900                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
901                        0x00, 0x00, 0x00, 0x00, // 0x140
902                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
903                        0x00, 0x00, 0x00, 0x00, // 0x150
904                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
905                        0x00, 0x00, 0x00, 0x00, // 0x160
906                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
907                        0x00, 0x00, 0x00, 0x00, // 0x170
908                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
909                        0x00, 0x00, 0x00, 0x00, // 0x180
910                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
911                        0x00, 0x00, 0x00, 0x00, // 0x190
912                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
913                        0x00, 0x00, 0x00, 0x00, // 0x1A0
914                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
915                        0x00, 0x00, 0x00, 0x00, // 0x1B0
916                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
917                        0x00, 0x00, 0x00, 0x00, // 0x1C0
918                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
919                        0x00, 0x00, 0x00, 0x00, // 0x1D0
920                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
921                        0x00, 0x00, 0x00, 0x00, // 0x1E0
922                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
923                        0x00, 0x00, 0x55, 0xaa, // 0x1F0
924                    ],
925                },
926                Block {
927                    contents: [
928                        0x52, 0x52, 0x61, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
929                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
930                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
931                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
932                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
933                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
934                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
935                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
936                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
937                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
938                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
939                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
940                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
941                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
942                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
943                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
944                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
945                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
946                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
947                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
948                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
949                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
950                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
951                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
952                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
953                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
954                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
955                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
956                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
957                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
958                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
959                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
960                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
961                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
962                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
963                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
964                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
965                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
966                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
967                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
968                        0x00, 0x00, 0x00, 0x00, 0x72, 0x72, 0x41, 0x61, 0xFF, 0xFF, 0xFF, 0xFF,
969                        0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
970                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xAA,
971                    ],
972                },
973            ];
974            println!(
975                "Reading block {} to {}",
976                start_block_idx.0,
977                start_block_idx.0 as usize + blocks.len()
978            );
979            for (idx, block) in blocks.iter_mut().enumerate() {
980                let block_idx = start_block_idx.0 as usize + idx;
981                if block_idx < BLOCKS.len() {
982                    *block = BLOCKS[block_idx].clone();
983                } else {
984                    return Err(Error::Unknown);
985                }
986            }
987            Ok(())
988        }
989
990        /// Write one or more blocks, starting at the given block index.
991        fn write(&self, _blocks: &[Block], _start_block_idx: BlockIdx) -> Result<(), Self::Error> {
992            unimplemented!();
993        }
994
995        /// Determine how many blocks this device can hold.
996        fn num_blocks(&self) -> Result<BlockCount, Self::Error> {
997            Ok(BlockCount(2))
998        }
999    }
1000
1001    #[test]
1002    fn partition0() {
1003        let mut c = Controller::new(DummyBlockDevice, Clock);
1004        let v = c.get_volume(VolumeIdx(0)).unwrap();
1005        assert_eq!(
1006            v,
1007            Volume {
1008                idx: VolumeIdx(0),
1009                volume_type: VolumeType::Fat(FatVolume {
1010                    lba_start: BlockIdx(1),
1011                    num_blocks: BlockCount(0x0011_2233),
1012                    blocks_per_cluster: 8,
1013                    first_data_block: BlockCount(15136),
1014                    fat_start: BlockCount(32),
1015                    name: fat::VolumeName {
1016                        data: *b"Pictures   "
1017                    },
1018                    free_clusters_count: None,
1019                    next_free_cluster: None,
1020                    cluster_count: 965_788,
1021                    fat_specific_info: fat::FatSpecificInfo::Fat32(fat::Fat32Info {
1022                        first_root_dir_cluster: Cluster(2),
1023                        info_location: BlockIdx(1) + BlockCount(1),
1024                    })
1025                })
1026            }
1027        );
1028    }
1029}
1030
1031// ****************************************************************************
1032//
1033// End Of File
1034//
1035// ****************************************************************************