ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_fs_fat_exfat_read.c
Go to the documentation of this file.
1
13
14#include <stddef.h>
15#include <stdint.h>
16
17#include "ra8_attributes.h"
18#include "ra8_fs.h"
19#include "ra8_fs_fat_internal.h"
20
21/* ===========================================================================
22 * exFAT support (read, whole-file write, mkfs)
23 * ===========================================================================
24 */
25
26/* `priv_strlen()`: see header for the documented contract. */
27uint32_t priv_strlen(const char* s)
28{
29 uint32_t n = 0U;
30 while (s[n] != '\0') {
31 n++;
32 }
33 return n;
34}
35
36/* `priv_ascii_upper()`: see header for the documented contract. */
37char priv_ascii_upper(char c)
38{
39 if (c < 'a') {
40 return c;
41 }
42 if (c > 'z') {
43 return c;
44 }
45 return (char)(c - ('a' - 'A'));
46}
47
66static uint8_t internal_exfat_is_volume(const uint8_t* buf)
67{
68 static const char sig[k_exfat_fsname_len] = {'E', 'X', 'F', 'A', 'T', ' ', ' ', ' '};
69 for (uint32_t i = 0U; i < (uint32_t)k_exfat_fsname_len; i++) {
70 if (buf[(uint32_t)k_exfat_off_fsname + i] != (uint8_t)sig[i]) {
71 return 0U;
72 }
73 }
74 return 1U;
75}
76
106static ra8_err_t internal_exfat_parse(ra8_fs_mount_t* m, const uint8_t* buf)
107{
108 const uint8_t shift = buf[k_exfat_off_bps_shift];
109 if ((shift < (uint8_t)k_exfat_bps_shift_min) || (shift > (uint8_t)k_exfat_bps_shift_max) ||
110 ((1UL << shift) != (unsigned long)m->bytes_per_sector)) {
112 }
115 m->num_fats = (uint32_t)buf[k_exfat_off_num_fats];
121 m->reserved_sectors = 0U;
122 m->root_entries = 0U;
123 m->first_root_lba = 0U;
125 /* Last, because it walks the root directory and so needs every field above.
126 * It cannot fail the mount: a volume it could not interrogate is simply one
127 * whose fold this build will not vouch for (#606). */
129 return k_ra8_ok;
130}
131
132/* `priv_exfat_upcase_verify()`: see header for the documented contract. */
134{
135 m->exfat_upcase_ok = 0U;
136 exfat_cursor_t cur = {.cluster = m->root_cluster, .entry_in_cluster = 0U, .scanned = 0U};
137 while (cur.scanned < (uint32_t)k_exfat_scan_limit) {
138 uint8_t e[k_exfat_entry_bytes] = {};
139 const ra8_err_t r = priv_exfat_next_entry(m, &cur, e);
140 if (r != k_ra8_ok) {
141 return;
142 }
143 if (e[0] == (uint8_t)k_exfat_entry_eod) {
144 return;
145 }
146 if (e[0] != (uint8_t)k_exfat_entry_upcase) {
147 continue;
148 }
150 m->exfat_upcase_ok = 1U;
151 }
152 return;
153 }
154 /* Falls out of the scan loop only after 65536 directory entries ahead of the
155 * up-case one, which no volume this driver mounts has. The loop's own exits
156 * (the up-case match and the EOD marker) are the real ends; there is no
157 * trailing return, because a void function does not need one and clang-tidy
158 * flags it if it has one. */
159}
160
161/* `priv_parse_volume()`: see header for the documented contract. */
169
170/* `priv_exfat_dir_root()`: see header for the documented contract. */
172{
173 out->cluster = m->root_cluster;
174 out->contig_end = 0U; /* the root is always FAT-chained; the format has no other shape */
175 /* The root owns no File-entry set: its extent is the boot sector's FAT chain,
176 * not a Stream entry, so ::priv_exfat_grow_dir extends the chain and patches
177 * no metadata. 0 is the "no entry set" sentinel (#677). */
178 out->self_cluster = 0U;
179 out->self_index = 0U;
180}
181
182/* `priv_exfat_dir_from_set()`: see header for the documented contract. */
183void priv_exfat_dir_from_set(const ra8_fs_mount_t* m, const uint8_t* strm, exfat_dir_t* out)
184{
186 out->contig_end = 0U;
187 if ((strm[k_exfat_strm_off_flags] & (uint8_t)k_exfat_secflag_no_fat) == 0U) {
188 return;
189 }
190 const uint32_t cbytes = priv_cluster_bytes(m);
191 const uint64_t size = priv_rd64(&strm[k_exfat_strm_off_dlen]);
192 out->contig_end = out->cluster + (uint32_t)((size + cbytes - 1U) / cbytes);
193}
194
195/* `priv_exfat_cursor_init()`: see header for the documented contract. */
197{
198 out->cluster = dir->cluster;
199 out->entry_in_cluster = 0U;
200 out->scanned = 0U;
201 out->contig_end = dir->contig_end;
202}
203
204/* `priv_exfat_step_cluster()`: see header for the documented contract. */
206 uint32_t cluster,
207 uint32_t contig_end,
208 uint32_t* out_next)
209{
210 if (contig_end != 0U) {
211 const uint32_t adjacent = cluster + 1U;
212 if (adjacent >= contig_end) {
213 return k_ra8_err_not_found;
214 }
215 *out_next = adjacent;
216 return k_ra8_ok;
217 }
218 uint32_t next = 0U;
219 const ra8_err_t e = priv_fat_get(m, cluster, &next);
220 if (e != k_ra8_ok) {
221 return e;
222 }
223 if (priv_is_eoc(m, next) != 0U) {
224 return k_ra8_err_not_found;
225 }
226 *out_next = next;
227 return k_ra8_ok;
228}
229
230/* `priv_exfat_next_entry()`: see header for the documented contract. */
232{
233 const uint32_t per_cluster = priv_cluster_bytes(m) / (uint32_t)k_exfat_entry_bytes;
234 if (cur->entry_in_cluster >= per_cluster) {
235 uint32_t next = 0U;
236 const ra8_err_t se = priv_exfat_step_cluster(m, cur->cluster, cur->contig_end, &next);
237 if (se != k_ra8_ok) {
238 return se;
239 }
240 cur->cluster = next;
241 cur->entry_in_cluster = 0U;
242 }
243 const uint32_t byte_off = cur->entry_in_cluster * (uint32_t)k_exfat_entry_bytes;
244 const uint64_t lba = priv_cluster_to_lba(m, cur->cluster) + (byte_off / priv_bps(m));
245 uint8_t* const sec = priv_sec_io();
246 ra8_err_t e = priv_read_sector(m, lba, sec);
247 if (e != k_ra8_ok) {
248 return e;
249 }
250 priv_byte_copy(out, &sec[byte_off % priv_bps(m)], (uint32_t)k_exfat_entry_bytes);
251 cur->entry_in_cluster++;
252 cur->scanned++;
253 return k_ra8_ok;
254}
255
256/* `priv_exfat_name_chunk_eq()`: see header for the documented contract. */
257uint8_t
258priv_exfat_name_chunk_eq(const uint8_t* entry, const uint16_t* name, uint32_t pos, uint32_t nlen)
259{
260 for (uint32_t i = 0U; i < (uint32_t)k_exfat_name_per_entry; i++) {
261 if ((pos + i) >= nlen) {
262 return 1U;
263 }
264 const uint32_t b = (uint32_t)k_exfat_name_off + (i * 2U);
265 const uint16_t unit = priv_rd16(&entry[b]);
266 if (priv_exfat_upcase_unit(unit) != priv_exfat_upcase_unit(name[pos + i])) {
267 return 0U;
268 }
269 }
270 return 1U;
271}
272
273/* `priv_exfat_name_to_units()`: see header for the documented contract. */
275 const char* path,
276 uint16_t* out,
277 uint32_t* out_units)
278{
279 const ra8_err_t err = priv_utf8_to_utf16(path, out, (uint32_t)k_exfat_name_cap, out_units);
280 if (err != k_ra8_ok) {
281 return err;
282 }
283 /* An ASCII name folds the same way under every conforming up-case table, so
284 * it never depends on the volume carrying the one this build embeds. Anything
285 * else does, and a NameHash computed with the wrong fold is a disagreement
286 * with the on-disk format rather than a limitation of this API (#606). */
287 if (m->exfat_upcase_ok != 0U) {
288 return k_ra8_ok;
289 }
290 if (priv_utf16_all_ascii(out, *out_units) != 0U) {
291 return k_ra8_ok;
292 }
293 *out_units = 0U;
295}
296
297/* `priv_exfat_needle_units()`: see header for the documented contract. */
299 const char* name,
300 uint16_t* out,
301 uint32_t* out_units,
302 ra8_err_t too_long)
303{
304 const ra8_err_t e = priv_exfat_name_to_units(m, name, out, out_units);
305 return (e == k_ra8_err_no_mem) ? too_long : e;
306}
307
337 exfat_cursor_t* cur,
338 const uint16_t* name,
339 uint32_t nlen,
340 uint8_t* out_strm)
341{
342 uint8_t strm[k_exfat_entry_bytes] = {};
343 ra8_err_t e = priv_exfat_next_entry(m, cur, strm);
344 if (e != k_ra8_ok) {
345 return e;
346 }
347 if (strm[0] != (uint8_t)k_exfat_entry_stream) {
348 return k_ra8_err_not_found;
349 }
350 if ((uint32_t)strm[k_exfat_strm_off_nlen] != nlen) {
351 return k_ra8_err_not_found;
352 }
353 for (uint32_t pos = 0U; pos < nlen; pos += (uint32_t)k_exfat_name_per_entry) {
354 uint8_t nm[k_exfat_entry_bytes] = {};
355 e = priv_exfat_next_entry(m, cur, nm);
356 if (e != k_ra8_ok) {
357 return e;
358 }
359 if (nm[0] != (uint8_t)k_exfat_entry_name) {
360 return k_ra8_err_not_found;
361 }
362 if (priv_exfat_name_chunk_eq(nm, name, pos, nlen) == 0U) {
363 return k_ra8_err_not_found;
364 }
365 }
366 priv_byte_copy(out_strm, strm, (uint32_t)k_exfat_entry_bytes);
367 return k_ra8_ok;
368}
369
370/* `priv_exfat_find()`: see header for the documented contract. */
372 const exfat_dir_t* dir,
373 const char* path,
374 uint8_t* out_strm,
375 uint8_t* out_attr)
376{
377 /* Leading slashes are not part of the name; match FAT's priv_path_to_83
378 * behavior so ra8_fs_open("/name") resolves on exFAT too (#93). */
379 while (*path == '/') {
380 path++;
381 }
382 uint16_t need[k_exfat_name_cap] = {};
383 uint32_t nlen = 0U;
384 const ra8_err_t ne = priv_exfat_name_to_units(m, path, need, &nlen);
385 if (ne == k_ra8_err_no_mem) {
386 /* Longer than any name this volume can hold, so it is genuinely absent --
387 * a lookup says so rather than reporting a resource failure. */
388 return k_ra8_err_not_found;
389 }
390 if (ne != k_ra8_ok) {
391 return ne;
392 }
393 exfat_cursor_t cur = {};
394 priv_exfat_cursor_init(dir, &cur);
395 while (cur.scanned < (uint32_t)k_exfat_scan_limit) {
396 uint8_t entry[k_exfat_entry_bytes] = {};
397 ra8_err_t e = priv_exfat_next_entry(m, &cur, entry);
398 if (e != k_ra8_ok) {
399 return e;
400 }
401 if (entry[0] == (uint8_t)k_exfat_entry_eod) {
402 return k_ra8_err_not_found;
403 }
404 if (entry[0] != (uint8_t)k_exfat_entry_file) {
405 continue;
406 }
407 e = internal_exfat_match_set(m, &cur, need, nlen, out_strm);
408 if (e == k_ra8_ok) {
409 /* FileAttributes is 16-bit but every bit we act on (directory, archive)
410 * lives in the low byte, so the caller gets that byte. */
411 *out_attr = entry[k_exfat_off_file_attr];
412 return k_ra8_ok;
413 }
414 if (e != k_ra8_err_not_found) {
415 return e;
416 }
417 }
418 return k_ra8_err_not_found; /* GCOVR_EXCL_LINE -- 65536 entries required */
419}
420
447static void
448internal_exfat_seed_read(ra8_fs_file_t* file, ra8_fs_mount_t* handle, const uint8_t* strm)
449{
450 const uint32_t first = priv_rd32(&strm[k_exfat_strm_off_clus]);
451 file->mount = handle;
452 file->first_cluster = first;
453 file->cur_cluster = first;
454 file->walk_cache_idx = 0U; /* read accelerator seeded at the chain head */
455 file->walk_cache_cluster = first;
456 file->size_bytes = priv_rd64(&strm[k_exfat_strm_off_dlen]);
457 file->offset = 0U;
458 file->dir_entry_lba = 0U;
459 file->dir_entry_idx = 0U;
460 file->valid_bytes = priv_rd64(&strm[k_exfat_off_strm_valid]);
461 file->entry_set_cluster = 0U;
462 file->entry_set_index = 0U;
463 file->entry_set_count = 0U;
464 file->alloc_clusters = 0U;
465 file->tail_cluster = 0U;
466 file->mode = k_ra8_fs_mode_read;
467 file->no_fat_chain =
468 ((strm[k_exfat_strm_off_flags] & (uint8_t)k_exfat_secflag_no_fat) != 0U) ? 1U : 0U;
469 file->in_use = 1U;
470 file->dirty = 0U;
471}
472
473/* `priv_exfat_open()`: see header for the documented contract. */
475 const char* path,
476 ra8_fs_mode_t mode,
477 ra8_fs_file_t** out_file)
478{
479 /* Writing modes need the file's entry-set coordinates, its allocation and
480 * its ValidDataLength -- none of which a read open has any use for -- so
481 * they get their own path rather than a second half bolted onto this one
482 * (#602). */
483 if (mode != k_ra8_fs_mode_read) {
484 return priv_exfat_open_write(handle, path, mode, out_file);
485 }
486 uint8_t strm[k_exfat_entry_bytes] = {};
487 uint8_t attr = 0U;
488 const ra8_err_t e = priv_exfat_lookup(handle, path, strm, &attr);
489 if (e != k_ra8_ok) {
490 return e;
491 }
492 /* A directory answers the name lookup exactly like a file and reports
493 * DataLength 0, so without this it opens as a bogus empty file (#604). */
494 if ((attr & (uint8_t)k_exfat_attr_directory) != 0U) {
496 }
498 if (f == nullptr) {
499 return k_ra8_err_no_mem;
500 }
501 internal_exfat_seed_read(f, handle, strm);
502 *out_file = f;
503 return k_ra8_ok;
504}
Annotation-attribute framework macros for ra8-firmware.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
@ k_ra8_err_not_supported
Requested feature not compiled in, not wired, or not supported by this MCU variant.
Definition ra8_err.h:180
@ k_ra8_err_no_mem
Static buffer exhausted (no dynamic memory on this project).
Definition ra8_err.h:142
@ k_ra8_err_invalid_arg
Invalid function argument.
Definition ra8_err.h:152
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ k_ra8_err_not_found
Requested item not found (lookup / search missed).
Definition ra8_err.h:173
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
Minimal FAT12/FAT16/FAT32 filesystem adapter (read + write).
uint32_t priv_rd32(const uint8_t *p)
Decode a little-endian uint32_t from a byte buffer.
Definition ra8_fs_fat.c:48
ra8_err_t priv_fat_get(const ra8_fs_mount_t *m, uint32_t cluster, uint32_t *out_value)
Fetch the FAT entry for cluster, returning the next-cluster value.
Definition ra8_fs_fat.c:187
uint16_t priv_rd16(const uint8_t *p)
Decode a little-endian uint16_t from a byte buffer.
Definition ra8_fs_fat.c:42
uint64_t priv_cluster_to_lba(const ra8_fs_mount_t *m, uint32_t cluster)
Convert a cluster number into its first data-region LBA.
Definition ra8_fs_fat.c:541
uint8_t priv_is_eoc(const ra8_fs_mount_t *m, uint32_t value)
Test whether value is an end-of-chain marker for this FAT type.
Definition ra8_fs_fat.c:511
ra8_err_t priv_read_sector(const ra8_fs_mount_t *m, uint64_t lba, uint8_t *buf)
Read a single sector into the module scratch buffer.
Definition ra8_fs_fat.c:134
uint32_t priv_bps(const ra8_fs_mount_t *m)
One mounted volume's sector size in bytes.
Definition ra8_fs_fat.c:84
void priv_byte_copy(uint8_t *dst, const uint8_t *src, uint32_t n)
Length-checked byte copy used in place of memcpy().
Definition ra8_fs_fat.c:110
uint64_t priv_rd64(const uint8_t *p)
Decode a little-endian uint64_t from a byte buffer.
Definition ra8_fs_fat.c:55
uint32_t priv_cluster_bytes(const ra8_fs_mount_t *m)
One mounted volume's cluster size in bytes.
Definition ra8_fs_fat.c:90
uint8_t * priv_sec_io(void)
The IO-role sector buffer (leaf data / bitmap sector transfers).
ra8_err_t priv_exfat_lookup(const ra8_fs_mount_t *m, const char *path, uint8_t *out_strm, uint8_t *out_attr)
Resolve a path and return the leaf entry set's location fields.
ra8_err_t priv_exfat_open_write(ra8_fs_mount_t *handle, const char *path, ra8_fs_mode_t mode, ra8_fs_file_t **out_file)
Open an exFAT file for writing: create, truncate, or position to append.
ra8_err_t priv_exfat_name_to_units(const ra8_fs_mount_t *m, const char *path, uint16_t *out, uint32_t *out_units)
Convert a caller's exFAT name to code units, refusing what will not fold.
ra8_err_t priv_exfat_open(ra8_fs_mount_t *handle, const char *path, ra8_fs_mode_t mode, ra8_fs_file_t **out_file)
Open a file (read-only) on a mounted exFAT volume.
ra8_err_t priv_exfat_next_entry(const ra8_fs_mount_t *m, exfat_cursor_t *cur, uint8_t *out)
Fetch the next 32-byte directory entry, following the cluster chain.
uint32_t priv_strlen(const char *s)
Length of a NUL-terminated string.
static void internal_exfat_seed_read(ra8_fs_file_t *file, ra8_fs_mount_t *handle, const uint8_t *strm)
Populate a read handle from the file's Stream-extension entry.
static ra8_err_t internal_exfat_parse(ra8_fs_mount_t *m, const uint8_t *buf)
Parse an exFAT VBR into the mount's geometry fields.
static uint8_t internal_exfat_is_volume(const uint8_t *buf)
Detect an exFAT volume from its boot sector.
uint8_t priv_exfat_name_chunk_eq(const uint8_t *entry, const uint16_t *name, uint32_t pos, uint32_t nlen)
Compare one file-name entry's 15 UTF-16 units against a needle.
void priv_exfat_upcase_verify(ra8_fs_mount_t *m)
Decide whether this build's fold matches the mounted volume's table.
static ra8_err_t internal_exfat_match_set(const ra8_fs_mount_t *m, exfat_cursor_t *cur, const uint16_t *name, uint32_t nlen, uint8_t *out_strm)
Read a stream-ext + name set and test it against path.
ra8_err_t priv_exfat_needle_units(const ra8_fs_mount_t *m, const char *name, uint16_t *out, uint32_t *out_units, ra8_err_t too_long)
Convert a leaf name to code units, mapping over-long to too_long.
void priv_exfat_dir_root(const ra8_fs_mount_t *m, exfat_dir_t *out)
Fill out with the volume root's directory location.
ra8_err_t priv_exfat_step_cluster(const ra8_fs_mount_t *m, uint32_t cluster, uint32_t contig_end, uint32_t *out_next)
Compute the cluster that follows cluster in a directory.
char priv_ascii_upper(char c)
Uppercase an ASCII character (others returned unchanged).
void priv_exfat_cursor_init(const exfat_dir_t *dir, exfat_cursor_t *out)
Aim a fresh cursor at the start of dir.
ra8_err_t priv_parse_volume(ra8_fs_mount_t *m)
Parse the volume at the current base: exFAT first, then FAT BPB.
ra8_err_t priv_exfat_find(const ra8_fs_mount_t *m, const exfat_dir_t *dir, const char *path, uint8_t *out_strm, uint8_t *out_attr)
Find a name in ONE exFAT directory.
void priv_exfat_dir_from_set(const ra8_fs_mount_t *m, const uint8_t *strm, exfat_dir_t *out)
Build a directory location from a Stream entry's allocation fields.
uint16_t priv_exfat_upcase_unit(uint16_t unit)
Fold a UTF-16 code unit through the canonical exFAT up-case table.
uint32_t priv_exfat_upcase_checksum(void)
Rotate-add checksum of the up-case table this build embeds.
Cross-TU shared declarations for the FAT/exFAT ra8_fs adapter.
ra8_fs_file_t * priv_alloc_file_slot(void)
Allocate a free entry from the file table; returns NULL if full.
uint8_t g_fs_scratch[k_ra8_fs_sector_max]
Single max-sector scratch buffer reused across all I/O.
ra8_err_t priv_parse_bpb_into_mount(ra8_fs_mount_t *m)
Parse the BPB layout fields out of g_fs_scratch into m.
@ k_exfat_entry_upcase
Up-case Table directory entry.
@ k_exfat_name_off
File-name entry character offset.
@ k_exfat_strm_off_dlen
Stream-ext DataLength (low 32 used).
@ k_exfat_off_root_clus
FirstClusterOfRootDirectory.
@ k_exfat_off_fat_lba
FatOffset (sectors from VBR).
@ k_exfat_strm_off_nlen
Stream-ext NameLength (UTF-16 units).
@ k_exfat_strm_off_clus
Stream-ext FirstCluster.
@ k_exfat_strm_off_flags
Stream-ext GeneralSecondaryFlags.
@ k_exfat_off_spc_shift
SectorsPerClusterShift (log2).
@ k_exfat_off_fat_len
FatLength (sectors).
@ k_exfat_off_bps_shift
BytesPerSectorShift (log2).
@ k_exfat_off_fsname
"EXFAT " filesystem-name field (8 chars).
@ k_exfat_off_num_fats
NumberOfFats.
@ k_exfat_off_clus_count
ClusterCount.
@ k_exfat_off_heap_lba
ClusterHeapOffset (sectors from VBR).
@ k_exfat_upc_off_csum
Up-case-table entry TableChecksum (32-bit).
@ k_exfat_secflag_no_fat
GeneralSecondaryFlags: NoFatChain.
@ k_exfat_name_per_entry
UTF-16 units per file-name entry.
@ k_exfat_entry_bytes
Directory entry size.
@ k_exfat_bps_shift_min
log2(512): smallest BytesPerSectorShift.
@ k_exfat_off_strm_valid
Stream entry: ValidDataLength (8 B).
@ k_exfat_entry_eod
End-of-directory marker.
@ k_exfat_entry_stream
Stream-extension entry.
@ k_exfat_off_file_attr
File entry: FileAttributes (2 bytes).
@ k_exfat_scan_limit
Max dir entries scanned (P10 bound).
@ k_exfat_attr_directory
FileAttributes: directory.
@ k_exfat_entry_name
File-name entry.
@ k_exfat_bps_shift_max
log2(4096): largest BytesPerSectorShift.
@ k_exfat_name_cap
Longest name we store, in UTF-16 units.
@ k_exfat_fsname_len
"EXFAT " field length.
@ k_exfat_entry_file
File directory entry.
@ k_exfat_foff_vol_len
VolumeLength (64-bit).
@ k_ra8_fs_type_exfat
exFAT (read + streaming write + format).
ra8_fs_mode_t
File-open modes accepted by ra8_fs_open().
@ k_ra8_fs_mode_read
Read-only, must exist.
uint8_t priv_utf16_all_ascii(const uint16_t *in, uint32_t units)
Is every unit of in inside the ASCII range?
Definition ra8_fs_utf.c:543
ra8_err_t priv_utf8_to_utf16(const char *in, uint16_t *out, uint32_t cap, uint32_t *out_units)
Convert a NUL-terminated UTF-8 name into UTF-16LE code units.
Definition ra8_fs_utf.c:283
Linear cursor over a directory's 32-byte entries.
uint32_t scanned
Total entries read (P10 bound).
uint32_t cluster
Current directory cluster.
uint32_t contig_end
One past the run's last cluster; 0 => FAT.
uint32_t entry_in_cluster
Next entry index within the cluster.
Identifies the exFAT directory a lookup, scan or link operates in.
uint32_t self_cluster
Parent cluster holding this dir's File entry; 0 => root.
uint32_t cluster
First cluster of the directory.
uint32_t self_index
File-entry index within self_cluster.
uint32_t contig_end
One past the run's last cluster; 0 => FAT-chained.
Open-file state.
Cached parse of one mounted FAT volume.
uint32_t reserved_sectors
BPB BPB_RsvdSecCnt.
uint32_t root_cluster
BPB BPB_RootClus (FAT32 only).
uint8_t exfat_upcase_ok
exFAT: volume's up-case table is ours.
ra8_fs_type_t type
FAT12 / FAT16 / FAT32.
uint32_t bytes_per_sector
Sector size (BPB / VBR == backend).
uint32_t root_entries
BPB BPB_RootEntCnt (FAT12/16).
uint64_t total_sectors
BPB TotSec / exFAT VolumeLength.
uint32_t sectors_per_cluster
BPB BPB_SecPerClus.
uint32_t num_fats
BPB BPB_NumFATs.
uint32_t fat_size_sectors
BPB BPB_FATSz16 / BPB_FATSz32.
uint64_t first_root_lba
FAT12/16 fixed root-dir start.
uint32_t count_of_clusters
Per MS spec: data_sectors / SPC.
uint64_t first_data_lba
First sector of the data region.
uint64_t first_fat_lba
Computed: first FAT sector.