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};
12#[cfg(feature = "write")]
13use crate::file::ShortFileName;
14use super::{
15    fs::FatVolume, dir::FileEntry,
16    io::{Cluster, ClusterLike, ErrorKind, Read, Seek, SeekFrom},
17};
18
19/// A reader for file content in a FAT filesystem.
20///
21/// This struct provides a `Read` implementation that follows the cluster chain
22/// to read file contents.
23///
24/// # Buffering
25///
26/// When the `alloc` feature is enabled, the reader can optionally buffer data
27/// to reduce the number of seek and read operations:
28///
29/// - [`with_buffer`](Self::with_buffer): Enable cluster-level buffering. Each cluster
30///   is read entirely into memory and subsequent reads are served from the buffer.
31///
32/// - [`with_cached_chain`](Self::with_cached_chain): Pre-cache the entire cluster chain.
33///   This is useful for small files where you want to avoid repeated FAT lookups.
34pub struct FileReader<'a, DATA: Read + Seek> {
35    fs: &'a FatVolume<DATA>,
36    /// First cluster of the file, as recorded in the directory entry.
37    first_cluster: Cluster<usize>,
38    cluster: Cluster<usize>,
39    /// Offset within the current cluster
40    offset_in_cluster: usize,
41    /// Current logical position in the file
42    position: u64,
43    /// Total size of the file
44    size: usize,
45    /// Cluster transitions taken so far. Bounded by `Fat::max_cluster()` so a
46    /// corrupt looping chain surfaces as `Error::ClusterLoop` instead of
47    /// hanging the reader.
48    cluster_steps: u32,
49    /// Optional cluster buffer for reduced I/O
50    #[cfg(feature = "alloc")]
51    cluster_buffer: Option<Vec<u8>>,
52    /// Pre-cached cluster chain (optional)
53    #[cfg(feature = "alloc")]
54    cached_chain: Option<Vec<u32>>,
55    /// Current index in the cached chain
56    #[cfg(feature = "alloc")]
57    chain_index: usize,
58    /// Directory-slot coordinates captured at open, used to revalidate that the
59    /// file has not been deleted (and its clusters reused) before serving reads.
60    /// Only meaningful with `write`: a read-only volume can never mutate an
61    /// entry, so a reader can never become stale.
62    #[cfg(feature = "write")]
63    entry_parent: Cluster<usize>,
64    #[cfg(feature = "write")]
65    entry_offset: usize,
66    #[cfg(feature = "write")]
67    entry_short_name: ShortFileName,
68    /// Creation timestamp captured at open, revalidated alongside the name.
69    #[cfg(feature = "write")]
70    entry_created: crate::time::FatDateTime,
71}
72
73impl<'a, DATA: Read + Seek> FileReader<'a, DATA> {
74    /// Create a new FileReader for a file entry.
75    ///
76    /// Returns an error if the entry is a directory.
77    pub fn new(fs: &'a FatVolume<DATA>, entry: &FileEntry) -> Result<Self> {
78        if entry.is_directory() {
79            return Err(Error::NotAFile);
80        }
81
82        Ok(Self {
83            fs,
84            first_cluster: entry.cluster(),
85            cluster: entry.cluster(),
86            offset_in_cluster: 0,
87            position: 0,
88            size: entry.len() as usize,
89            cluster_steps: 0,
90            #[cfg(feature = "alloc")]
91            cluster_buffer: None,
92            #[cfg(feature = "alloc")]
93            cached_chain: None,
94            #[cfg(feature = "alloc")]
95            chain_index: 0,
96            #[cfg(feature = "write")]
97            entry_parent: entry.parent_clus,
98            #[cfg(feature = "write")]
99            entry_offset: entry.offset_within_cluster,
100            #[cfg(feature = "write")]
101            entry_short_name: entry.short_name,
102            #[cfg(feature = "write")]
103            entry_created: entry.created,
104        })
105    }
106
107    /// Returns the total size of the file in bytes.
108    pub fn size(&self) -> usize {
109        self.size
110    }
111
112    /// Returns the current logical position in the file.
113    pub fn position(&self) -> u64 {
114        self.position
115    }
116
117    /// Returns the number of bytes remaining to be read.
118    pub fn remaining(&self) -> usize {
119        (self.size as u64).saturating_sub(self.position) as usize
120    }
121
122    /// Enable cluster-level buffering.
123    ///
124    /// When enabled, each cluster is read entirely into memory on first access,
125    /// and subsequent reads within that cluster are served from the buffer.
126    /// This reduces the number of seek operations at the cost of memory usage.
127    ///
128    /// Memory usage: One cluster size (typically 4KB to 64KB).
129    #[cfg(feature = "alloc")]
130    pub fn with_buffer(mut self) -> Self {
131        self.cluster_buffer = Some(Vec::new());
132        self
133    }
134
135    /// Pre-cache the entire cluster chain.
136    ///
137    /// This reads the entire FAT chain for the file into memory, eliminating
138    /// the need for FAT lookups during sequential reads. This is most beneficial
139    /// for fragmented files or when performing many random seeks. The current
140    /// logical position is preserved.
141    ///
142    /// Memory usage: 4 bytes per cluster in the file.
143    #[cfg(feature = "alloc")]
144    pub async fn with_cached_chain(mut self) -> Result<Self> {
145        if self.first_cluster.0 < 2 {
146            // Empty file, no chain to cache
147            self.cached_chain = Some(Vec::new());
148            return Ok(self);
149        }
150
151        let max_clusters = self.fs.info.max_cluster as usize;
152        let mut data = self.fs.data.lock();
153        let cluster_size = data.cluster_size;
154        let chain = self
155            .fs
156            .fat
157            .read_chain(
158                data.deref_mut(),
159                self.first_cluster.0 as u32,
160                max_clusters,
161            )
162            .await?;
163        drop(data);
164
165        if self.position < self.size as u64 {
166            let chain_index = self.position as usize / cluster_size;
167            let cluster = chain
168                .get(chain_index)
169                .copied()
170                .ok_or(Error::UnexpectedEndOfChain {
171                    cluster: chain
172                        .last()
173                        .copied()
174                        .unwrap_or(self.first_cluster.0 as u32),
175                })?;
176            self.cluster.0 = cluster as usize;
177            self.offset_in_cluster = self.position as usize % cluster_size;
178            self.chain_index = chain_index;
179        }
180        self.cached_chain = Some(chain);
181        Ok(self)
182    }
183
184    /// Read data from the file.
185    ///
186    /// Reads up to `buf.len()` bytes, or fewer at end-of-file. Use a small `buf` to
187    /// stream incrementally; a larger `buf` allows more bytes per call (including
188    /// contiguous-cluster bulk I/O when the chain is cached). The underlying
189    /// [`Read`](crate::io::Read) / [`Seek`](crate::io::Seek) implementation can enforce
190    /// alignment or transfer sizes as needed. Optional per-cluster buffering applies when
191    /// enabled.
192    pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
193        // Cap by caller buffer and bytes left in the file; actual I/O may be one or many
194        // steps inside read_to_buf (cached contiguous runs vs. read_one_chunk loop).
195        let max = buf.len().min(self.remaining());
196        if max == 0 {
197            return Ok(0);
198        }
199        self.read_to_buf(&mut buf[..max]).await
200    }
201
202    /// Read into `buf` until the buffer is full or the end of the file is reached.
203    ///
204    /// At most `buf.len().min(self.remaining())` bytes are read. Returns the number of
205    /// bytes written to the start of `buf`.
206    ///
207    /// When the cluster chain is cached (`with_cached_chain`), contiguous runs of
208    /// clusters are read in one seek+read per run (one I/O per fragment) instead of
209    /// one per cluster.
210    async fn read_to_buf(&mut self, buf: &mut [u8]) -> Result<usize> {
211        let max = buf.len().min(self.remaining());
212        if max == 0 {
213            return Ok(0);
214        }
215        // Before serving any bytes, confirm the file's directory slot still holds
216        // this exact entry. If it was deleted (and its clusters possibly reused)
217        // since the reader opened, revalidation returns `StaleEntry` rather than
218        // disclosing an unrelated file's data. Only reachable with `write`; a
219        // read-only volume can never mutate an entry out from under a reader.
220        #[cfg(feature = "write")]
221        self.fs
222            .revalidate_slot(
223                self.entry_parent,
224                self.entry_offset,
225                &self.entry_short_name,
226                &self.entry_created,
227            )
228            .await?;
229        let buf = &mut buf[..max];
230
231        #[cfg(feature = "alloc")]
232        {
233            let mut data = self.fs.data.lock();
234            let cluster_size = data.cluster_size;
235            let data_start = self.fs.info.data_start;
236
237            // Cached chain: coalesce physically consecutive clusters into one seek+read per run.
238            if let Some(ref chain) = self.cached_chain {
239                let buf_len = buf.len();
240                // Fast path: one seek + one read per contiguous run of clusters
241                let mut total = 0usize;
242                let mut chain_index = self.chain_index;
243                let mut offset_in_cluster = self.offset_in_cluster;
244
245                while total < buf_len && chain_index < chain.len() {
246                    let first_cluster = chain[chain_index] as usize;
247                    // Count contiguous run: chain[chain_index], chain[chain_index+1], ... while consecutive
248                    let mut run_len = 1usize;
249                    while chain_index + run_len < chain.len()
250                        && chain[chain_index + run_len] == chain[chain_index + run_len - 1] + 1
251                    {
252                        run_len += 1;
253                    }
254
255                    // Bytes we can read from this run (from current offset to end of run)
256                    let bytes_from_first = cluster_size.saturating_sub(offset_in_cluster);
257                    let bytes_from_run = bytes_from_first
258                        .saturating_add(run_len.saturating_sub(1).saturating_mul(cluster_size));
259                    let to_read = bytes_from_run.min(buf_len - total);
260
261                    if to_read == 0 {
262                        break;
263                    }
264
265                    let seek_pos = Cluster(first_cluster)
266                        .to_bytes(data_start, cluster_size)
267                        .saturating_add(offset_in_cluster);
268                    data.seek(SeekFrom::Start(seek_pos as u64)).await?;
269                    data.read_exact(&mut buf[total..total + to_read]).await?;
270
271                    total += to_read;
272                    self.position += to_read as u64;
273
274                    // Advance by clusters we consumed
275                    let new_offset = offset_in_cluster + to_read;
276                    chain_index += new_offset / cluster_size;
277                    offset_in_cluster = new_offset % cluster_size;
278                }
279
280                self.chain_index = chain_index;
281                self.offset_in_cluster = offset_in_cluster;
282                if chain_index < chain.len() {
283                    self.cluster.0 = chain[chain_index] as usize;
284                }
285                drop(data);
286                return Ok(total);
287            }
288
289            drop(data);
290        }
291
292        // No cached chain (or alloc off): walk the FAT one cluster-sized step at a time.
293        let buf_len = buf.len();
294        let mut total = 0usize;
295        while total < buf_len {
296            let n = self.read_one_chunk(&mut buf[total..]).await?;
297            if n == 0 {
298                break;
299            }
300            total += n;
301        }
302        Ok(total)
303    }
304
305    /// Read at most one contiguous span within the current cluster (or less if `buf` or the
306    /// file ends sooner). Used by [`read_to_buf`](Self::read_to_buf) when the chain is not
307    /// cached; advances to the next FAT cluster when the current one is exhausted.
308    async fn read_one_chunk(&mut self, buf: &mut [u8]) -> Result<usize> {
309        // End of logical file
310        if self.position >= self.size as u64 {
311            return Ok(0);
312        }
313
314        // A directory entry whose first cluster is 0 has no data allocated
315        // (1 is reserved); report EOF instead of underflowing the
316        // cluster-to-offset math.
317        if self.cluster.0 < 2 {
318            return Ok(0);
319        }
320
321        let mut data = self.fs.data.lock();
322        let cluster_size = data.cluster_size;
323
324        // Consumed the whole cluster: follow the chain to the next data cluster.
325        if self.offset_in_cluster >= cluster_size {
326            #[cfg(feature = "alloc")]
327            {
328                if let Some(ref chain) = self.cached_chain {
329                    // Next cluster from the pre-read chain (no FAT table walk).
330                    self.chain_index += 1;
331                    if self.chain_index >= chain.len() {
332                        return Ok(0); // End of file
333                    }
334                    self.cluster.0 = chain[self.chain_index] as usize;
335                    self.offset_in_cluster = 0;
336                    // Cluster buffer holds one cluster; must reload after moving.
337                    if let Some(ref mut buffer) = self.cluster_buffer {
338                        buffer.clear();
339                    }
340                } else {
341                    // Resolve next cluster from the FAT on disk.
342                    self.cluster_steps = self.cluster_steps.saturating_add(1);
343                    if self.cluster_steps > self.fs.fat.max_cluster() {
344                        return Err(Error::ClusterLoop {
345                            cluster: self.cluster.0 as u32,
346                        });
347                    }
348                    // Drop data lock so next_cluster_routed can acquire
349                    // cache+data in canonical order; re-lock after.
350                    drop(data);
351                    let next = self.fs.next_cluster_routed(self.cluster.0).await?;
352                    data = self.fs.data.lock();
353                    match next {
354                        Some(cluster) => {
355                            self.cluster.0 = cluster as usize;
356                            self.offset_in_cluster = 0;
357                            if let Some(ref mut buffer) = self.cluster_buffer {
358                                buffer.clear();
359                            }
360                        }
361                        None => return Ok(0), // End of cluster chain
362                    }
363                }
364            }
365
366            #[cfg(not(feature = "alloc"))]
367            {
368                self.cluster_steps = self.cluster_steps.saturating_add(1);
369                if self.cluster_steps > self.fs.fat.max_cluster() {
370                    return Err(Error::ClusterLoop {
371                        cluster: self.cluster.0 as u32,
372                    });
373                }
374                // Drop data lock so next_cluster_routed can acquire
375                // cache+data in canonical order; re-lock after.
376                drop(data);
377                let next = self.fs.next_cluster_routed(self.cluster.0).await?;
378                data = self.fs.data.lock();
379                match next {
380                    Some(cluster) => {
381                        self.cluster.0 = cluster as usize;
382                        self.offset_in_cluster = 0;
383                    }
384                    None => return Ok(0), // End of cluster chain
385                }
386            }
387        }
388
389        // How much we can copy from the current cluster in this step.
390        let bytes_left_in_cluster = cluster_size - self.offset_in_cluster;
391        let bytes_left_in_file = (self.size as u64 - self.position) as usize;
392        let read_max = buf.len().min(bytes_left_in_cluster).min(bytes_left_in_file);
393
394        if read_max == 0 {
395            return Ok(0);
396        }
397
398        #[cfg(feature = "alloc")]
399        let bytes_read = if let Some(ref mut buffer) = self.cluster_buffer {
400            // Load whole cluster once, then serve reads from RAM.
401            if buffer.is_empty() {
402                let cluster_start = self.cluster.to_bytes(self.fs.info.data_start, cluster_size);
403                data.seek(SeekFrom::Start(cluster_start as u64)).await?;
404
405                buffer.resize(cluster_size, 0);
406                data.read_exact(buffer).await?;
407            }
408
409            let src = &buffer[self.offset_in_cluster..self.offset_in_cluster + read_max];
410            buf[..read_max].copy_from_slice(src);
411            read_max
412        } else {
413            // Direct read at file offset within this cluster.
414            let seek_pos = self.cluster.to_bytes(self.fs.info.data_start, cluster_size)
415                + self.offset_in_cluster;
416            data.seek(SeekFrom::Start(seek_pos as u64)).await?;
417            data.read(&mut buf[..read_max]).await?
418        };
419
420        #[cfg(not(feature = "alloc"))]
421        let bytes_read = {
422            let seek_pos = self.cluster.to_bytes(self.fs.info.data_start, cluster_size)
423                + self.offset_in_cluster;
424            data.seek(SeekFrom::Start(seek_pos as u64)).await?;
425            data.read(&mut buf[..read_max]).await?
426        };
427
428        self.offset_in_cluster += bytes_read;
429        self.position += bytes_read as u64;
430
431        Ok(bytes_read)
432    }
433
434    /// Reposition the reader within the file.
435    ///
436    /// Follows `std::io::Seek` semantics: `Start`/`Current`/`End` are all
437    /// supported, seeking beyond the end of the file is allowed (subsequent
438    /// reads return 0), and seeking before the start is an error. Returns the
439    /// new position from the start of the file.
440    pub async fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
441        fn invalid(message: &'static str) -> Error {
442            Error::Io(hadris_io::Error::new(ErrorKind::InvalidInput, message))
443        }
444
445        let target = match pos {
446            SeekFrom::Start(position) => Some(position),
447            SeekFrom::Current(offset) => self.position.checked_add_signed(offset),
448            SeekFrom::End(offset) => (self.size as u64).checked_add_signed(offset),
449        }
450        .ok_or_else(|| invalid("invalid seek position"))?;
451
452        if target >= self.size as u64 || self.first_cluster.0 < 2 {
453            self.position = target;
454            return Ok(target);
455        }
456
457        let target_usize = target as usize;
458        let cluster_size = self.fs.data.lock().cluster_size;
459        let target_cluster = target_usize / cluster_size;
460        let target_offset = target_usize % cluster_size;
461
462        #[cfg(feature = "alloc")]
463        if let Some(ref chain) = self.cached_chain {
464            let cluster = *chain
465                .get(target_cluster)
466                .ok_or(Error::UnexpectedEndOfChain {
467                    cluster: chain.last().copied().unwrap_or(self.first_cluster.0 as u32),
468                })?;
469            if let Some(ref mut buffer) = self.cluster_buffer {
470                buffer.clear();
471            }
472            self.chain_index = target_cluster;
473            self.cluster.0 = cluster as usize;
474            self.offset_in_cluster = target_offset;
475            self.position = target;
476            return Ok(target);
477        }
478
479        let current_cluster = (self.position as usize).saturating_sub(self.offset_in_cluster)
480            / cluster_size;
481
482        let (mut cluster, mut hops, mut cluster_steps) = if self.position >= self.size as u64 {
483            (self.first_cluster.0, target_cluster, 0)
484        } else if target_cluster >= current_cluster {
485            (
486                self.cluster.0,
487                target_cluster - current_cluster,
488                self.cluster_steps,
489            )
490        } else {
491            (self.first_cluster.0, target_cluster, 0)
492        };
493
494        while hops > 0 {
495            cluster_steps = cluster_steps.saturating_add(1);
496            if cluster_steps > self.fs.fat.max_cluster() {
497                return Err(Error::ClusterLoop {
498                    cluster: cluster as u32,
499                });
500            }
501            cluster = self
502                .fs
503                .next_cluster_routed(cluster)
504                .await?
505                .ok_or(Error::UnexpectedEndOfChain {
506                    cluster: cluster as u32,
507                })? as usize;
508            hops -= 1;
509        }
510
511        #[cfg(feature = "alloc")]
512        if let Some(ref mut buffer) = self.cluster_buffer {
513            buffer.clear();
514        }
515        self.cluster_steps = cluster_steps;
516        self.cluster.0 = cluster;
517        self.offset_in_cluster = target_offset;
518        self.position = target;
519
520        Ok(target)
521    }
522
523    /// Read all bytes from the current read position through the end of the file.
524    ///
525    /// Data is read starting at this reader's current offset in the file (the same
526    /// position the next [`read`](Self::read) would use—not necessarily offset 0). Bytes
527    /// already consumed by earlier [`read`](Self::read) or `read_to_vec` calls are not read
528    /// again. Bytes are read using the same internal bulk-read path as [`read`](Self::read).
529    #[cfg(feature = "alloc")]
530    pub async fn read_to_vec(&mut self) -> Result<Vec<u8>> {
531        let remaining = self.remaining();
532        // `size` derives from the untrusted directory-entry file size (u32, up to
533        // ~4 GiB). A file cannot exceed the volume's cluster heap, so bound the
534        // up-front allocation against it — otherwise a corrupt entry in a tiny
535        // image could force a multi-gigabyte allocation (a DoS that aborts the
536        // process on no-overcommit / embedded targets).
537        let volume_capacity = self.fs.info.max_cluster as u64 * self.fs.info.cluster_size as u64;
538        if remaining as u64 > volume_capacity {
539            return Err(Error::CorruptFilesystem {
540                context: "file size exceeds volume capacity",
541            });
542        }
543        // The capacity check above is not sufficient on its own: a corrupt BPB
544        // can also claim a huge volume (total_sectors), letting a ~4 GiB file
545        // size through on a tiny image. So cap the up-front allocation and
546        // grow only as actual data arrives — reads past the real data yield
547        // short reads or I/O errors, not gigabytes of zeros.
548        const MAX_PREALLOC: usize = 16 * 1024 * 1024;
549        let mut buf = alloc::vec![0u8; remaining.min(MAX_PREALLOC)];
550        let mut filled = 0;
551        while filled < remaining {
552            if filled == buf.len() {
553                buf.resize((buf.len() * 2).min(remaining), 0);
554            }
555            let n = self.read_to_buf(&mut buf[filled..]).await?;
556            if n == 0 {
557                break;
558            }
559            filled += n;
560        }
561        buf.truncate(filled);
562        Ok(buf)
563    }
564}
565
566/// Extension trait for FatVolume to read files directly.
567pub trait FatVolumeReadExt<DATA: Read + Seek> {
568    /// Create a reader for a file entry.
569    fn read_file<'a>(&'a self, entry: &FileEntry) -> Result<FileReader<'a, DATA>>;
570}
571
572impl<DATA: Read + Seek> FatVolumeReadExt<DATA> for FatVolume<DATA> {
573    fn read_file<'a>(&'a self, entry: &FileEntry) -> Result<FileReader<'a, DATA>> {
574        FileReader::new(self, entry)
575    }
576}
577
578} // end io_transform!