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        // A directory entry whose first cluster is 0 has no data allocated
250        // (1 is reserved); report EOF instead of underflowing the
251        // cluster-to-offset math.
252        if self.cluster.0 < 2 {
253            return Ok(0);
254        }
255
256        let mut data = self.fs.data.lock();
257        let cluster_size = data.cluster_size;
258
259        // Consumed the whole cluster: follow the chain to the next data cluster.
260        if self.offset_in_cluster >= cluster_size {
261            #[cfg(feature = "alloc")]
262            {
263                if let Some(ref chain) = self.cached_chain {
264                    // Next cluster from the pre-read chain (no FAT table walk).
265                    self.chain_index += 1;
266                    if self.chain_index >= chain.len() {
267                        return Ok(0); // End of file
268                    }
269                    self.cluster.0 = chain[self.chain_index] as usize;
270                    self.offset_in_cluster = 0;
271                    // Cluster buffer holds one cluster; must reload after moving.
272                    if let Some(ref mut buffer) = self.cluster_buffer {
273                        buffer.clear();
274                    }
275                } else {
276                    // Resolve next cluster from the FAT on disk.
277                    self.cluster_steps = self.cluster_steps.saturating_add(1);
278                    if self.cluster_steps > self.fs.fat.max_cluster() {
279                        return Err(Error::ClusterLoop {
280                            cluster: self.cluster.0 as u32,
281                        });
282                    }
283                    // Drop data lock so next_cluster_routed can acquire
284                    // cache+data in canonical order; re-lock after.
285                    drop(data);
286                    let next = self.fs.next_cluster_routed(self.cluster.0).await?;
287                    data = self.fs.data.lock();
288                    match next {
289                        Some(cluster) => {
290                            self.cluster.0 = cluster as usize;
291                            self.offset_in_cluster = 0;
292                            if let Some(ref mut buffer) = self.cluster_buffer {
293                                buffer.clear();
294                            }
295                        }
296                        None => return Ok(0), // End of cluster chain
297                    }
298                }
299            }
300
301            #[cfg(not(feature = "alloc"))]
302            {
303                self.cluster_steps = self.cluster_steps.saturating_add(1);
304                if self.cluster_steps > self.fs.fat.max_cluster() {
305                    return Err(Error::ClusterLoop {
306                        cluster: self.cluster.0 as u32,
307                    });
308                }
309                // Drop data lock so next_cluster_routed can acquire
310                // cache+data in canonical order; re-lock after.
311                drop(data);
312                let next = self.fs.next_cluster_routed(self.cluster.0).await?;
313                data = self.fs.data.lock();
314                match next {
315                    Some(cluster) => {
316                        self.cluster.0 = cluster as usize;
317                        self.offset_in_cluster = 0;
318                    }
319                    None => return Ok(0), // End of cluster chain
320                }
321            }
322        }
323
324        // How much we can copy from the current cluster in this step.
325        let bytes_left_in_cluster = cluster_size - self.offset_in_cluster;
326        let bytes_left_in_file = self.size - self.total_read;
327        let read_max = buf.len().min(bytes_left_in_cluster).min(bytes_left_in_file);
328
329        if read_max == 0 {
330            return Ok(0);
331        }
332
333        #[cfg(feature = "alloc")]
334        let bytes_read = if let Some(ref mut buffer) = self.cluster_buffer {
335            // Load whole cluster once, then serve reads from RAM.
336            if buffer.is_empty() {
337                let cluster_start = self.cluster.to_bytes(self.fs.info.data_start, cluster_size);
338                data.seek(SeekFrom::Start(cluster_start as u64)).await?;
339
340                buffer.resize(cluster_size, 0);
341                data.read_exact(buffer).await?;
342            }
343
344            let src = &buffer[self.offset_in_cluster..self.offset_in_cluster + read_max];
345            buf[..read_max].copy_from_slice(src);
346            read_max
347        } else {
348            // Direct read at file offset within this cluster.
349            let seek_pos = self.cluster.to_bytes(self.fs.info.data_start, cluster_size)
350                + self.offset_in_cluster;
351            data.seek(SeekFrom::Start(seek_pos as u64)).await?;
352            data.read(&mut buf[..read_max]).await?
353        };
354
355        #[cfg(not(feature = "alloc"))]
356        let bytes_read = {
357            let seek_pos = self.cluster.to_bytes(self.fs.info.data_start, cluster_size)
358                + self.offset_in_cluster;
359            data.seek(SeekFrom::Start(seek_pos as u64)).await?;
360            data.read(&mut buf[..read_max]).await?
361        };
362
363        self.offset_in_cluster += bytes_read;
364        self.total_read += bytes_read;
365
366        Ok(bytes_read)
367    }
368
369    /// Read all bytes from the current read position through the end of the file.
370    ///
371    /// Data is read starting at this reader's current offset in the file (the same
372    /// position the next [`read`](Self::read) would use—not necessarily offset 0). Bytes
373    /// already consumed by earlier [`read`](Self::read) or `read_to_vec` calls are not read
374    /// again. Bytes are read using the same internal bulk-read path as [`read`](Self::read).
375    #[cfg(feature = "alloc")]
376    pub async fn read_to_vec(&mut self) -> Result<Vec<u8>> {
377        let remaining = self.remaining();
378        // `size` derives from the untrusted directory-entry file size (u32, up to
379        // ~4 GiB). A file cannot exceed the volume's cluster heap, so bound the
380        // up-front allocation against it — otherwise a corrupt entry in a tiny
381        // image could force a multi-gigabyte allocation (a DoS that aborts the
382        // process on no-overcommit / embedded targets).
383        let volume_capacity = self.fs.info.max_cluster as u64 * self.fs.info.cluster_size as u64;
384        if remaining as u64 > volume_capacity {
385            return Err(Error::CorruptFilesystem {
386                context: "file size exceeds volume capacity",
387            });
388        }
389        // The capacity check above is not sufficient on its own: a corrupt BPB
390        // can also claim a huge volume (total_sectors), letting a ~4 GiB file
391        // size through on a tiny image. So cap the up-front allocation and
392        // grow only as actual data arrives — reads past the real data yield
393        // short reads or I/O errors, not gigabytes of zeros.
394        const MAX_PREALLOC: usize = 16 * 1024 * 1024;
395        let mut buf = alloc::vec![0u8; remaining.min(MAX_PREALLOC)];
396        let mut filled = 0;
397        while filled < remaining {
398            if filled == buf.len() {
399                buf.resize((buf.len() * 2).min(remaining), 0);
400            }
401            let n = self.read_to_buf(&mut buf[filled..]).await?;
402            if n == 0 {
403                break;
404            }
405            filled += n;
406        }
407        buf.truncate(filled);
408        Ok(buf)
409    }
410}
411
412/// Extension trait for FatVolume to read files directly.
413pub trait FatVolumeReadExt<DATA: Read + Seek> {
414    /// Create a reader for a file entry.
415    fn read_file<'a>(&'a self, entry: &FileEntry) -> Result<FileReader<'a, DATA>>;
416}
417
418impl<DATA: Read + Seek> FatVolumeReadExt<DATA> for FatVolume<DATA> {
419    fn read_file<'a>(&'a self, entry: &FileEntry) -> Result<FileReader<'a, DATA>> {
420        FileReader::new(self, entry)
421    }
422}
423
424} // end io_transform!