Skip to main content

hadris_fat/
read.rs

1//! Read operations for FAT filesystems.
2
3io_transform! {
4
5#[cfg(feature = "alloc")]
6use core::ops::DerefMut;
7
8#[cfg(feature = "alloc")]
9use alloc::vec::Vec;
10
11use crate::error::{Error, Result};
12use super::{
13    fs::FatVolume, dir::FileEntry,
14    io::{Cluster, ClusterLike, Read, Seek, SeekFrom},
15};
16
17/// A reader for file content in a FAT filesystem.
18///
19/// This struct provides a `Read` implementation that follows the cluster chain
20/// to read file contents.
21///
22/// # Buffering
23///
24/// When the `alloc` feature is enabled, the reader can optionally buffer data
25/// to reduce the number of seek and read operations:
26///
27/// - [`with_buffer`](Self::with_buffer): Enable cluster-level buffering. Each cluster
28///   is read entirely into memory and subsequent reads are served from the buffer.
29///
30/// - [`with_cached_chain`](Self::with_cached_chain): Pre-cache the entire cluster chain.
31///   This is useful for small files where you want to avoid repeated FAT lookups.
32pub struct FileReader<'a, DATA: Read + Seek> {
33    fs: &'a FatVolume<DATA>,
34    cluster: Cluster<usize>,
35    /// Offset within the current cluster
36    offset_in_cluster: usize,
37    /// Total bytes read so far
38    total_read: usize,
39    /// Total size of the file
40    size: usize,
41    /// Cluster transitions taken so far. Bounded by `Fat::max_cluster()` so a
42    /// corrupt looping chain surfaces as `Error::ClusterLoop` instead of
43    /// hanging the reader.
44    cluster_steps: u32,
45    /// Optional cluster buffer for reduced I/O
46    #[cfg(feature = "alloc")]
47    cluster_buffer: Option<Vec<u8>>,
48    /// Pre-cached cluster chain (optional)
49    #[cfg(feature = "alloc")]
50    cached_chain: Option<Vec<u32>>,
51    /// Current index in the cached chain
52    #[cfg(feature = "alloc")]
53    chain_index: usize,
54}
55
56impl<'a, DATA: Read + Seek> FileReader<'a, DATA> {
57    /// Create a new FileReader for a file entry.
58    ///
59    /// Returns an error if the entry is a directory.
60    pub fn new(fs: &'a FatVolume<DATA>, entry: &FileEntry) -> Result<Self> {
61        if entry.is_directory() {
62            return Err(Error::NotAFile);
63        }
64
65        Ok(Self {
66            fs,
67            cluster: entry.cluster(),
68            offset_in_cluster: 0,
69            total_read: 0,
70            size: entry.len() as usize,
71            cluster_steps: 0,
72            #[cfg(feature = "alloc")]
73            cluster_buffer: None,
74            #[cfg(feature = "alloc")]
75            cached_chain: None,
76            #[cfg(feature = "alloc")]
77            chain_index: 0,
78        })
79    }
80
81    /// Returns the total size of the file in bytes.
82    pub fn size(&self) -> usize {
83        self.size
84    }
85
86    /// Returns the number of bytes remaining to be read.
87    pub fn remaining(&self) -> usize {
88        self.size.saturating_sub(self.total_read)
89    }
90
91    /// Enable cluster-level buffering.
92    ///
93    /// When enabled, each cluster is read entirely into memory on first access,
94    /// and subsequent reads within that cluster are served from the buffer.
95    /// This reduces the number of seek operations at the cost of memory usage.
96    ///
97    /// Memory usage: One cluster size (typically 4KB to 64KB).
98    #[cfg(feature = "alloc")]
99    pub fn with_buffer(mut self) -> Self {
100        self.cluster_buffer = Some(Vec::new());
101        self
102    }
103
104    /// Pre-cache the entire cluster chain.
105    ///
106    /// This reads the entire FAT chain for the file into memory, eliminating
107    /// the need for FAT lookups during sequential reads. This is most beneficial
108    /// for fragmented files or when performing many random seeks.
109    ///
110    /// Memory usage: 4 bytes per cluster in the file.
111    #[cfg(feature = "alloc")]
112    pub async fn with_cached_chain(mut self) -> Result<Self> {
113        if self.cluster.0 < 2 {
114            // Empty file, no chain to cache
115            self.cached_chain = Some(Vec::new());
116            return Ok(self);
117        }
118
119        let max_clusters = self.fs.info.max_cluster as usize;
120        let mut data = self.fs.data.lock();
121        let chain = self
122            .fs
123            .fat
124            .read_chain(data.deref_mut(), self.cluster.0 as u32, max_clusters)
125            .await?;
126        drop(data);
127
128        self.cached_chain = Some(chain);
129        self.chain_index = 0;
130        Ok(self)
131    }
132
133    /// Read data from the file.
134    ///
135    /// Reads up to `buf.len()` bytes, or fewer at end-of-file. Use a small `buf` to
136    /// stream incrementally; a larger `buf` allows more bytes per call (including
137    /// contiguous-cluster bulk I/O when the chain is cached). The underlying
138    /// [`Read`](crate::io::Read) / [`Seek`](crate::io::Seek) implementation can enforce
139    /// alignment or transfer sizes as needed. Optional per-cluster buffering applies when
140    /// enabled.
141    pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
142        // Cap by caller buffer and bytes left in the file; actual I/O may be one or many
143        // steps inside read_to_buf (cached contiguous runs vs. read_one_chunk loop).
144        let max = buf.len().min(self.remaining());
145        if max == 0 {
146            return Ok(0);
147        }
148        self.read_to_buf(&mut buf[..max]).await
149    }
150
151    /// Read into `buf` until the buffer is full or the end of the file is reached.
152    ///
153    /// At most `buf.len().min(self.remaining())` bytes are read. Returns the number of
154    /// bytes written to the start of `buf`.
155    ///
156    /// When the cluster chain is cached (`with_cached_chain`), contiguous runs of
157    /// clusters are read in one seek+read per run (one I/O per fragment) instead of
158    /// one per cluster.
159    async fn read_to_buf(&mut self, buf: &mut [u8]) -> Result<usize> {
160        let max = buf.len().min(self.remaining());
161        if max == 0 {
162            return Ok(0);
163        }
164        let buf = &mut buf[..max];
165
166        #[cfg(feature = "alloc")]
167        {
168            let mut data = self.fs.data.lock();
169            let cluster_size = data.cluster_size;
170            let data_start = self.fs.info.data_start;
171
172            // Cached chain: coalesce physically consecutive clusters into one seek+read per run.
173            if let Some(ref chain) = self.cached_chain {
174                let buf_len = buf.len();
175                // Fast path: one seek + one read per contiguous run of clusters
176                let mut total = 0usize;
177                let mut chain_index = self.chain_index;
178                let mut offset_in_cluster = self.offset_in_cluster;
179
180                while total < buf_len && chain_index < chain.len() {
181                    let first_cluster = chain[chain_index] as usize;
182                    // Count contiguous run: chain[chain_index], chain[chain_index+1], ... while consecutive
183                    let mut run_len = 1usize;
184                    while chain_index + run_len < chain.len()
185                        && chain[chain_index + run_len] == chain[chain_index + run_len - 1] + 1
186                    {
187                        run_len += 1;
188                    }
189
190                    // Bytes we can read from this run (from current offset to end of run)
191                    let bytes_from_first = cluster_size.saturating_sub(offset_in_cluster);
192                    let bytes_from_run = bytes_from_first
193                        .saturating_add(run_len.saturating_sub(1).saturating_mul(cluster_size));
194                    let to_read = bytes_from_run.min(buf_len - total);
195
196                    if to_read == 0 {
197                        break;
198                    }
199
200                    let seek_pos = Cluster(first_cluster)
201                        .to_bytes(data_start, cluster_size)
202                        .saturating_add(offset_in_cluster);
203                    data.seek(SeekFrom::Start(seek_pos as u64)).await?;
204                    data.read_exact(&mut buf[total..total + to_read]).await?;
205
206                    total += to_read;
207                    self.total_read += to_read;
208
209                    // Advance by clusters we consumed
210                    let new_offset = offset_in_cluster + to_read;
211                    chain_index += new_offset / cluster_size;
212                    offset_in_cluster = new_offset % cluster_size;
213                }
214
215                self.chain_index = chain_index;
216                self.offset_in_cluster = offset_in_cluster;
217                if chain_index < chain.len() {
218                    self.cluster.0 = chain[chain_index] as usize;
219                }
220                drop(data);
221                return Ok(total);
222            }
223
224            drop(data);
225        }
226
227        // No cached chain (or alloc off): walk the FAT one cluster-sized step at a time.
228        let buf_len = buf.len();
229        let mut total = 0usize;
230        while total < buf_len {
231            let n = self.read_one_chunk(&mut buf[total..]).await?;
232            if n == 0 {
233                break;
234            }
235            total += n;
236        }
237        Ok(total)
238    }
239
240    /// Read at most one contiguous span within the current cluster (or less if `buf` or the
241    /// file ends sooner). Used by [`read_to_buf`](Self::read_to_buf) when the chain is not
242    /// cached; advances to the next FAT cluster when the current one is exhausted.
243    async fn read_one_chunk(&mut self, buf: &mut [u8]) -> Result<usize> {
244        // End of logical file
245        if self.total_read >= self.size {
246            return Ok(0);
247        }
248
249        let mut data = self.fs.data.lock();
250        let cluster_size = data.cluster_size;
251
252        // Consumed the whole cluster: follow the chain to the next data cluster.
253        if self.offset_in_cluster >= cluster_size {
254            #[cfg(feature = "alloc")]
255            {
256                if let Some(ref chain) = self.cached_chain {
257                    // Next cluster from the pre-read chain (no FAT table walk).
258                    self.chain_index += 1;
259                    if self.chain_index >= chain.len() {
260                        return Ok(0); // End of file
261                    }
262                    self.cluster.0 = chain[self.chain_index] as usize;
263                    self.offset_in_cluster = 0;
264                    // Cluster buffer holds one cluster; must reload after moving.
265                    if let Some(ref mut buffer) = self.cluster_buffer {
266                        buffer.clear();
267                    }
268                } else {
269                    // Resolve next cluster from the FAT on disk.
270                    self.cluster_steps = self.cluster_steps.saturating_add(1);
271                    if self.cluster_steps > self.fs.fat.max_cluster() {
272                        return Err(Error::ClusterLoop {
273                            cluster: self.cluster.0 as u32,
274                        });
275                    }
276                    // Drop data lock so next_cluster_routed can acquire
277                    // cache+data in canonical order; re-lock after.
278                    drop(data);
279                    let next = self.fs.next_cluster_routed(self.cluster.0).await?;
280                    data = self.fs.data.lock();
281                    match next {
282                        Some(cluster) => {
283                            self.cluster.0 = cluster as usize;
284                            self.offset_in_cluster = 0;
285                            if let Some(ref mut buffer) = self.cluster_buffer {
286                                buffer.clear();
287                            }
288                        }
289                        None => return Ok(0), // End of cluster chain
290                    }
291                }
292            }
293
294            #[cfg(not(feature = "alloc"))]
295            {
296                self.cluster_steps = self.cluster_steps.saturating_add(1);
297                if self.cluster_steps > self.fs.fat.max_cluster() {
298                    return Err(Error::ClusterLoop {
299                        cluster: self.cluster.0 as u32,
300                    });
301                }
302                // Drop data lock so next_cluster_routed can acquire
303                // cache+data in canonical order; re-lock after.
304                drop(data);
305                let next = self.fs.next_cluster_routed(self.cluster.0).await?;
306                data = self.fs.data.lock();
307                match next {
308                    Some(cluster) => {
309                        self.cluster.0 = cluster as usize;
310                        self.offset_in_cluster = 0;
311                    }
312                    None => return Ok(0), // End of cluster chain
313                }
314            }
315        }
316
317        // How much we can copy from the current cluster in this step.
318        let bytes_left_in_cluster = cluster_size - self.offset_in_cluster;
319        let bytes_left_in_file = self.size - self.total_read;
320        let read_max = buf.len().min(bytes_left_in_cluster).min(bytes_left_in_file);
321
322        if read_max == 0 {
323            return Ok(0);
324        }
325
326        #[cfg(feature = "alloc")]
327        let bytes_read = if let Some(ref mut buffer) = self.cluster_buffer {
328            // Load whole cluster once, then serve reads from RAM.
329            if buffer.is_empty() {
330                let cluster_start = self.cluster.to_bytes(self.fs.info.data_start, cluster_size);
331                data.seek(SeekFrom::Start(cluster_start as u64)).await?;
332
333                buffer.resize(cluster_size, 0);
334                data.read_exact(buffer).await?;
335            }
336
337            let src = &buffer[self.offset_in_cluster..self.offset_in_cluster + read_max];
338            buf[..read_max].copy_from_slice(src);
339            read_max
340        } else {
341            // Direct read at file offset within this cluster.
342            let seek_pos = self.cluster.to_bytes(self.fs.info.data_start, cluster_size)
343                + self.offset_in_cluster;
344            data.seek(SeekFrom::Start(seek_pos as u64)).await?;
345            data.read(&mut buf[..read_max]).await?
346        };
347
348        #[cfg(not(feature = "alloc"))]
349        let bytes_read = {
350            let seek_pos = self.cluster.to_bytes(self.fs.info.data_start, cluster_size)
351                + self.offset_in_cluster;
352            data.seek(SeekFrom::Start(seek_pos as u64)).await?;
353            data.read(&mut buf[..read_max]).await?
354        };
355
356        self.offset_in_cluster += bytes_read;
357        self.total_read += bytes_read;
358
359        Ok(bytes_read)
360    }
361
362    /// Read all bytes from the current read position through the end of the file.
363    ///
364    /// Data is read starting at this reader's current offset in the file (the same
365    /// position the next [`read`](Self::read) would use—not necessarily offset 0). Bytes
366    /// already consumed by earlier [`read`](Self::read) or `read_to_vec` calls are not read
367    /// again. The allocation size is [`remaining`](Self::remaining); bytes are read using the
368    /// same internal bulk-read path as [`read`](Self::read).
369    #[cfg(feature = "alloc")]
370    pub async fn read_to_vec(&mut self) -> Result<Vec<u8>> {
371        let remaining = self.remaining();
372        // `size` derives from the untrusted directory-entry file size (u32, up to
373        // ~4 GiB). A file cannot exceed the volume's cluster heap, so bound the
374        // up-front allocation against it — otherwise a corrupt entry in a tiny
375        // image could force a multi-gigabyte allocation (a DoS that aborts the
376        // process on no-overcommit / embedded targets).
377        let volume_capacity =
378            self.fs.info.max_cluster as u64 * self.fs.info.cluster_size as u64;
379        if remaining as u64 > volume_capacity {
380            return Err(Error::CorruptFilesystem {
381                context: "file size exceeds volume capacity",
382            });
383        }
384        // One allocation sized to what's left from the current read cursor.
385        let mut buf = alloc::vec![0u8; remaining];
386        let n = self.read_to_buf(&mut buf).await?;
387        buf.truncate(n);
388        Ok(buf)
389    }
390}
391
392/// Extension trait for FatVolume to read files directly.
393pub trait FatVolumeReadExt<DATA: Read + Seek> {
394    /// Create a reader for a file entry.
395    fn read_file<'a>(&'a self, entry: &FileEntry) -> Result<FileReader<'a, DATA>>;
396}
397
398impl<DATA: Read + Seek> FatVolumeReadExt<DATA> for FatVolume<DATA> {
399    fn read_file<'a>(&'a self, entry: &FileEntry) -> Result<FileReader<'a, DATA>> {
400        FileReader::new(self, entry)
401    }
402}
403
404} // end io_transform!