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!