ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_fs_fat_name.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
33typedef enum : uint16_t {
34 k_lfn_space = 0x20U,
35 k_lfn_del = 0x7FU,
42
43/* =============================================================================
44 * 8.3 short name pack / unpack
45 * =============================================================================
46 */
47
55/* `priv_to_upper()`: see header for the documented contract. */
56char priv_to_upper(char c)
57{
58 if (c >= 'a' && c <= 'z') {
59 return (char)(c - 'a' + 'A');
60 }
61 return c;
62}
63
87static uint8_t internal_pack_base(const char** path_io, uint8_t* out11)
88{
89 const char* path = *path_io;
90 uint8_t base_len = 0;
91 while (*path != '\0' && *path != '.') {
92 if (base_len >= k_filename_base_len) {
93 return 0U;
94 }
95 if ((uint32_t)(unsigned char)*path > (uint32_t)k_lfn_del) {
96 return 0U; /* a packed 8.3 name here is ASCII; a UTF-8 byte is not one */
97 }
98 out11[base_len++] = (uint8_t)priv_to_upper(*path++);
99 }
100 if (base_len == 0U) {
101 return 0U;
102 }
103 *path_io = path;
104 return 1U;
105}
106
129static uint8_t internal_pack_ext(const char* path, uint8_t* out11)
130{
131 if (*path != '.') {
132 return 1U;
133 }
134 path++;
135 uint32_t ext_len = 0U;
136 while (*path != '\0') {
137 if (ext_len >= (uint32_t)k_filename_ext_len) {
138 return 0U;
139 }
140 if ((uint32_t)(unsigned char)*path > (uint32_t)k_lfn_del) {
141 return 0U; /* as in the base: an 8.3 field never holds a UTF-8 byte */
142 }
143 out11[k_filename_base_len + ext_len] = (uint8_t)priv_to_upper(*path++);
144 ext_len++;
145 }
146 return 1U;
147}
148
149/* `priv_path_to_83()`: see header for the documented contract. */
150uint8_t priv_path_to_83(const char* path, uint8_t* out11)
151{
152 if (path == nullptr || out11 == nullptr) {
153 return 0U;
154 }
155 while (*path == '/') {
156 path++;
157 }
158 for (uint32_t i = 0; i < (uint32_t)k_max_8_3_name; i++) {
159 out11[i] = ' ';
160 }
161 if (internal_pack_base(&path, out11) == 0U) {
162 return 0U;
163 }
164 if (internal_pack_ext(path, out11) == 0U) {
165 return 0U;
166 }
167 /* No kanji escape on the way IN. It used to sit here, mapping a packed 0xE5
168 * first byte to 0x05 -- but both packers now refuse every byte above DEL, so
169 * no input can produce one, and a branch nothing reaches is not a safety net.
170 * The escape still matters on the way OUT, where ::priv_83_to_str() restores
171 * it for a volume some Shift-JIS system wrote. */
172 return 1U;
173}
174
200static char internal_case_apply(char c, uint8_t lower)
201{
202 if (lower == 0U) {
203 return c;
204 }
205 if ((c >= 'A') && (c <= 'Z')) {
206 /* Arithmetic in an unsigned type, not on the chars themselves (MISRA 10.2
207 * forbids + / - on essentially-character operands), and the cast lands on
208 * a plain object rather than on the composite expression (MISRA 10.8). */
209 const uint32_t lowered = (uint32_t)c + ((uint32_t)'a' - (uint32_t)'A');
210 return (char)lowered;
211 }
212 return c;
213}
214
215/* `priv_83_to_str()`: see header for the documented contract. */
216void priv_83_to_str(const uint8_t* in11, uint8_t ntres, char* out13)
217{
218 const uint8_t base_lower = (uint8_t)(ntres & (uint8_t)k_ntres_base_lower);
219 const uint8_t ext_lower = (uint8_t)(ntres & (uint8_t)k_ntres_ext_lower);
220 uint32_t i = 0;
221 uint32_t j = 0;
222 for (i = 0; i < (uint32_t)k_filename_base_len; i++) {
223 if (in11[i] == ' ') {
224 break;
225 }
226 out13[j++] = internal_case_apply((char)in11[i], base_lower);
227 }
228 /* Restore kanji escape. */
229 if (in11[0] == (uint8_t)k_dir_marker_kanji_e5) {
230 out13[0] = (char)k_dir_marker_free_used;
231 }
232 uint8_t has_ext = 0;
233 for (i = 0; i < k_filename_ext_len; i++) {
234 if (in11[k_filename_base_len + i] != ' ') {
235 has_ext = 1;
236 break;
237 }
238 }
239 if (has_ext != 0U) {
240 out13[j++] = '.';
241 for (i = 0; i < k_filename_ext_len; i++) {
242 if (in11[k_filename_base_len + i] == ' ') {
243 break;
244 }
245 out13[j++] = internal_case_apply((char)in11[k_filename_base_len + i], ext_lower);
246 }
247 }
248 out13[j] = '\0';
249}
250
251/* =============================================================================
252 * Long-name classification -- which on-disk shape a leaf component needs
253 * =============================================================================
254 */
255
281static uint8_t internal_unit_in_set(uint16_t u, const char* set)
282{
283 for (uint32_t i = 0U; i < (uint32_t)k_lfn_write_max; i++) {
284 if (set[i] == '\0') {
285 return 0U;
286 }
287 if ((uint32_t)(unsigned char)set[i] == (uint32_t)u) {
288 return 1U;
289 }
290 }
291 /* Unreachable: both sets are single-digit literals, so the NUL above always
292 * ends the scan first. This is the Rule 2 bound's exit, not a second answer. */
293 return 0U; /* GCOVR_EXCL_LINE -- arith/string invariants make fallback unreachable */
294}
295
322static uint8_t internal_unit_is_lfn_legal(uint16_t u)
323{
324 /* Microsoft FAT specification section 7 ("Long File Name Implementation"):
325 * the long name inherits the short name's illegal set minus the characters
326 * VFAT deliberately allows (`+ , ; = [ ]` and space). Control characters are
327 * excluded by range below, not by this table. Block scope because exactly
328 * one function reads it (MISRA 8.9). */
329 static const char lfn_illegal[] = "\"*/:<>?\\|";
330
331 const uint32_t v = (uint32_t)u;
332 if (v < (uint32_t)k_lfn_space) {
333 return 0U; /* control characters, including the NUL that ends the name */
334 }
335 if (v == (uint32_t)k_lfn_del) {
336 return 0U; /* DEL is a control code wherever it appears */
337 }
338 if (internal_unit_in_set(u, lfn_illegal) != 0U) {
339 return 0U;
340 }
341 return 1U;
342}
343
370static uint8_t internal_unit_is_83_legal(uint16_t u)
371{
372 /* Microsoft FAT specification section 6.1 ("Name Limitations"). A leaf
373 * containing any of these is representable only as a long name, however
374 * short it is -- `my file.txt` is eight characters of base and still cannot
375 * be an 8.3 name, because of the space. Block scope: one reader (MISRA 8.9). */
376 static const char sfn_extra_illegal[] = "+,;=[] ";
377
378 if ((uint32_t)u > (uint32_t)k_lfn_del) {
379 return 0U;
380 }
381 if (u == (uint16_t)(unsigned char)'.') {
382 return 0U;
383 }
384 if (internal_unit_in_set(u, sfn_extra_illegal) != 0U) {
385 return 0U;
386 }
387 return 1U;
388}
389
415static uint32_t internal_name_dot_index(const uint16_t* leaf, uint32_t n)
416{
417 uint32_t dot = n;
418 for (uint32_t i = 0U; i < n; i++) {
419 if (leaf[i] == (uint16_t)(unsigned char)'.') {
420 dot = i;
421 }
422 }
423 return dot;
424}
425
454static uint8_t internal_name_is_83(const uint16_t* leaf, uint32_t n)
455{
456 const uint32_t dot = internal_name_dot_index(leaf, n);
457 const uint32_t base_len = dot;
458 if ((base_len == 0U) || (base_len > (uint32_t)k_filename_base_len)) {
459 return 0U;
460 }
461 if (dot < n) {
462 const uint32_t ext_len = (n - dot) - 1U;
463 if ((ext_len == 0U) || (ext_len > (uint32_t)k_filename_ext_len)) {
464 return 0U;
465 }
466 }
467 for (uint32_t i = 0U; i < n; i++) {
468 if (i == dot) {
469 continue;
470 }
471 if (internal_unit_is_83_legal(leaf[i]) == 0U) {
472 return 0U;
473 }
474 }
475 return 1U;
476}
477
504static void internal_case_observe(const uint16_t* leaf, uint32_t from, uint32_t to, uint8_t* seen)
505{
506 for (uint32_t i = from; i < to; i++) {
507 const uint32_t c = (uint32_t)leaf[i];
508 if ((c >= (uint32_t)'A') && (c <= (uint32_t)'Z')) {
509 *seen |= (uint8_t)k_case_seen_upper;
510 } else if ((c >= (uint32_t)'a') && (c <= (uint32_t)'z')) {
511 *seen |= (uint8_t)k_case_seen_lower;
512 } else {
513 /* Digits and punctuation carry no case; they constrain nothing. */
514 }
515 }
516}
517
545internal_name_case_kind(const uint16_t* leaf, uint32_t n, uint8_t* out_ntres)
546{
547 const uint32_t dot = internal_name_dot_index(leaf, n);
548 uint8_t base_seen = 0U;
549 uint8_t ext_seen = 0U;
550 internal_case_observe(leaf, 0U, dot, &base_seen);
551 if (dot < n) {
552 internal_case_observe(leaf, dot + 1U, n, &ext_seen);
553 }
554 *out_ntres = 0U;
555 if (base_seen == (uint8_t)k_case_seen_mixed) {
556 return k_name_kind_long;
557 }
558 if (ext_seen == (uint8_t)k_case_seen_mixed) {
559 return k_name_kind_long;
560 }
561 if (base_seen == (uint8_t)k_case_seen_lower) {
562 *out_ntres |= (uint8_t)k_ntres_base_lower;
563 }
564 if (ext_seen == (uint8_t)k_case_seen_lower) {
565 *out_ntres |= (uint8_t)k_ntres_ext_lower;
566 }
567 return k_name_kind_short;
568}
569
570/* `priv_name_classify()`: see header for the documented contract. */
572 uint16_t* out_units,
573 uint32_t* out_nunits,
574 uint8_t* out83,
575 uint8_t* out_ntres)
576{
577 if ((leaf == nullptr) || (out_units == nullptr) || (out_nunits == nullptr)) {
578 return k_name_kind_invalid;
579 }
580 if ((out83 == nullptr) || (out_ntres == nullptr)) {
581 return k_name_kind_invalid;
582 }
583 *out_ntres = 0U;
584 /* The decode is where a malformed name dies -- an over-long sequence, a raw
585 * surrogate, a truncated one -- and the unit count it produces is the length
586 * every on-disk structure below counts in. A byte count is neither (#606). */
587 uint32_t n = 0U;
588 if (priv_utf8_to_utf16(leaf, out_units, (uint32_t)k_lfn_write_max, &n) != k_ra8_ok) {
589 *out_nunits = 0U;
590 return k_name_kind_invalid;
591 }
592 *out_nunits = n;
593 if (n == 0U) {
594 return k_name_kind_invalid;
595 }
596 for (uint32_t i = 0U; i < n; i++) {
597 if (internal_unit_is_lfn_legal(out_units[i]) == 0U) {
598 return k_name_kind_invalid;
599 }
600 }
601 if (internal_name_is_83(out_units, n) == 0U) {
602 return k_name_kind_long;
603 }
604 if (priv_path_to_83(leaf, out83) == 0U) {
605 /* Unreachable: priv_name_is_83() already proved the base and extension
606 * lengths priv_path_to_83() checks. Kept because the two functions are
607 * separately maintained and a silent disagreement would create an
608 * unpacked name. */
609 return k_name_kind_long; /* GCOVR_EXCL_LINE -- string invariants make fallback unreachable */
610 }
611 return internal_name_case_kind(out_units, n, out_ntres);
612}
613
614/* =============================================================================
615 * Basis-name generation -- the `LONGNA~1.TXT` alias a long name is filed under
616 * =============================================================================
617 */
618
646static uint8_t internal_alias_map_unit(uint16_t u)
647{
648 if ((uint32_t)u > (uint32_t)k_lfn_del) {
649 return (uint8_t)'_';
650 }
651 const char up = priv_to_upper((char)(unsigned char)u);
652 if (internal_unit_is_83_legal((uint16_t)(unsigned char)up) == 0U) {
653 return (uint8_t)'_';
654 }
655 return (uint8_t)up;
656}
657
684static uint32_t
685internal_alias_collect(const uint16_t* leaf, uint32_t from, uint32_t to, uint32_t cap, uint8_t* out)
686{
687 uint32_t got = 0U;
688 for (uint32_t i = from; (i < to) && (got < cap); i++) {
689 const uint16_t u = leaf[i];
690 if ((u == (uint16_t)(unsigned char)' ') || (u == (uint16_t)(unsigned char)'.')) {
691 continue;
692 }
693 out[got] = internal_alias_map_unit(u);
694 got++;
695 }
696 return got;
697}
698
723static uint32_t internal_alias_ext_dot(const uint16_t* leaf, uint32_t n)
724{
725 uint32_t dot = n;
726 for (uint32_t i = 1U; (i + 1U) < n; i++) {
727 if (leaf[i] == (uint16_t)(unsigned char)'.') {
728 dot = i;
729 }
730 }
731 return dot;
732}
733
756static uint32_t internal_alias_digits(uint32_t tail)
757{
758 uint32_t digits = 1U;
759 uint32_t scale = (uint32_t)k_alias_radix;
760 /* Bounded by the six-digit cap the alias probe never exceeds. */
761 while ((digits < (uint32_t)k_alias_digits_max) && (tail >= scale)) {
762 digits++;
763 scale *= (uint32_t)k_alias_radix;
764 }
765 return digits;
766}
767
768/* `priv_lfn_alias_basis()`: see header for the documented contract. */
769void priv_lfn_alias_basis(const uint16_t* leaf, uint32_t n, uint32_t tail, uint8_t* out11)
770{
771 for (uint32_t i = 0U; i < (uint32_t)k_max_8_3_name; i++) {
772 out11[i] = ' ';
773 }
774 const uint32_t dot = internal_alias_ext_dot(leaf, n);
775 uint8_t base[k_filename_base_len] = {};
776 uint32_t base_len = internal_alias_collect(leaf, 0U, dot, (uint32_t)k_filename_base_len, base);
777 if (base_len == 0U) {
778 base[0] = (uint8_t)'_'; /* a name of nothing but dots and spaces */
779 base_len = 1U;
780 }
781 if (dot < n) {
782 (void)internal_alias_collect(leaf,
783 dot + 1U,
784 n,
785 (uint32_t)k_filename_ext_len,
786 &out11[k_filename_base_len]);
787 }
788 const uint32_t digits = internal_alias_digits(tail);
789 uint32_t keep = ((uint32_t)k_filename_base_len - digits) - 1U;
790 if (base_len < keep) {
791 keep = base_len;
792 }
793 for (uint32_t i = 0U; i < keep; i++) {
794 out11[i] = base[i];
795 }
796 out11[keep] = (uint8_t)'~';
797 uint32_t rest = tail;
798 for (uint32_t d = 0U; d < digits; d++) {
799 const uint32_t digit = rest % (uint32_t)k_alias_radix;
800 out11[(keep + digits) - d] = (uint8_t)((uint32_t)'0' + digit);
801 rest /= (uint32_t)k_alias_radix;
802 }
803 /* No kanji escape is needed here: every byte written above is an upper-case
804 * ASCII letter, a digit, '_', '~' or a space, and none of those is 0xE5. */
805}
806
807/* =============================================================================
808 * Directory walking
809 * =============================================================================
810 */
811
832{
833 if (m->type == k_ra8_fs_type_fat32) {
834 w->is_root_fixed = 0;
835 w->fixed_remaining = 0;
836 w->cluster = m->root_cluster;
837 w->sector_in_cluster = 0;
839 } else {
840 const uint32_t root_dir_sectors =
841 ((m->root_entries * k_ra8_fs_dir_entry_bytes) + (priv_bps(m) - 1U)) / priv_bps(m);
842 w->is_root_fixed = 1;
843 w->fixed_remaining = root_dir_sectors;
844 w->cluster = 0;
845 w->sector_in_cluster = 0;
846 w->cur_lba = m->first_root_lba;
847 }
848 w->entry_idx = 0;
849 w->cluster_hops = 0;
850}
851
852/* `priv_dir_walk_init_loc()`: see header for the documented contract. */
854{
855 if (loc->is_root != 0U) {
857 return;
858 }
859 w->is_root_fixed = 0;
860 w->fixed_remaining = 0;
861 w->cluster = loc->cluster;
862 w->sector_in_cluster = 0;
863 w->cur_lba = priv_cluster_to_lba(m, loc->cluster);
864 w->entry_idx = 0;
865 w->cluster_hops = 0;
866}
867
868/* `priv_dir_walk_next_sector()`: see header for the documented contract. */
870{
871 *out_eod = 0;
872 if (w->is_root_fixed != 0U) {
873 if (w->fixed_remaining <= 1U) {
874 *out_eod = 1;
875 return k_ra8_ok;
876 }
877 w->fixed_remaining--;
878 w->cur_lba++;
879 w->entry_idx = 0;
880 return k_ra8_ok;
881 }
882 /* FAT32 cluster-chain root. */
885 uint32_t next = 0;
886 ra8_err_t err = priv_fat_get(m, w->cluster, &next);
887 if (err != k_ra8_ok) {
888 return err;
889 }
890 if (priv_is_eoc(m, next) != 0U) {
891 *out_eod = 1;
892 return k_ra8_ok;
893 }
894 /* Cycle guard (NASA Rule 2): a healthy chain visits at most
895 * count_of_clusters distinct clusters; more means a corrupt loop. */
896 w->cluster_hops++;
897 if (w->cluster_hops > m->count_of_clusters) {
899 }
900 w->cluster = next;
901 w->sector_in_cluster = 0;
903 } else {
904 w->cur_lba++;
905 }
906 w->entry_idx = 0;
907 return k_ra8_ok;
908}
909
910/* `priv_dir_find()`: see header for the documented contract. */
912 const dir_loc_t* loc,
913 const uint8_t* name83,
914 uint64_t* out_lba,
915 uint32_t* out_entry_off,
916 uint8_t out_entry[k_ra8_fs_dir_entry_bytes])
917{
918 dir_walk_t w = {};
919 priv_dir_walk_init_loc(m, loc, &w);
920 uint8_t eod = 0;
921 uint8_t* const buf = priv_sec_walk();
922 while (eod == 0U) {
923 ra8_err_t err = priv_read_sector(m, w.cur_lba, buf);
924 if (err != k_ra8_ok) {
925 return err;
926 }
927 for (uint32_t e = 0; e < priv_dir_eps(m); e++) {
928 const uint8_t* ent = &buf[(size_t)e * (size_t)k_ra8_fs_dir_entry_bytes];
930 return k_ra8_err_not_found;
931 }
933 continue;
934 }
935 if (ent[k_dir_off_attr] == k_ra8_fs_attr_lfn) {
936 continue;
937 }
938 if (priv_byte_equal(ent, name83, k_dir_name_field_len) != 0U) {
939 *out_lba = w.cur_lba;
940 *out_entry_off = e * (uint32_t)k_ra8_fs_dir_entry_bytes;
942 return k_ra8_ok;
943 }
944 }
945 err = priv_dir_walk_next_sector(m, &w, &eod);
946 if (err != k_ra8_ok) {
947 return err;
948 }
949 }
950 return k_ra8_err_not_found;
951}
Annotation-attribute framework macros for ra8-firmware.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ k_ra8_err_protocol_error
Protocol-level error (e.g.
Definition ra8_err.h:429
@ 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).
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
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
uint32_t priv_dir_eps(const ra8_fs_mount_t *m)
Directory entries per sector on one mounted volume.
Definition ra8_fs_fat.c:96
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
uint8_t priv_byte_equal(const uint8_t *a, const uint8_t *b, uint32_t n)
Compare two byte buffers for equality (length n).
Definition ra8_fs_fat.c:118
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
uint8_t * priv_sec_walk(void)
The WALK-role sector buffer (directory scans and entry RMW).
Cross-TU shared declarations for the FAT/exFAT ra8_fs adapter.
void priv_lfn_alias_basis(const uint16_t *leaf, uint32_t n, uint32_t tail, uint8_t *out11)
Derive the LONGNA~N.TXT 8.3 alias a long name is filed under.
static char internal_case_apply(char c, uint8_t lower)
Lower-case one ASCII character when a DIR_NTRes flag asks for it.
static uint8_t internal_name_is_83(const uint16_t *leaf, uint32_t n)
Can leaf be stored as a packed 8.3 name without losing anything?
void priv_dir_walk_init_loc(const ra8_fs_mount_t *m, const dir_loc_t *loc, dir_walk_t *w)
Initialise a directory walker for an arbitrary directory location.
static uint32_t internal_alias_digits(uint32_t tail)
Decimal digit count of tail (1..k_lfn_alias_tail_max).
ra8_fs_name_local_t
Small constants used only by this translation unit's name analysis.
@ k_case_seen_upper
An upper-case letter was seen.
@ k_alias_radix
The alias tail is written in decimal.
@ k_lfn_space
Lowest character code a long name may hold.
@ k_alias_digits_max
Digits in k_lfn_alias_tail_max.
@ k_case_seen_mixed
Both were: no NTRes flag can express it.
@ k_case_seen_lower
A lower-case letter was seen.
@ k_lfn_del
DEL: a control code, not a name character.
static uint8_t internal_alias_map_unit(uint16_t u)
Map one long-name character into the short-name character set.
uint8_t priv_path_to_83(const char *path, uint8_t *out11)
Convert a "/FILE.TXT"-style path to packed 11-byte 8.3 form.
static uint8_t internal_pack_base(const char **path_io, uint8_t *out11)
Pack the base portion of a path into out11[0..7].
static uint8_t internal_unit_is_83_legal(uint16_t u)
Is c usable verbatim inside a packed 8.3 short name?
static void internal_case_observe(const uint16_t *leaf, uint32_t from, uint32_t to, uint8_t *seen)
Fold one half of a name into "has upper" / "has lower" observations.
static void internal_dir_walk_init_root(const ra8_fs_mount_t *m, dir_walk_t *w)
Initialise a walker that iterates the volume root directory.
static uint8_t internal_unit_is_lfn_legal(uint16_t u)
Is u a code unit a VFAT long name is allowed to hold?
ra8_err_t priv_dir_find(const ra8_fs_mount_t *m, const dir_loc_t *loc, const uint8_t *name83, uint64_t *out_lba, uint32_t *out_entry_off, uint8_t out_entry[k_ra8_fs_dir_entry_bytes])
Find a directory entry by 8.3 name within a given directory.
static uint32_t internal_name_dot_index(const uint16_t *leaf, uint32_t n)
Index of the field-separating dot, or n when there is none.
static ra8_fs_name_kind_t internal_name_case_kind(const uint16_t *leaf, uint32_t n, uint8_t *out_ntres)
Decide the case shape of an 8.3-representable leaf.
static uint32_t internal_alias_collect(const uint16_t *leaf, uint32_t from, uint32_t to, uint32_t cap, uint8_t *out)
Collect up to cap alias characters from from up to to.
static uint32_t internal_alias_ext_dot(const uint16_t *leaf, uint32_t n)
Index of the dot that separates the alias extension, or n if none.
static uint8_t internal_unit_in_set(uint16_t u, const char *set)
Does u appear in the NUL-terminated ASCII set set?
ra8_err_t priv_dir_walk_next_sector(const ra8_fs_mount_t *m, dir_walk_t *w, uint8_t *out_eod)
Advance the walker to the next sector.
char priv_to_upper(char c)
Convert "FILE.TXT" (caller-supplied path) to packed 11-byte 8.3.
ra8_fs_name_kind_t priv_name_classify(const char *leaf, uint16_t *out_units, uint32_t *out_nunits, uint8_t *out83, uint8_t *out_ntres)
Decide whether a leaf needs one entry, one entry plus case flags, or a chain.
static uint8_t internal_pack_ext(const char *path, uint8_t *out11)
Pack the extension portion of a path into out11[8..10].
void priv_83_to_str(const uint8_t *in11, uint8_t ntres, char *out13)
Unpack on-disk 11-byte 8.3 name into NUL-terminated "NAME.EXT".
ra8_fs_name_kind_t
How a leaf component has to be stored in a FAT directory.
@ k_name_kind_invalid
Empty, over-long, or holds an illegal character.
@ k_name_kind_short
Fits 8.3; store one entry (+ NTRes case bits).
@ k_name_kind_long
Needs a VFAT chain and a generated ~N alias.
@ k_dir_off_attr
MS FAT spec sec 6 "DIR_Attr".
@ k_dir_name_field_len
8 + 3 raw chars (no dot).
@ k_dir_off_name
MS FAT spec sec 6 "DIR_Name" (11 bytes).
@ k_dir_marker_free_used
Slot was used, deleted.
@ k_dir_marker_kanji_e5
0xE5 in raw name, escaped.
@ k_dir_marker_free_perm
End-of-directory.
@ k_max_8_3_name
8.3 packed length without dot.
@ k_filename_ext_len
Filename ext length.
@ k_filename_base_len
Filename base length.
@ k_lfn_write_max
Longest name we WRITE (k_lfn_max_entries).
@ k_ntres_base_lower
Render DIR_Name[0..7] lower case.
@ k_ntres_ext_lower
Render DIR_Name[8..10] lower case.
@ k_ra8_fs_type_fat32
count_of_clusters >= 65525.
@ k_ra8_fs_attr_lfn
Long-file-name marker (we skip).
@ k_ra8_fs_dir_entry_bytes
MS FAT spec sec 6 "Directory Entry".
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
Identifies the directory a lookup/scan should operate in.
uint8_t is_root
1 => the volume root; 0 => the subdirectory at cluster.
uint32_t cluster
First cluster of the subdirectory (ignored when root).
Internal cursor used by the directory iterator.
uint32_t cluster
Current cluster (FAT32 root case).
uint32_t sector_in_cluster
0..SPC-1 inside cluster.
uint64_t cur_lba
Currently loaded LBA.
uint32_t entry_idx
Byte offset within the loaded sector.
uint32_t cluster_hops
FAT-chain follows so far (cycle guard).
uint32_t fixed_remaining
Sectors left in fixed region.
uint8_t is_root_fixed
1 = FAT12/16 fixed root dir region.
Cached parse of one mounted FAT volume.
uint32_t root_cluster
BPB BPB_RootClus (FAT32 only).
ra8_fs_type_t type
FAT12 / FAT16 / FAT32.
uint32_t root_entries
BPB BPB_RootEntCnt (FAT12/16).
uint32_t sectors_per_cluster
BPB BPB_SecPerClus.
uint64_t first_root_lba
FAT12/16 fixed root-dir start.
uint32_t count_of_clusters
Per MS spec: data_sectors / SPC.